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
  • PHP

Classes

  • NHttpContext
  • NHttpRequest
  • NHttpRequestFactory
  • NHttpResponse
  • NHttpUploadedFile
  • NSession
  • NSessionSection
  • NUrl
  • NUrlScript
  • NUserStorage

Interfaces

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