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