Namespaces

  • Latte
    • Loaders
    • Macros
    • Runtime
  • Nette
    • Application
      • Responses
      • Routers
      • UI
    • Bridges
      • ApplicationDI
      • ApplicationLatte
      • ApplicationTracy
      • CacheDI
      • CacheLatte
      • DatabaseDI
      • DatabaseTracy
      • DITracy
      • FormsDI
      • FormsLatte
      • Framework
      • HttpDI
      • HttpTracy
      • MailDI
      • ReflectionDI
      • SecurityDI
      • SecurityTracy
    • Caching
      • Storages
    • ComponentModel
    • Database
      • Conventions
      • Drivers
      • Table
    • DI
      • Config
        • Adapters
      • Extensions
    • Forms
      • Controls
      • Rendering
    • Http
    • Iterators
    • Loaders
    • Localization
    • Mail
    • Neon
    • PhpGenerator
    • Reflection
    • Security
    • Utils
  • none
  • Tracy
    • Bridges
      • Nette

Classes

  • Context
  • FileUpload
  • Helpers
  • Request
  • RequestFactory
  • Response
  • Session
  • SessionSection
  • Url
  • UrlScript
  • UserStorage

Interfaces

  • IRequest
  • IResponse
  • ISessionStorage
  • Overview
  • Namespace
  • Class
  • Tree
  • Deprecated
  1: <?php
  2: 
  3: /**
  4:  * This file is part of the Nette Framework (https://nette.org)
  5:  * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
  6:  */
  7: 
  8: namespace Nette\Http;
  9: 
 10: use Nette;
 11: 
 12: 
 13: /**
 14:  * Provides access to session sections as well as session settings and management methods.
 15:  */
 16: class Session
 17: {
 18:     use Nette\SmartObject;
 19: 
 20:     /** Default file lifetime */
 21:     const DEFAULT_FILE_LIFETIME = 3 * Nette\Utils\DateTime::HOUR;
 22: 
 23:     /** @var bool  has been session ID regenerated? */
 24:     private $regenerated = FALSE;
 25: 
 26:     /** @var bool  has been session started? */
 27:     private static $started = FALSE;
 28: 
 29:     /** @var array default configuration */
 30:     private $options = [
 31:         // security
 32:         'referer_check' => '',    // must be disabled because PHP implementation is invalid
 33:         'use_cookies' => 1,       // must be enabled to prevent Session Hijacking and Fixation
 34:         'use_only_cookies' => 1,  // must be enabled to prevent Session Fixation
 35:         'use_trans_sid' => 0,     // must be disabled to prevent Session Hijacking and Fixation
 36: 
 37:         // cookies
 38:         'cookie_lifetime' => 0,   // until the browser is closed
 39:         'cookie_path' => '/',     // cookie is available within the entire domain
 40:         'cookie_domain' => '',    // cookie is available on current subdomain only
 41:         'cookie_secure' => FALSE, // cookie is available on HTTP & HTTPS
 42:         'cookie_httponly' => TRUE,// must be enabled to prevent Session Hijacking
 43: 
 44:         // other
 45:         'gc_maxlifetime' => self::DEFAULT_FILE_LIFETIME, // 3 hours
 46:     ];
 47: 
 48:     /** @var IRequest */
 49:     private $request;
 50: 
 51:     /** @var IResponse */
 52:     private $response;
 53: 
 54:     /** @var \SessionHandlerInterface */
 55:     private $handler;
 56: 
 57: 
 58:     public function __construct(IRequest $request, IResponse $response)
 59:     {
 60:         $this->request = $request;
 61:         $this->response = $response;
 62:     }
 63: 
 64: 
 65:     /**
 66:      * Starts and initializes session data.
 67:      * @throws Nette\InvalidStateException
 68:      * @return void
 69:      */
 70:     public function start()
 71:     {
 72:         if (self::$started) {
 73:             return;
 74:         }
 75: 
 76:         $this->configure($this->options);
 77: 
 78:         $id = $this->request->getCookie(session_name());
 79:         if (is_string($id) && preg_match('#^[0-9a-zA-Z,-]{22,256}\z#i', $id)) {
 80:             session_id($id);
 81:         } else {
 82:             unset($_COOKIE[session_name()]);
 83:         }
 84: 
 85:         try {
 86:             // session_start returns FALSE on failure only sometimes
 87:             Nette\Utils\Callback::invokeSafe('session_start', [], function ($message) use (&$e) {
 88:                 $e = new Nette\InvalidStateException($message);
 89:             });
 90:         } catch (\Exception $e) {
 91:         }
 92: 
 93:         if ($e) {
 94:             @session_write_close(); // this is needed
 95:             throw $e;
 96:         }
 97: 
 98:         self::$started = TRUE;
 99: 
100:         /* structure:
101:             __NF: Data, Meta, Time
102:                 DATA: section->variable = data
103:                 META: section->variable = Timestamp
104:         */
105:         $nf = &$_SESSION['__NF'];
106: 
107:         if (!is_array($nf)) {
108:             $nf = [];
109:         }
110: 
111:         // regenerate empty session
112:         if (empty($nf['Time'])) {
113:             $nf['Time'] = time();
114:             $this->regenerated = TRUE;
115:         }
116: 
117:         // resend cookie
118:         $this->sendCookie();
119: 
120:         // process meta metadata
121:         if (isset($nf['META'])) {
122:             $now = time();
123:             // expire section variables
124:             foreach ($nf['META'] as $section => $metadata) {
125:                 if (is_array($metadata)) {
126:                     foreach ($metadata as $variable => $value) {
127:                         if (!empty($value['T']) && $now > $value['T']) {
128:                             if ($variable === '') { // expire whole section
129:                                 unset($nf['META'][$section], $nf['DATA'][$section]);
130:                                 continue 2;
131:                             }
132:                             unset($nf['META'][$section][$variable], $nf['DATA'][$section][$variable]);
133:                         }
134:                     }
135:                 }
136:             }
137:         }
138: 
139:         if ($this->regenerated) {
140:             $this->regenerated = FALSE;
141:             $this->regenerateId();
142:         }
143: 
144:         register_shutdown_function([$this, 'clean']);
145:     }
146: 
147: 
148:     /**
149:      * Has been session started?
150:      * @return bool
151:      */
152:     public function isStarted()
153:     {
154:         return (bool) self::$started;
155:     }
156: 
157: 
158:     /**
159:      * Ends the current session and store session data.
160:      * @return void
161:      */
162:     public function close()
163:     {
164:         if (self::$started) {
165:             $this->clean();
166:             session_write_close();
167:             self::$started = FALSE;
168:         }
169:     }
170: 
171: 
172:     /**
173:      * Destroys all data registered to a session.
174:      * @return void
175:      */
176:     public function destroy()
177:     {
178:         if (!self::$started) {
179:             throw new Nette\InvalidStateException('Session is not started.');
180:         }
181: 
182:         session_destroy();
183:         $_SESSION = NULL;
184:         self::$started = FALSE;
185:         if (!$this->response->isSent()) {
186:             $params = session_get_cookie_params();
187:             $this->response->deleteCookie(session_name(), $params['path'], $params['domain'], $params['secure']);
188:         }
189:     }
190: 
191: 
192:     /**
193:      * Does session exists for the current request?
194:      * @return bool
195:      */
196:     public function exists()
197:     {
198:         return self::$started || $this->request->getCookie($this->getName()) !== NULL;
199:     }
200: 
201: 
202:     /**
203:      * Regenerates the session ID.
204:      * @throws Nette\InvalidStateException
205:      * @return void
206:      */
207:     public function regenerateId()
208:     {
209:         if (self::$started && !$this->regenerated) {
210:             if (headers_sent($file, $line)) {
211:                 throw new Nette\InvalidStateException('Cannot regenerate session ID after HTTP headers have been sent' . ($file ? " (output started at $file:$line)." : '.'));
212:             }
213:             if (session_status() === PHP_SESSION_ACTIVE) {
214:                 session_regenerate_id(TRUE);
215:                 session_write_close();
216:             }
217:             $backup = $_SESSION;
218:             session_start();
219:             $_SESSION = $backup;
220:         }
221:         $this->regenerated = TRUE;
222:     }
223: 
224: 
225:     /**
226:      * Returns the current session ID. Don't make dependencies, can be changed for each request.
227:      * @return string
228:      */
229:     public function getId()
230:     {
231:         return session_id();
232:     }
233: 
234: 
235:     /**
236:      * Sets the session name to a specified one.
237:      * @param  string
238:      * @return static
239:      */
240:     public function setName($name)
241:     {
242:         if (!is_string($name) || !preg_match('#[^0-9.][^.]*\z#A', $name)) {
243:             throw new Nette\InvalidArgumentException('Session name must be a string and cannot contain dot.');
244:         }
245: 
246:         session_name($name);
247:         return $this->setOptions([
248:             'name' => $name,
249:         ]);
250:     }
251: 
252: 
253:     /**
254:      * Gets the session name.
255:      * @return string
256:      */
257:     public function getName()
258:     {
259:         return isset($this->options['name']) ? $this->options['name'] : session_name();
260:     }
261: 
262: 
263:     /********************* sections management ****************d*g**/
264: 
265: 
266:     /**
267:      * Returns specified session section.
268:      * @param  string
269:      * @param  string
270:      * @return SessionSection
271:      * @throws Nette\InvalidArgumentException
272:      */
273:     public function getSection($section, $class = SessionSection::class)
274:     {
275:         return new $class($this, $section);
276:     }
277: 
278: 
279:     /**
280:      * Checks if a session section exist and is not empty.
281:      * @param  string
282:      * @return bool
283:      */
284:     public function hasSection($section)
285:     {
286:         if ($this->exists() && !self::$started) {
287:             $this->start();
288:         }
289: 
290:         return !empty($_SESSION['__NF']['DATA'][$section]);
291:     }
292: 
293: 
294:     /**
295:      * Iteration over all sections.
296:      * @return \ArrayIterator
297:      */
298:     public function getIterator()
299:     {
300:         if ($this->exists() && !self::$started) {
301:             $this->start();
302:         }
303: 
304:         if (isset($_SESSION['__NF']['DATA'])) {
305:             return new \ArrayIterator(array_keys($_SESSION['__NF']['DATA']));
306: 
307:         } else {
308:             return new \ArrayIterator;
309:         }
310:     }
311: 
312: 
313:     /**
314:      * Cleans and minimizes meta structures. This method is called automatically on shutdown, do not call it directly.
315:      * @internal
316:      * @return void
317:      */
318:     public function clean()
319:     {
320:         if (!self::$started || empty($_SESSION)) {
321:             return;
322:         }
323: 
324:         $nf = &$_SESSION['__NF'];
325:         if (isset($nf['META']) && is_array($nf['META'])) {
326:             foreach ($nf['META'] as $name => $foo) {
327:                 if (empty($nf['META'][$name])) {
328:                     unset($nf['META'][$name]);
329:                 }
330:             }
331:         }
332: 
333:         if (empty($nf['META'])) {
334:             unset($nf['META']);
335:         }
336: 
337:         if (empty($nf['DATA'])) {
338:             unset($nf['DATA']);
339:         }
340:     }
341: 
342: 
343:     /********************* configuration ****************d*g**/
344: 
345: 
346:     /**
347:      * Sets session options.
348:      * @param  array
349:      * @return static
350:      * @throws Nette\NotSupportedException
351:      * @throws Nette\InvalidStateException
352:      */
353:     public function setOptions(array $options)
354:     {
355:         if (self::$started) {
356:             $this->configure($options);
357:         }
358:         $this->options = $options + $this->options;
359:         if (!empty($options['auto_start'])) {
360:             $this->start();
361:         }
362:         return $this;
363:     }
364: 
365: 
366:     /**
367:      * Returns all session options.
368:      * @return array
369:      */
370:     public function getOptions()
371:     {
372:         return $this->options;
373:     }
374: 
375: 
376:     /**
377:      * Configures session environment.
378:      * @param  array
379:      * @return void
380:      */
381:     private function configure(array $config)
382:     {
383:         $special = ['cache_expire' => 1, 'cache_limiter' => 1, 'save_path' => 1, 'name' => 1];
384: 
385:         foreach ($config as $key => $value) {
386:             if (!strncmp($key, 'session.', 8)) { // back compatibility
387:                 $key = substr($key, 8);
388:             }
389:             $key = strtolower(preg_replace('#(.)(?=[A-Z])#', '$1_', $key));
390: 
391:             if ($value === NULL || ini_get("session.$key") == $value) { // intentionally ==
392:                 continue;
393: 
394:             } elseif (strncmp($key, 'cookie_', 7) === 0) {
395:                 if (!isset($cookie)) {
396:                     $cookie = session_get_cookie_params();
397:                 }
398:                 $cookie[substr($key, 7)] = $value;
399: 
400:             } else {
401:                 if (session_status() === PHP_SESSION_ACTIVE) {
402:                     throw new Nette\InvalidStateException("Unable to set 'session.$key' to value '$value' when session has been started" . (self::$started ? '.' : ' by session.auto_start or session_start().'));
403:                 }
404:                 if (isset($special[$key])) {
405:                     $key = "session_$key";
406:                     $key($value);
407: 
408:                 } elseif (function_exists('ini_set')) {
409:                     ini_set("session.$key", (string) $value);
410: 
411:                 } elseif (ini_get("session.$key") != $value) { // intentionally !=
412:                     throw new Nette\NotSupportedException("Unable to set 'session.$key' to '$value' because function ini_set() is disabled.");
413:                 }
414:             }
415:         }
416: 
417:         if (isset($cookie)) {
418:             session_set_cookie_params(
419:                 $cookie['lifetime'], $cookie['path'], $cookie['domain'],
420:                 $cookie['secure'], $cookie['httponly']
421:             );
422:             if (self::$started) {
423:                 $this->sendCookie();
424:             }
425:         }
426: 
427:         if ($this->handler) {
428:             session_set_save_handler($this->handler);
429:         }
430:     }
431: 
432: 
433:     /**
434:      * Sets the amount of time allowed between requests before the session will be terminated.
435:      * @param  string|int|\DateTimeInterface  time, value 0 means "until the browser is closed"
436:      * @return static
437:      */
438:     public function setExpiration($time)
439:     {
440:         if (empty($time)) {
441:             return $this->setOptions([
442:                 'gc_maxlifetime' => self::DEFAULT_FILE_LIFETIME,
443:                 'cookie_lifetime' => 0,
444:             ]);
445: 
446:         } else {
447:             $time = Nette\Utils\DateTime::from($time)->format('U') - time();
448:             return $this->setOptions([
449:                 'gc_maxlifetime' => $time,
450:                 'cookie_lifetime' => $time,
451:             ]);
452:         }
453:     }
454: 
455: 
456:     /**
457:      * Sets the session cookie parameters.
458:      * @param  string  path
459:      * @param  string  domain
460:      * @param  bool    secure
461:      * @return static
462:      */
463:     public function setCookieParameters($path, $domain = NULL, $secure = NULL)
464:     {
465:         return $this->setOptions([
466:             'cookie_path' => $path,
467:             'cookie_domain' => $domain,
468:             'cookie_secure' => $secure,
469:         ]);
470:     }
471: 
472: 
473:     /**
474:      * Returns the session cookie parameters.
475:      * @return array  containing items: lifetime, path, domain, secure, httponly
476:      */
477:     public function getCookieParameters()
478:     {
479:         return session_get_cookie_params();
480:     }
481: 
482: 
483:     /**
484:      * Sets path of the directory used to save session data.
485:      * @return static
486:      */
487:     public function setSavePath($path)
488:     {
489:         return $this->setOptions([
490:             'save_path' => $path,
491:         ]);
492:     }
493: 
494: 
495:     /**
496:      * @deprecated  use setHandler().
497:      * @return static
498:      */
499:     public function setStorage(ISessionStorage $storage)
500:     {
501:         if (self::$started) {
502:             throw new Nette\InvalidStateException('Unable to set storage when session has been started.');
503:         }
504:         session_set_save_handler(
505:             [$storage, 'open'], [$storage, 'close'], [$storage, 'read'],
506:             [$storage, 'write'], [$storage, 'remove'], [$storage, 'clean']
507:         );
508:         return $this;
509:     }
510: 
511: 
512:     /**
513:      * Sets user session handler.
514:      * @return static
515:      */
516:     public function setHandler(\SessionHandlerInterface $handler)
517:     {
518:         if (self::$started) {
519:             throw new Nette\InvalidStateException('Unable to set handler when session has been started.');
520:         }
521:         $this->handler = $handler;
522:         return $this;
523:     }
524: 
525: 
526:     /**
527:      * Sends the session cookies.
528:      * @return void
529:      */
530:     private function sendCookie()
531:     {
532:         $cookie = $this->getCookieParameters();
533:         $this->response->setCookie(
534:             session_name(), session_id(),
535:             $cookie['lifetime'] ? $cookie['lifetime'] + time() : 0,
536:             $cookie['path'], $cookie['domain'], $cookie['secure'], $cookie['httponly']
537:         );
538:     }
539: 
540: }
541: 
Nette 2.4-20170119 API API documentation generated by ApiGen 2.8.0