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