Packages

  • Nette
    • Application
      • Diagnostics
      • Responses
      • Routers
      • UI
    • Caching
      • Storages
    • ComponentModel
    • Config
      • Adapters
      • Extensions
    • Database
      • Diagnostics
      • Drivers
      • Reflection
      • Table
    • DI
      • Diagnostics
    • Diagnostics
    • Forms
      • Controls
      • Rendering
    • Http
    • Iterators
    • Latte
      • Macros
    • Loaders
    • Localization
    • Mail
    • Reflection
    • Security
      • Diagnostics
    • Templating
    • Utils
      • PhpGenerator
  • NetteModule
  • none

Classes

Interfaces

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