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