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