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 static $defaultConfig =
array(
50: 'session.referer_check' =>
'', // must be disabled because PHP implementation is invalid
51: 'session.use_cookies' =>
1, // must be enabled to prevent Session Hijacking and Fixation
52: 'session.use_only_cookies' =>
1, // must be enabled to prevent Session Fixation
53: 'session.use_trans_sid' =>
0, // must be disabled to prevent Session Hijacking and Fixation
56: 'session.cookie_lifetime' =>
0, // until the browser is closed
57: 'session.cookie_path' =>
'/', // cookie is available within the entire domain
58: 'session.cookie_domain' =>
'', // cookie is available on current subdomain only
59: 'session.cookie_secure' =>
FALSE, // cookie is available on HTTP & HTTPS
60: 'session.cookie_httponly' =>
TRUE,// must be enabled to prevent Session Fixation
64: 'session.cache_limiter' =>
NULL, // (default "nocache", special value "\0")
65: 'session.cache_expire' =>
NULL, // (default "180")
66: 'session.hash_function' =>
NULL, // (default "0", means MD5)
67: 'session.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.'));
114: self::$started =
TRUE;
115: if ($this->regenerationNeeded) {
117: $this->regenerationNeeded =
FALSE;
122: data: __NS->namespace->variable = data
123: meta: __NM->namespace->EXP->variable = timestamp
126: // initialize structures
128: if (!isset($_SESSION['__NT']['V'])) { // new session
129: $_SESSION['__NT'] =
array();
130: $_SESSION['__NT']['C'] =
0;
131: $_SESSION['__NT']['V'] =
$verKey;
134: $saved =
& $_SESSION['__NT']['V'];
135: if ($saved ===
$verKey) { // verified
136: $_SESSION['__NT']['C']++
;
138: } else { // session attack?
140: $_SESSION =
array();
141: $_SESSION['__NT']['C'] =
0;
142: $_SESSION['__NT']['V'] =
$verKey;
146: // browser closing detection
151: $browserClosed =
!isset($_SESSION['__NT']['B']) ||
$_SESSION['__NT']['B'] !==
$browserKey;
152: $_SESSION['__NT']['B'] =
$browserKey;
155: $this->sendCookie();
157: // process meta metadata
158: if (isset($_SESSION['__NM'])) {
161: // expire namespace variables
162: foreach ($_SESSION['__NM'] as $namespace =>
$metadata) {
163: if (isset($metadata['EXP'])) {
164: foreach ($metadata['EXP'] as $variable =>
$value) {
165: if (!is_array($value)) $value =
array($value, !$value); // back compatibility
167: list($time, $whenBrowserIsClosed) =
$value;
168: if (($whenBrowserIsClosed &&
$browserClosed) ||
($time &&
$now >
$time)) {
169: if ($variable ===
'') { // expire whole namespace
170: unset($_SESSION['__NM'][$namespace], $_SESSION['__NS'][$namespace]);
173: unset($_SESSION['__NS'][$namespace][$variable],
174: $_SESSION['__NM'][$namespace]['EXP'][$variable]);
187: * Has been session started?
192: return (bool)
self::$started;
198: * Ends the current session and store session data.
203: if (self::$started) {
204: session_write_close();
205: self::$started =
FALSE;
212: * Destroys all data registered to a session.
217: if (!self::$started) {
218: throw new InvalidStateException('Session is not started.');
223: self::$started =
FALSE;
233: * Does session exists for the current request?
238: return self::$started ||
$this->getHttpRequest()->getCookie(session_name()) !==
NULL;
244: * Regenerates the session ID.
245: * @throws InvalidStateException
250: if (self::$started) {
251: if (headers_sent($file, $line)) {
252: throw new InvalidStateException("Cannot regenerate session ID after HTTP headers have been sent" .
($file ?
" (output started at $file:$line)." :
"."));
258: $this->regenerationNeeded =
TRUE;
265: * Returns the current session ID. Don't make dependencies, can be changed for each request.
276: * Sets the session name to a specified one.
283: throw new InvalidArgumentException('Session name must be a string and cannot contain dot.');
287: 'session.name' =>
$name,
294: * Gets the session name.
305: * Generates key as protection against Session Hijacking & Fixation.
310: $list =
array('Accept-Charset', 'Accept-Encoding', 'Accept-Language', 'User-Agent');
313: foreach ($list as $header) {
314: $key[] =
$httpRequest->getHeader($header);
321: /********************* namespaces management ****************d*g**/
326: * Returns specified session namespace.
329: * @return SessionNamespace
330: * @throws InvalidArgumentException
335: throw new InvalidArgumentException('Session namespace must be a non-empty string.');
338: if (!self::$started) {
342: return new $class($_SESSION['__NS'][$namespace], $_SESSION['__NM'][$namespace]);
348: * Checks if a session namespace exist and is not empty.
358: return !empty($_SESSION['__NS'][$namespace]);
364: * Iteration over all namespaces.
365: * @return ArrayIterator
373: if (isset($_SESSION['__NS'])) {
377: return new ArrayIterator;
384: * Cleans and minimizes meta structures.
389: if (!self::$started ||
empty($_SESSION)) {
393: if (isset($_SESSION['__NM']) &&
is_array($_SESSION['__NM'])) {
394: foreach ($_SESSION['__NM'] as $name =>
$foo) {
395: if (empty($_SESSION['__NM'][$name]['EXP'])) {
396: unset($_SESSION['__NM'][$name]['EXP']);
399: if (empty($_SESSION['__NM'][$name])) {
400: unset($_SESSION['__NM'][$name]);
405: if (empty($_SESSION['__NM'])) {
406: unset($_SESSION['__NM']);
409: if (empty($_SESSION['__NS'])) {
410: unset($_SESSION['__NS']);
413: if (empty($_SESSION)) {
414: //$this->destroy(); only when shutting down
420: /********************* configuration ****************d*g**/
425: * Configurates session environment.
427: * @param bool throw exception?
429: * @throws NotSupportedException
430: * @throws InvalidStateException
432: public function configure(array $config, $throwException =
TRUE)
434: $special =
array('session.cache_expire' =>
1, 'session.cache_limiter' =>
1,
435: 'session.save_path' =>
1, 'session.name' =>
1);
437: foreach ($config as $key =>
$value) {
438: unset(self::$defaultConfig[$key]); // prevents overwriting
440: if ($value ===
NULL) {
443: } elseif (isset($special[$key])) {
444: if (self::$started) {
445: throw new InvalidStateException('Session has already been started.');
450: } elseif (strncmp($key, 'session.cookie_', 15) ===
0) {
451: if (!isset($cookie)) {
457: if ($throwException &&
ini_get($key) !=
$value) { // intentionally ==
462: if (self::$started) {
463: throw new InvalidStateException('Session has already been started.');
469: if (isset($cookie)) {
470: session_set_cookie_params($cookie['lifetime'], $cookie['path'], $cookie['domain'], $cookie['secure'], $cookie['httponly']);
471: if (self::$started) {
472: $this->sendCookie();
480: * Sets the amount of time allowed between requests before the session will be terminated.
481: * @param mixed number of seconds, value 0 means "until the browser is closed"
490: if ($seconds <=
0) {
492: 'session.gc_maxlifetime' =>
self::DEFAULT_FILE_LIFETIME,
493: 'session.cookie_lifetime' =>
0,
501: 'session.gc_maxlifetime' =>
$seconds,
502: 'session.cookie_lifetime' =>
$seconds,
510: * Sets the session cookie parameters.
511: * @param string path
512: * @param string domain
513: * @param bool secure
519: 'session.cookie_path' =>
$path,
520: 'session.cookie_domain' =>
$domain,
521: 'session.cookie_secure' =>
$secure
528: * Returns the session cookie parameters.
529: * @return array containing items: lifetime, path, domain, secure, httponly
539: * Sets path of the directory used to save session data.
545: 'session.save_path' =>
$path,
552: * Sends the session cookies.
555: private function sendCookie()
559: $this->getHttpResponse()->setCookie('nette-browser', $_SESSION['__NT']['B'], HttpResponse::BROWSER, $cookie['path'], $cookie['domain'], $cookie['secure'], $cookie['httponly']);
564: /********************* backend ****************d*g**/
569: * @return IHttpRequest
579: * @return IHttpResponse