Packages

  • Nette
    • Application
    • Caching
    • Collections
    • Config
    • Forms
    • IO
    • Loaders
    • Mail
    • Reflection
    • Security
    • Templates
    • Web
  • None
  • PHP

Classes

  • NFtp
  • NHtml
  • NHttpContext
  • NHttpRequest
  • NHttpResponse
  • NHttpUploadedFile
  • NSession
  • NSessionNamespace
  • NUri
  • NUriScript
  • NUser

Interfaces

  • IHttpRequest
  • IHttpResponse
  • IUser

Exceptions

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