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
      • Table
    • DI
      • Config
        • Adapters
      • Extensions
    • Forms
      • Controls
      • Rendering
    • Http
    • Iterators
    • Loaders
    • Localization
    • Mail
    • Neon
    • PhpGenerator
    • Reflection
    • Security
    • Tokenizer
    • Utils
  • Tracy
    • Bridges
      • Nette
  • none

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