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