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
  • Other releases
  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:         // process meta metadata
120:         if (isset($nf['META'])) {
121:             $now = time();
122:             // expire section variables
123:             foreach ($nf['META'] as $section => $metadata) {
124:                 if (is_array($metadata)) {
125:                     foreach ($metadata as $variable => $value) {
126:                         if (!empty($value['T']) && $now > $value['T']) {
127:                             if ($variable === '') { // expire whole section
128:                                 unset($nf['META'][$section], $nf['DATA'][$section]);
129:                                 continue 2;
130:                             }
131:                             unset($nf['META'][$section][$variable], $nf['DATA'][$section][$variable]);
132:                         }
133:                     }
134:                 }
135:             }
136:         }
137: 
138:         if ($this->regenerated) {
139:             $this->regenerated = false;
140:             $this->regenerateId();
141:         }
142: 
143:         register_shutdown_function([$this, 'clean']);
144:     }
145: 
146: 
147:     /**
148:      * Has been session started?
149:      * @return bool
150:      */
151:     public function isStarted()
152:     {
153:         return (bool) self::$started;
154:     }
155: 
156: 
157:     /**
158:      * Ends the current session and store session data.
159:      * @return void
160:      */
161:     public function close()
162:     {
163:         if (self::$started) {
164:             $this->clean();
165:             session_write_close();
166:             self::$started = false;
167:         }
168:     }
169: 
170: 
171:     /**
172:      * Destroys all data registered to a session.
173:      * @return void
174:      */
175:     public function destroy()
176:     {
177:         if (!self::$started) {
178:             throw new Nette\InvalidStateException('Session is not started.');
179:         }
180: 
181:         session_destroy();
182:         $_SESSION = null;
183:         self::$started = false;
184:         if (!$this->response->isSent()) {
185:             $params = session_get_cookie_params();
186:             $this->response->deleteCookie(session_name(), $params['path'], $params['domain'], $params['secure']);
187:         }
188:     }
189: 
190: 
191:     /**
192:      * Does session exists for the current request?
193:      * @return bool
194:      */
195:     public function exists()
196:     {
197:         return self::$started || $this->request->getCookie($this->getName()) !== null;
198:     }
199: 
200: 
201:     /**
202:      * Regenerates the session ID.
203:      * @throws Nette\InvalidStateException
204:      * @return void
205:      */
206:     public function regenerateId()
207:     {
208:         if (self::$started && !$this->regenerated) {
209:             if (headers_sent($file, $line)) {
210:                 throw new Nette\InvalidStateException('Cannot regenerate session ID after HTTP headers have been sent' . ($file ? " (output started at $file:$line)." : '.'));
211:             }
212:             if (session_status() === PHP_SESSION_ACTIVE) {
213:                 session_regenerate_id(true);
214:                 session_write_close();
215:             }
216:             $backup = $_SESSION;
217:             session_start();
218:             $_SESSION = $backup;
219:         }
220:         $this->regenerated = true;
221:     }
222: 
223: 
224:     /**
225:      * Returns the current session ID. Don't make dependencies, can be changed for each request.
226:      * @return string
227:      */
228:     public function getId()
229:     {
230:         return session_id();
231:     }
232: 
233: 
234:     /**
235:      * Sets the session name to a specified one.
236:      * @param  string
237:      * @return static
238:      */
239:     public function setName($name)
240:     {
241:         if (!is_string($name) || !preg_match('#[^0-9.][^.]*\z#A', $name)) {
242:             throw new Nette\InvalidArgumentException('Session name must be a string and cannot contain dot.');
243:         }
244: 
245:         session_name($name);
246:         return $this->setOptions([
247:             'name' => $name,
248:         ]);
249:     }
250: 
251: 
252:     /**
253:      * Gets the session name.
254:      * @return string
255:      */
256:     public function getName()
257:     {
258:         return isset($this->options['name']) ? $this->options['name'] : session_name();
259:     }
260: 
261: 
262:     /********************* sections management ****************d*g**/
263: 
264: 
265:     /**
266:      * Returns specified session section.
267:      * @param  string
268:      * @param  string
269:      * @return SessionSection
270:      * @throws Nette\InvalidArgumentException
271:      */
272:     public function getSection($section, $class = SessionSection::class)
273:     {
274:         return new $class($this, $section);
275:     }
276: 
277: 
278:     /**
279:      * Checks if a session section exist and is not empty.
280:      * @param  string
281:      * @return bool
282:      */
283:     public function hasSection($section)
284:     {
285:         if ($this->exists() && !self::$started) {
286:             $this->start();
287:         }
288: 
289:         return !empty($_SESSION['__NF']['DATA'][$section]);
290:     }
291: 
292: 
293:     /**
294:      * Iteration over all sections.
295:      * @return \Iterator
296:      */
297:     public function getIterator()
298:     {
299:         if ($this->exists() && !self::$started) {
300:             $this->start();
301:         }
302: 
303:         if (isset($_SESSION['__NF']['DATA'])) {
304:             return new \ArrayIterator(array_keys($_SESSION['__NF']['DATA']));
305: 
306:         } else {
307:             return new \ArrayIterator;
308:         }
309:     }
310: 
311: 
312:     /**
313:      * Cleans and minimizes meta structures. This method is called automatically on shutdown, do not call it directly.
314:      * @internal
315:      * @return void
316:      */
317:     public function clean()
318:     {
319:         if (!self::$started || empty($_SESSION)) {
320:             return;
321:         }
322: 
323:         $nf = &$_SESSION['__NF'];
324:         if (isset($nf['META']) && is_array($nf['META'])) {
325:             foreach ($nf['META'] as $name => $foo) {
326:                 if (empty($nf['META'][$name])) {
327:                     unset($nf['META'][$name]);
328:                 }
329:             }
330:         }
331: 
332:         if (empty($nf['META'])) {
333:             unset($nf['META']);
334:         }
335: 
336:         if (empty($nf['DATA'])) {
337:             unset($nf['DATA']);
338:         }
339:     }
340: 
341: 
342:     /********************* configuration ****************d*g**/
343: 
344: 
345:     /**
346:      * Sets session options.
347:      * @param  array
348:      * @return static
349:      * @throws Nette\NotSupportedException
350:      * @throws Nette\InvalidStateException
351:      */
352:     public function setOptions(array $options)
353:     {
354:         $normalized = [];
355:         foreach ($options as $key => $value) {
356:             if (!strncmp($key, 'session.', 8)) { // back compatibility
357:                 $key = substr($key, 8);
358:             }
359:             $key = strtolower(preg_replace('#(.)(?=[A-Z])#', '$1_', $key)); // camelCase -> snake_case
360:             $normalized[$key] = $value;
361:         }
362:         if (self::$started) {
363:             $this->configure($normalized);
364:         }
365:         $this->options = $normalized + $this->options;
366:         if (!empty($normalized['auto_start'])) {
367:             $this->start();
368:         }
369:         return $this;
370:     }
371: 
372: 
373:     /**
374:      * Returns all session options.
375:      * @return array
376:      */
377:     public function getOptions()
378:     {
379:         return $this->options;
380:     }
381: 
382: 
383:     /**
384:      * Configures session environment.
385:      * @param  array
386:      * @return void
387:      */
388:     private function configure(array $config)
389:     {
390:         $special = ['cache_expire' => 1, 'cache_limiter' => 1, 'save_path' => 1, 'name' => 1];
391:         $cookie = $origCookie = session_get_cookie_params();
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:                 $cookie[substr($key, 7)] = $value;
399: 
400:             } else {
401:                 if (session_status() === PHP_SESSION_ACTIVE) {
402:                     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().'));
403:                 }
404:                 if (isset($special[$key])) {
405:                     $key = "session_$key";
406:                     $key($value);
407: 
408:                 } elseif (function_exists('ini_set')) {
409:                     ini_set("session.$key", (string) $value);
410: 
411:                 } elseif (ini_get("session.$key") != $value) { // intentionally !=
412:                     throw new Nette\NotSupportedException("Unable to set 'session.$key' to '$value' because function ini_set() is disabled.");
413:                 }
414:             }
415:         }
416: 
417:         if ($cookie !== $origCookie) {
418:             if (PHP_VERSION_ID >= 70300) {
419:                 session_set_cookie_params($cookie);
420:             } else {
421:                 session_set_cookie_params(
422:                     $cookie['lifetime'],
423:                     $cookie['path'] . (isset($cookie['samesite']) ? '; SameSite=' . $cookie['samesite'] : ''),
424:                     $cookie['domain'],
425:                     $cookie['secure'],
426:                     $cookie['httponly']
427:                 );
428:             }
429:             if (self::$started) {
430:                 $this->sendCookie();
431:             }
432:         }
433: 
434:         if ($this->handler) {
435:             session_set_save_handler($this->handler);
436:         }
437:     }
438: 
439: 
440:     /**
441:      * Sets the amount of time allowed between requests before the session will be terminated.
442:      * @param  string|int|\DateTimeInterface  time, value 0 means "until the browser is closed"
443:      * @return static
444:      */
445:     public function setExpiration($time)
446:     {
447:         if (empty($time)) {
448:             return $this->setOptions([
449:                 'gc_maxlifetime' => self::DEFAULT_FILE_LIFETIME,
450:                 'cookie_lifetime' => 0,
451:             ]);
452: 
453:         } else {
454:             $time = Nette\Utils\DateTime::from($time)->format('U') - time();
455:             return $this->setOptions([
456:                 'gc_maxlifetime' => $time,
457:                 'cookie_lifetime' => $time,
458:             ]);
459:         }
460:     }
461: 
462: 
463:     /**
464:      * Sets the session cookie parameters.
465:      * @param  string  path
466:      * @param  string  domain
467:      * @param  bool    secure
468:      * @param  string  samesite
469:      * @return static
470:      */
471:     public function setCookieParameters($path, $domain = null, $secure = null, $samesite = null)
472:     {
473:         return $this->setOptions([
474:             'cookie_path' => $path,
475:             'cookie_domain' => $domain,
476:             'cookie_secure' => $secure,
477:             'cookie_samesite' => $samesite,
478:         ]);
479:     }
480: 
481: 
482:     /**
483:      * Returns the session cookie parameters.
484:      * @return array  containing items: lifetime, path, domain, secure, httponly
485:      */
486:     public function getCookieParameters()
487:     {
488:         return session_get_cookie_params();
489:     }
490: 
491: 
492:     /**
493:      * Sets path of the directory used to save session data.
494:      * @return static
495:      */
496:     public function setSavePath($path)
497:     {
498:         return $this->setOptions([
499:             'save_path' => $path,
500:         ]);
501:     }
502: 
503: 
504:     /**
505:      * @deprecated  use setHandler().
506:      * @return static
507:      */
508:     public function setStorage(ISessionStorage $storage)
509:     {
510:         if (self::$started) {
511:             throw new Nette\InvalidStateException('Unable to set storage when session has been started.');
512:         }
513:         session_set_save_handler(
514:             [$storage, 'open'], [$storage, 'close'], [$storage, 'read'],
515:             [$storage, 'write'], [$storage, 'remove'], [$storage, 'clean']
516:         );
517:         return $this;
518:     }
519: 
520: 
521:     /**
522:      * Sets user session handler.
523:      * @return static
524:      */
525:     public function setHandler(\SessionHandlerInterface $handler)
526:     {
527:         if (self::$started) {
528:             throw new Nette\InvalidStateException('Unable to set handler when session has been started.');
529:         }
530:         $this->handler = $handler;
531:         return $this;
532:     }
533: 
534: 
535:     /**
536:      * Sends the session cookies.
537:      * @return void
538:      */
539:     private function sendCookie()
540:     {
541:         $cookie = $this->getCookieParameters();
542:         $this->response->setCookie(
543:             session_name(), session_id(),
544:             $cookie['lifetime'] ? $cookie['lifetime'] + time() : 0,
545:             $cookie['path'], $cookie['domain'], $cookie['secure'], $cookie['httponly'],
546:             isset($cookie['samesite']) ? $cookie['samesite'] : null
547:         );
548:     }
549: }
550: 
Nette 2.4-20180918 API API documentation generated by ApiGen 2.8.0