Source for file Session.php
Documentation is available at Session.php
6: * Copyright (c) 2004, 2009 David Grudl (http://davidgrudl.com)
8: * This source file is subject to the "Nette license" that is bundled
9: * with this package in the file license.txt.
11: * For more information please see http://nettephp.com
13: * @copyright Copyright (c) 2004, 2009 David Grudl
14: * @license http://nettephp.com/license Nette license
15: * @link http://nettephp.com
22: require_once dirname(__FILE__) .
'/../Object.php';
27: * Provides access to session namespaces as well as session settings and management methods.
29: * @author David Grudl
30: * @copyright Copyright (c) 2004, 2009 David Grudl
35: /** Default file lifetime is 3 hours */
38: /** @var callback Validation key generator */
41: /** @var bool is required session ID regeneration? */
42: private $regenerationNeeded;
44: /** @var bool has been session started? */
45: private static $started;
47: /** @var array default configuration */
48: private $options =
array(
50: 'referer_check' =>
'', // must be disabled because PHP implementation is invalid
51: 'use_cookies' =>
1, // must be enabled to prevent Session Hijacking and Fixation
52: 'use_only_cookies' =>
1, // must be enabled to prevent Session Fixation
53: 'use_trans_sid' =>
0, // must be disabled to prevent Session Hijacking and Fixation
56: 'cookie_lifetime' =>
0, // until the browser is closed
57: 'cookie_path' =>
'/', // cookie is available within the entire domain
58: 'cookie_domain' =>
'', // cookie is available on current subdomain only
59: 'cookie_secure' =>
FALSE, // cookie is available on HTTP & HTTPS
60: 'cookie_httponly' =>
TRUE,// must be enabled to prevent Session Fixation
63: 'gc_maxlifetime' =>
self::DEFAULT_FILE_LIFETIME,// 3 hours
64: 'cache_limiter' =>
NULL, // (default "nocache", special value "\0")
65: 'cache_expire' =>
NULL, // (default "180")
66: 'hash_function' =>
NULL, // (default "0", means MD5)
67: 'hash_bits_per_character' =>
NULL, // (default "4")
80: * Starts and initializes session data.
81: * @throws InvalidStateException
86: if (self::$started) {
87: throw new InvalidStateException('Session has already been started.');
89: } elseif (self::$started ===
NULL &&
defined('SID')) {
94: // additional protection against Session Hijacking & Fixation
99: throw new InvalidStateException("Verification key generator '$textual' is not " .
($able ?
'callable.' :
'valid PHP callback.'));
106: $this->configure($this->options);
118: self::$started =
TRUE;
119: if ($this->regenerationNeeded) {
121: $this->regenerationNeeded =
FALSE;
126: data: __NS->namespace->variable = data
127: meta: __NM->namespace->EXP->variable = timestamp
130: // initialize structures
132: if (!isset($_SESSION['__NT']['V'])) { // new session
133: $_SESSION['__NT'] =
array();
134: $_SESSION['__NT']['C'] =
0;
135: $_SESSION['__NT']['V'] =
$verKey;
138: $saved =
& $_SESSION['__NT']['V'];
139: if ($verKey ==
NULL ||
$verKey ===
$saved) { // verified
140: $_SESSION['__NT']['C']++
;
142: } else { // session attack?
144: $_SESSION =
array();
145: $_SESSION['__NT']['C'] =
0;
146: $_SESSION['__NT']['V'] =
$verKey;
150: // browser closing detection
155: $browserClosed =
!isset($_SESSION['__NT']['B']) ||
$_SESSION['__NT']['B'] !==
$browserKey;
156: $_SESSION['__NT']['B'] =
$browserKey;
159: $this->sendCookie();
161: // process meta metadata
162: if (isset($_SESSION['__NM'])) {
165: // expire namespace variables
166: foreach ($_SESSION['__NM'] as $namespace =>
$metadata) {
167: if (isset($metadata['EXP'])) {
168: foreach ($metadata['EXP'] as $variable =>
$value) {
169: if (!is_array($value)) $value =
array($value, !$value); // back compatibility
171: list($time, $whenBrowserIsClosed) =
$value;
172: if (($whenBrowserIsClosed &&
$browserClosed) ||
($time &&
$now >
$time)) {
173: if ($variable ===
'') { // expire whole namespace
174: unset($_SESSION['__NM'][$namespace], $_SESSION['__NS'][$namespace]);
177: unset($_SESSION['__NS'][$namespace][$variable],
178: $_SESSION['__NM'][$namespace]['EXP'][$variable]);
191: * Has been session started?
196: return (bool)
self::$started;
202: * Ends the current session and store session data.
207: if (self::$started) {
208: session_write_close();
209: self::$started =
FALSE;
216: * Destroys all data registered to a session.
221: if (!self::$started) {
222: throw new InvalidStateException('Session is not started.');
227: self::$started =
FALSE;
237: * Does session exists for the current request?
242: return self::$started ||
$this->getHttpRequest()->getCookie(session_name()) !==
NULL;
248: * Regenerates the session ID.
249: * @throws InvalidStateException
254: if (self::$started) {
255: if (headers_sent($file, $line)) {
256: throw new InvalidStateException("Cannot regenerate session ID after HTTP headers have been sent" .
($file ?
" (output started at $file:$line)." :
"."));
261: $this->regenerationNeeded =
TRUE;
268: * Returns the current session ID. Don't make dependencies, can be changed for each request.
279: * Sets the session name to a specified one.
281: * @return Session provides a fluent interface
286: throw new InvalidArgumentException('Session name must be a string and cannot contain dot.');
298: * Gets the session name.
309: * Generates key as protection against Session Hijacking & Fixation.
315: $key[] =
$httpRequest->getHeader('Accept-Charset');
316: $key[] =
$httpRequest->getHeader('Accept-Encoding');
317: $key[] =
$httpRequest->getHeader('Accept-Language');
318: $key[] =
$httpRequest->getHeader('User-Agent');
319: if (strpos($key[3], 'MSIE 8.0')) { // IE 8 AJAX bug
327: /********************* namespaces management ****************d*g**/
332: * Returns specified session namespace.
335: * @return SessionNamespace
336: * @throws InvalidArgumentException
341: throw new InvalidArgumentException('Session namespace must be a non-empty string.');
344: if (!self::$started) {
348: return new $class($_SESSION['__NS'][$namespace], $_SESSION['__NM'][$namespace]);
354: * Checks if a session namespace exist and is not empty.
364: return !empty($_SESSION['__NS'][$namespace]);
370: * Iteration over all namespaces.
371: * @return ArrayIterator
379: if (isset($_SESSION['__NS'])) {
383: return new ArrayIterator;
390: * Cleans and minimizes meta structures.
395: if (!self::$started ||
empty($_SESSION)) {
399: if (isset($_SESSION['__NM']) &&
is_array($_SESSION['__NM'])) {
400: foreach ($_SESSION['__NM'] as $name =>
$foo) {
401: if (empty($_SESSION['__NM'][$name]['EXP'])) {
402: unset($_SESSION['__NM'][$name]['EXP']);
405: if (empty($_SESSION['__NM'][$name])) {
406: unset($_SESSION['__NM'][$name]);
411: if (empty($_SESSION['__NM'])) {
412: unset($_SESSION['__NM']);
415: if (empty($_SESSION['__NS'])) {
416: unset($_SESSION['__NS']);
419: if (empty($_SESSION)) {
420: //$this->destroy(); only when shutting down
426: /********************* configuration ****************d*g**/
431: * Sets session options.
433: * @return Session provides a fluent interface
434: * @throws NotSupportedException
435: * @throws InvalidStateException
439: if (self::$started) {
440: $this->configure($options);
442: $this->options =
$options +
$this->options;
449: * Returns all session options.
454: return $this->options;
460: * Configurates session environment.
464: private function configure(array $config)
466: $special =
array('cache_expire' =>
1, 'cache_limiter' =>
1, 'save_path' =>
1, 'name' =>
1);
468: foreach ($config as $key =>
$value) {
469: if (!strncmp($key, 'session.', 8)) { // back compatibility
473: if ($value ===
NULL) {
476: } elseif (isset($special[$key])) {
477: if (self::$started) {
478: throw new InvalidStateException("Unable to set '$key' when session has been started.");
480: $key =
"session_$key";
484: if (!isset($cookie)) {
490: if (ini_get($key) !=
$value) { // intentionally ==
495: if (self::$started) {
496: throw new InvalidStateException("Unable to set '$key' when session has been started.");
502: if (isset($cookie)) {
504: if (self::$started) {
505: $this->sendCookie();
513: * Sets the amount of time allowed between requests before the session will be terminated.
514: * @param mixed number of seconds, value 0 means "until the browser is closed"
515: * @return Session provides a fluent interface
523: if ($seconds <=
0) {
525: 'gc_maxlifetime' =>
self::DEFAULT_FILE_LIFETIME,
526: 'cookie_lifetime' =>
0,
534: 'gc_maxlifetime' =>
$seconds,
535: 'cookie_lifetime' =>
$seconds,
543: * Sets the session cookie parameters.
544: * @param string path
545: * @param string domain
546: * @param bool secure
547: * @return Session provides a fluent interface
552: 'cookie_path' =>
$path,
553: 'cookie_domain' =>
$domain,
554: 'cookie_secure' =>
$secure
561: * Returns the session cookie parameters.
562: * @return array containing items: lifetime, path, domain, secure, httponly
572: * Sets path of the directory used to save session data.
573: * @return Session provides a fluent interface
578: 'save_path' =>
$path,
585: * Sends the session cookies.
588: private function sendCookie()
592: $this->getHttpResponse()->setCookie('nette-browser', $_SESSION['__NT']['B'], HttpResponse::BROWSER, $cookie['path'], $cookie['domain'], $cookie['secure'], $cookie['httponly']);
597: /********************* backend ****************d*g**/
602: * @return IHttpRequest
612: * @return IHttpResponse