Namespaces

  • 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

  • Context
  • FileUpload
  • Request
  • RequestFactory
  • Response
  • Session
  • SessionSection
  • Url
  • UrlScript
  • UserStorage

Interfaces

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