Namespaces

  • Latte
    • Loaders
    • Macros
    • Runtime
  • Nette
    • Application
      • Responses
      • Routers
      • UI
    • Bridges
      • ApplicationDI
      • ApplicationLatte
      • ApplicationTracy
      • CacheDI
      • CacheLatte
      • DatabaseDI
      • DatabaseTracy
      • DITracy
      • FormsDI
      • FormsLatte
      • Framework
      • HttpDI
      • HttpTracy
      • MailDI
      • ReflectionDI
      • SecurityDI
      • SecurityTracy
    • Caching
      • Storages
    • ComponentModel
    • Database
      • Conventions
      • Drivers
      • Reflection
      • Table
    • DI
      • Config
        • Adapters
      • Extensions
    • Diagnostics
    • Forms
      • Controls
      • Rendering
    • Http
    • Iterators
    • Latte
    • Loaders
    • Localization
    • Mail
    • Neon
    • PhpGenerator
    • Reflection
    • Security
    • Templating
    • Utils
  • NetteModule
  • none
  • Tracy
    • Bridges
      • Nette

Classes

  • Context
  • FileUpload
  • Helpers
  • 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:  * 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:  * @property-read bool $started
 17:  * @property-read string $id
 18:  * @property   string $name
 19:  * @property-read \ArrayIterator $iterator
 20:  * @property   array $options
 21:  * @property-write $savePath
 22:  * @property-write ISessionStorage $storage
 23:  */
 24: class Session extends Nette\Object
 25: {
 26:     /** Default file lifetime is 3 hours */
 27:     const DEFAULT_FILE_LIFETIME = 10800;
 28: 
 29:     /** @var bool  has been session ID regenerated? */
 30:     private $regenerated;
 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:     /** @var IRequest */
 59:     private $request;
 60: 
 61:     /** @var IResponse */
 62:     private $response;
 63: 
 64:     /** @var \SessionHandlerInterface */
 65:     private $handler;
 66: 
 67: 
 68:     public function __construct(IRequest $request, IResponse $response)
 69:     {
 70:         $this->request = $request;
 71:         $this->response = $response;
 72:     }
 73: 
 74: 
 75:     /**
 76:      * Starts and initializes session data.
 77:      * @throws Nette\InvalidStateException
 78:      * @return void
 79:      */
 80:     public function start()
 81:     {
 82:         if (self::$started) {
 83:             return;
 84:         }
 85: 
 86:         $this->configure($this->options);
 87: 
 88:         $id = $this->request->getCookie(session_name());
 89:         if (is_string($id) && preg_match('#^[0-9a-zA-Z,-]{22,128}\z#i', $id)) {
 90:             session_id($id);
 91:         } else {
 92:             unset($_COOKIE[session_name()]);
 93:         }
 94: 
 95:         try {
 96:             // session_start returns FALSE on failure only sometimes
 97:             Nette\Utils\Callback::invokeSafe('session_start', array(), function ($message) use (& $e) {
 98:                 $e = new Nette\InvalidStateException($message);
 99:             });
100:         } catch (\Exception $e) {
101:         }
102: 
103:         Helpers::removeDuplicateCookies();
104:         if ($e) {
105:             @session_write_close(); // this is needed
106:             throw $e;
107:         }
108: 
109:         self::$started = TRUE;
110: 
111:         /* structure:
112:             __NF: BrowserKey, Data, Meta, Time
113:                 DATA: section->variable = data
114:                 META: section->variable = Timestamp, Browser
115:         */
116:         $nf = & $_SESSION['__NF'];
117: 
118:         // regenerate empty session
119:         if (empty($nf['Time'])) {
120:             $nf['Time'] = time();
121:             $this->regenerated = TRUE;
122:         }
123: 
124:         // browser closing detection
125:         $browserKey = $this->request->getCookie('nette-browser');
126:         if (!is_string($browserKey) || !preg_match('#^[0-9a-z]{10}\z#', $browserKey)) {
127:             $browserKey = Nette\Utils\Random::generate();
128:         }
129:         $browserClosed = !isset($nf['B']) || $nf['B'] !== $browserKey;
130:         $nf['B'] = $browserKey;
131: 
132:         // resend cookie
133:         $this->sendCookie();
134: 
135:         // process meta metadata
136:         if (isset($nf['META'])) {
137:             $now = time();
138:             // expire section variables
139:             foreach ($nf['META'] as $section => $metadata) {
140:                 if (is_array($metadata)) {
141:                     foreach ($metadata as $variable => $value) {
142:                         if ((!empty($value['B']) && $browserClosed) || (!empty($value['T']) && $now > $value['T'])) { // whenBrowserIsClosed || Time
143:                             if ($variable === '') { // expire whole section
144:                                 unset($nf['META'][$section], $nf['DATA'][$section]);
145:                                 continue 2;
146:                             }
147:                             unset($nf['META'][$section][$variable], $nf['DATA'][$section][$variable]);
148:                         }
149:                     }
150:                 }
151:             }
152:         }
153: 
154:         if ($this->regenerated) {
155:             $this->regenerated = FALSE;
156:             $this->regenerateId();
157:         }
158: 
159:         register_shutdown_function(array($this, 'clean'));
160:     }
161: 
162: 
163:     /**
164:      * Has been session started?
165:      * @return bool
166:      */
167:     public function isStarted()
168:     {
169:         return (bool) self::$started;
170:     }
171: 
172: 
173:     /**
174:      * Ends the current session and store session data.
175:      * @return void
176:      */
177:     public function close()
178:     {
179:         if (self::$started) {
180:             $this->clean();
181:             session_write_close();
182:             self::$started = FALSE;
183:         }
184:     }
185: 
186: 
187:     /**
188:      * Destroys all data registered to a session.
189:      * @return void
190:      */
191:     public function destroy()
192:     {
193:         if (!self::$started) {
194:             throw new Nette\InvalidStateException('Session is not started.');
195:         }
196: 
197:         session_destroy();
198:         $_SESSION = NULL;
199:         self::$started = FALSE;
200:         if (!$this->response->isSent()) {
201:             $params = session_get_cookie_params();
202:             $this->response->deleteCookie(session_name(), $params['path'], $params['domain'], $params['secure']);
203:         }
204:     }
205: 
206: 
207:     /**
208:      * Does session exists for the current request?
209:      * @return bool
210:      */
211:     public function exists()
212:     {
213:         return self::$started || $this->request->getCookie($this->getName()) !== NULL;
214:     }
215: 
216: 
217:     /**
218:      * Regenerates the session ID.
219:      * @throws Nette\InvalidStateException
220:      * @return void
221:      */
222:     public function regenerateId()
223:     {
224:         if (self::$started && !$this->regenerated) {
225:             if (headers_sent($file, $line)) {
226:                 throw new Nette\InvalidStateException('Cannot regenerate session ID after HTTP headers have been sent' . ($file ? " (output started at $file:$line)." : '.'));
227:             }
228:             if (session_id() !== '') {
229:                 session_regenerate_id(TRUE);
230:             }
231:             session_write_close();
232:             $backup = $_SESSION;
233:             session_start();
234:             $_SESSION = $backup;
235:             Helpers::removeDuplicateCookies();
236:         }
237:         $this->regenerated = TRUE;
238:     }
239: 
240: 
241:     /**
242:      * Returns the current session ID. Don't make dependencies, can be changed for each request.
243:      * @return string
244:      */
245:     public function getId()
246:     {
247:         return session_id();
248:     }
249: 
250: 
251:     /**
252:      * Sets the session name to a specified one.
253:      * @param  string
254:      * @return self
255:      */
256:     public function setName($name)
257:     {
258:         if (!is_string($name) || !preg_match('#[^0-9.][^.]*\z#A', $name)) {
259:             throw new Nette\InvalidArgumentException('Session name must be a string and cannot contain dot.');
260:         }
261: 
262:         session_name($name);
263:         return $this->setOptions(array(
264:             'name' => $name,
265:         ));
266:     }
267: 
268: 
269:     /**
270:      * Gets the session name.
271:      * @return string
272:      */
273:     public function getName()
274:     {
275:         return isset($this->options['name']) ? $this->options['name'] : session_name();
276:     }
277: 
278: 
279:     /********************* sections management ****************d*g**/
280: 
281: 
282:     /**
283:      * Returns specified session section.
284:      * @param  string
285:      * @param  string
286:      * @return SessionSection
287:      * @throws Nette\InvalidArgumentException
288:      */
289:     public function getSection($section, $class = 'Nette\Http\SessionSection')
290:     {
291:         return new $class($this, $section);
292:     }
293: 
294: 
295:     /**
296:      * Checks if a session section exist and is not empty.
297:      * @param  string
298:      * @return bool
299:      */
300:     public function hasSection($section)
301:     {
302:         if ($this->exists() && !self::$started) {
303:             $this->start();
304:         }
305: 
306:         return !empty($_SESSION['__NF']['DATA'][$section]);
307:     }
308: 
309: 
310:     /**
311:      * Iteration over all sections.
312:      * @return \ArrayIterator
313:      */
314:     public function getIterator()
315:     {
316:         if ($this->exists() && !self::$started) {
317:             $this->start();
318:         }
319: 
320:         if (isset($_SESSION['__NF']['DATA'])) {
321:             return new \ArrayIterator(array_keys($_SESSION['__NF']['DATA']));
322: 
323:         } else {
324:             return new \ArrayIterator;
325:         }
326:     }
327: 
328: 
329:     /**
330:      * Cleans and minimizes meta structures. This method is called automatically on shutdown, do not call it directly.
331:      * @internal
332:      * @return void
333:      */
334:     public function clean()
335:     {
336:         if (!self::$started || empty($_SESSION)) {
337:             return;
338:         }
339: 
340:         $nf = & $_SESSION['__NF'];
341:         if (isset($nf['META']) && is_array($nf['META'])) {
342:             foreach ($nf['META'] as $name => $foo) {
343:                 if (empty($nf['META'][$name])) {
344:                     unset($nf['META'][$name]);
345:                 }
346:             }
347:         }
348: 
349:         if (empty($nf['META'])) {
350:             unset($nf['META']);
351:         }
352: 
353:         if (empty($nf['DATA'])) {
354:             unset($nf['DATA']);
355:         }
356:     }
357: 
358: 
359:     /********************* configuration ****************d*g**/
360: 
361: 
362:     /**
363:      * Sets session options.
364:      * @param  array
365:      * @return self
366:      * @throws Nette\NotSupportedException
367:      * @throws Nette\InvalidStateException
368:      */
369:     public function setOptions(array $options)
370:     {
371:         if (self::$started) {
372:             $this->configure($options);
373:         }
374:         $this->options = $options + $this->options;
375:         if (!empty($options['auto_start'])) {
376:             $this->start();
377:         }
378:         return $this;
379:     }
380: 
381: 
382:     /**
383:      * Returns all session options.
384:      * @return array
385:      */
386:     public function getOptions()
387:     {
388:         return $this->options;
389:     }
390: 
391: 
392:     /**
393:      * Configures session environment.
394:      * @param  array
395:      * @return void
396:      */
397:     private function configure(array $config)
398:     {
399:         $special = array('cache_expire' => 1, 'cache_limiter' => 1, 'save_path' => 1, 'name' => 1);
400: 
401:         foreach ($config as $key => $value) {
402:             if (!strncmp($key, 'session.', 8)) { // back compatibility
403:                 $key = substr($key, 8);
404:             }
405:             $key = strtolower(preg_replace('#(.)(?=[A-Z])#', '$1_', $key));
406: 
407:             if ($value === NULL || ini_get("session.$key") == $value) { // intentionally ==
408:                 continue;
409: 
410:             } elseif (strncmp($key, 'cookie_', 7) === 0) {
411:                 if (!isset($cookie)) {
412:                     $cookie = session_get_cookie_params();
413:                 }
414:                 $cookie[substr($key, 7)] = $value;
415: 
416:             } else {
417:                 if (defined('SID')) {
418:                     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().'));
419:                 }
420:                 if (isset($special[$key])) {
421:                     $key = "session_$key";
422:                     $key($value);
423: 
424:                 } elseif (function_exists('ini_set')) {
425:                     ini_set("session.$key", $value);
426: 
427:                 } elseif (ini_get("session.$key") != $value) { // intentionally ==
428:                     throw new Nette\NotSupportedException("Unable to set 'session.$key' to '$value' because function ini_set() is disabled.");
429:                 }
430:             }
431:         }
432: 
433:         if (isset($cookie)) {
434:             session_set_cookie_params(
435:                 $cookie['lifetime'], $cookie['path'], $cookie['domain'],
436:                 $cookie['secure'], $cookie['httponly']
437:             );
438:             if (self::$started) {
439:                 $this->sendCookie();
440:             }
441:         }
442: 
443:         if ($this->handler) {
444:             session_set_save_handler($this->handler);
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 self
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 = Nette\Utils\DateTime::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:      * Sets the session cookie parameters.
474:      * @param  string  path
475:      * @param  string  domain
476:      * @param  bool    secure
477:      * @return self
478:      */
479:     public function setCookieParameters($path, $domain = NULL, $secure = NULL)
480:     {
481:         return $this->setOptions(array(
482:             'cookie_path' => $path,
483:             'cookie_domain' => $domain,
484:             'cookie_secure' => $secure,
485:         ));
486:     }
487: 
488: 
489:     /**
490:      * Returns the session cookie parameters.
491:      * @return array  containing items: lifetime, path, domain, secure, httponly
492:      */
493:     public function getCookieParameters()
494:     {
495:         return session_get_cookie_params();
496:     }
497: 
498: 
499:     /**
500:      * Sets path of the directory used to save session data.
501:      * @return self
502:      */
503:     public function setSavePath($path)
504:     {
505:         return $this->setOptions(array(
506:             'save_path' => $path,
507:         ));
508:     }
509: 
510: 
511:     /**
512:      * Sets user session storage for PHP < 5.4. For PHP >= 5.4, use setHandler().
513:      * @return self
514:      */
515:     public function setStorage(ISessionStorage $storage)
516:     {
517:         if (self::$started) {
518:             throw new Nette\InvalidStateException('Unable to set storage when session has been started.');
519:         }
520:         session_set_save_handler(
521:             array($storage, 'open'), array($storage, 'close'), array($storage, 'read'),
522:             array($storage, 'write'), array($storage, 'remove'), array($storage, 'clean')
523:         );
524:         return $this;
525:     }
526: 
527: 
528:     /**
529:      * Sets user session handler.
530:      * @return self
531:      */
532:     public function setHandler(\SessionHandlerInterface $handler)
533:     {
534:         if (self::$started) {
535:             throw new Nette\InvalidStateException('Unable to set handler when session has been started.');
536:         }
537:         $this->handler = $handler;
538:         return $this;
539:     }
540: 
541: 
542:     /**
543:      * Sends the session cookies.
544:      * @return void
545:      */
546:     private function sendCookie()
547:     {
548:         if (!headers_sent() && ob_get_level() && ob_get_length()) {
549:             trigger_error('Possible problem: you are starting session while already having some data in output buffer. This may not work if the outputted data grows. Try starting the session earlier.', E_USER_NOTICE);
550:         }
551: 
552:         $cookie = $this->getCookieParameters();
553:         $this->response->setCookie(
554:             session_name(), session_id(),
555:             $cookie['lifetime'] ? $cookie['lifetime'] + time() : 0,
556:             $cookie['path'], $cookie['domain'], $cookie['secure'], $cookie['httponly']
557:         );
558:         $this->response->setCookie(
559:             'nette-browser', $_SESSION['__NF']['B'],
560:             Response::BROWSER, $cookie['path'], $cookie['domain']
561:         );
562:     }
563: 
564: }
565: 
Nette 2.3.4 API API documentation generated by ApiGen 2.8.0