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