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