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

Classes

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

Interfaces

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