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
      • Traits
    • 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:         if (!session_id()) {
 79:             $id = $this->request->getCookie(session_name());
 80:             if (is_string($id) && preg_match('#^[0-9a-zA-Z,-]{22,256}\z#i', $id)) {
 81:                 session_id($id);
 82:             } else {
 83:                 unset($_COOKIE[session_name()]);
 84:             }
 85:         }
 86: 
 87:         try {
 88:             // session_start returns false on failure only sometimes
 89:             Nette\Utils\Callback::invokeSafe('session_start', [], function ($message) use (&$e) {
 90:                 $e = new Nette\InvalidStateException($message);
 91:             });
 92:         } catch (\Exception $e) {
 93:         }
 94: 
 95:         if ($e) {
 96:             @session_write_close(); // this is needed
 97:             throw $e;
 98:         }
 99: 
100:         self::$started = true;
101: 
102:         /* structure:
103:             __NF: Data, Meta, Time
104:                 DATA: section->variable = data
105:                 META: section->variable = Timestamp
106:         */
107:         $nf = &$_SESSION['__NF'];
108: 
109:         if (!is_array($nf)) {
110:             $nf = [];
111:         }
112: 
113:         // regenerate empty session
114:         if (empty($nf['Time'])) {
115:             $nf['Time'] = time();
116:             $this->regenerated = true;
117:         }
118: 
119:         // resend cookie
120:         $this->sendCookie();
121: 
122:         // process meta metadata
123:         if (isset($nf['META'])) {
124:             $now = time();
125:             // expire section variables
126:             foreach ($nf['META'] as $section => $metadata) {
127:                 if (is_array($metadata)) {
128:                     foreach ($metadata as $variable => $value) {
129:                         if (!empty($value['T']) && $now > $value['T']) {
130:                             if ($variable === '') { // expire whole section
131:                                 unset($nf['META'][$section], $nf['DATA'][$section]);
132:                                 continue 2;
133:                             }
134:                             unset($nf['META'][$section][$variable], $nf['DATA'][$section][$variable]);
135:                         }
136:                     }
137:                 }
138:             }
139:         }
140: 
141:         if ($this->regenerated) {
142:             $this->regenerated = false;
143:             $this->regenerateId();
144:         }
145: 
146:         register_shutdown_function([$this, 'clean']);
147:     }
148: 
149: 
150:     /**
151:      * Has been session started?
152:      * @return bool
153:      */
154:     public function isStarted()
155:     {
156:         return (bool) self::$started;
157:     }
158: 
159: 
160:     /**
161:      * Ends the current session and store session data.
162:      * @return void
163:      */
164:     public function close()
165:     {
166:         if (self::$started) {
167:             $this->clean();
168:             session_write_close();
169:             self::$started = false;
170:         }
171:     }
172: 
173: 
174:     /**
175:      * Destroys all data registered to a session.
176:      * @return void
177:      */
178:     public function destroy()
179:     {
180:         if (!self::$started) {
181:             throw new Nette\InvalidStateException('Session is not started.');
182:         }
183: 
184:         session_destroy();
185:         $_SESSION = null;
186:         self::$started = false;
187:         if (!$this->response->isSent()) {
188:             $params = session_get_cookie_params();
189:             $this->response->deleteCookie(session_name(), $params['path'], $params['domain'], $params['secure']);
190:         }
191:     }
192: 
193: 
194:     /**
195:      * Does session exists for the current request?
196:      * @return bool
197:      */
198:     public function exists()
199:     {
200:         return self::$started || $this->request->getCookie($this->getName()) !== null;
201:     }
202: 
203: 
204:     /**
205:      * Regenerates the session ID.
206:      * @throws Nette\InvalidStateException
207:      * @return void
208:      */
209:     public function regenerateId()
210:     {
211:         if (self::$started && !$this->regenerated) {
212:             if (headers_sent($file, $line)) {
213:                 throw new Nette\InvalidStateException('Cannot regenerate session ID after HTTP headers have been sent' . ($file ? " (output started at $file:$line)." : '.'));
214:             }
215:             if (session_status() === PHP_SESSION_ACTIVE) {
216:                 session_regenerate_id(true);
217:                 session_write_close();
218:             }
219:             $backup = $_SESSION;
220:             session_start();
221:             $_SESSION = $backup;
222:         }
223:         $this->regenerated = true;
224:     }
225: 
226: 
227:     /**
228:      * Returns the current session ID. Don't make dependencies, can be changed for each request.
229:      * @return string
230:      */
231:     public function getId()
232:     {
233:         return session_id();
234:     }
235: 
236: 
237:     /**
238:      * Sets the session name to a specified one.
239:      * @param  string
240:      * @return static
241:      */
242:     public function setName($name)
243:     {
244:         if (!is_string($name) || !preg_match('#[^0-9.][^.]*\z#A', $name)) {
245:             throw new Nette\InvalidArgumentException('Session name must be a string and cannot contain dot.');
246:         }
247: 
248:         session_name($name);
249:         return $this->setOptions([
250:             'name' => $name,
251:         ]);
252:     }
253: 
254: 
255:     /**
256:      * Gets the session name.
257:      * @return string
258:      */
259:     public function getName()
260:     {
261:         return isset($this->options['name']) ? $this->options['name'] : session_name();
262:     }
263: 
264: 
265:     /********************* sections management ****************d*g**/
266: 
267: 
268:     /**
269:      * Returns specified session section.
270:      * @param  string
271:      * @param  string
272:      * @return SessionSection
273:      * @throws Nette\InvalidArgumentException
274:      */
275:     public function getSection($section, $class = SessionSection::class)
276:     {
277:         return new $class($this, $section);
278:     }
279: 
280: 
281:     /**
282:      * Checks if a session section exist and is not empty.
283:      * @param  string
284:      * @return bool
285:      */
286:     public function hasSection($section)
287:     {
288:         if ($this->exists() && !self::$started) {
289:             $this->start();
290:         }
291: 
292:         return !empty($_SESSION['__NF']['DATA'][$section]);
293:     }
294: 
295: 
296:     /**
297:      * Iteration over all sections.
298:      * @return \Iterator
299:      */
300:     public function getIterator()
301:     {
302:         if ($this->exists() && !self::$started) {
303:             $this->start();
304:         }
305: 
306:         if (isset($_SESSION['__NF']['DATA'])) {
307:             return new \ArrayIterator(array_keys($_SESSION['__NF']['DATA']));
308: 
309:         } else {
310:             return new \ArrayIterator;
311:         }
312:     }
313: 
314: 
315:     /**
316:      * Cleans and minimizes meta structures. This method is called automatically on shutdown, do not call it directly.
317:      * @internal
318:      * @return void
319:      */
320:     public function clean()
321:     {
322:         if (!self::$started || empty($_SESSION)) {
323:             return;
324:         }
325: 
326:         $nf = &$_SESSION['__NF'];
327:         if (isset($nf['META']) && is_array($nf['META'])) {
328:             foreach ($nf['META'] as $name => $foo) {
329:                 if (empty($nf['META'][$name])) {
330:                     unset($nf['META'][$name]);
331:                 }
332:             }
333:         }
334: 
335:         if (empty($nf['META'])) {
336:             unset($nf['META']);
337:         }
338: 
339:         if (empty($nf['DATA'])) {
340:             unset($nf['DATA']);
341:         }
342:     }
343: 
344: 
345:     /********************* configuration ****************d*g**/
346: 
347: 
348:     /**
349:      * Sets session options.
350:      * @param  array
351:      * @return static
352:      * @throws Nette\NotSupportedException
353:      * @throws Nette\InvalidStateException
354:      */
355:     public function setOptions(array $options)
356:     {
357:         $normalized = [];
358:         foreach ($options as $key => $value) {
359:             if (!strncmp($key, 'session.', 8)) { // back compatibility
360:                 $key = substr($key, 8);
361:             }
362:             $key = strtolower(preg_replace('#(.)(?=[A-Z])#', '$1_', $key)); // camelCase -> snake_case
363:             $normalized[$key] = $value;
364:         }
365:         if (self::$started) {
366:             $this->configure($normalized);
367:         }
368:         $this->options = $normalized + $this->options;
369:         if (!empty($normalized['auto_start'])) {
370:             $this->start();
371:         }
372:         return $this;
373:     }
374: 
375: 
376:     /**
377:      * Returns all session options.
378:      * @return array
379:      */
380:     public function getOptions()
381:     {
382:         return $this->options;
383:     }
384: 
385: 
386:     /**
387:      * Configures session environment.
388:      * @param  array
389:      * @return void
390:      */
391:     private function configure(array $config)
392:     {
393:         $special = ['cache_expire' => 1, 'cache_limiter' => 1, 'save_path' => 1, 'name' => 1];
394:         $cookie = $origCookie = session_get_cookie_params();
395: 
396:         foreach ($config as $key => $value) {
397:             if ($value === null || ini_get("session.$key") == $value) { // intentionally ==
398:                 continue;
399: 
400:             } elseif (strncmp($key, 'cookie_', 7) === 0) {
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 ($cookie !== $origCookie) {
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-20180703 API API documentation generated by ApiGen 2.8.0