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 NSession extends NObject
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:
71: public function __construct(IHttpRequest $request, IHttpResponse $response)
72: {
73: $this->request = $request;
74: $this->response = $response;
75: }
76:
77:
78:
79: 80: 81: 82: 83:
84: public function start()
85: {
86: if (self::$started) {
87: return;
88: }
89:
90: $this->configure($this->options);
91:
92: $id = & $_COOKIE[session_name()];
93: if (!is_string($id) || !preg_match('#^[0-9a-zA-Z,-]{22,128}\z#i', $id)) {
94: unset($_COOKIE[session_name()]);
95: }
96:
97: 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.
98: if (($severity & error_reporting()) === $severity) {
99: $error = $message;
100: restore_error_handler();
101: }
102: '));
103: session_start();
104: $this->response->removeDuplicateCookies();
105: restore_error_handler();
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 = NStrings::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: != NClassReflection::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:
178: public function isStarted()
179: {
180: return (bool) self::$started;
181: }
182:
183:
184:
185: 186: 187: 188:
189: public function close()
190: {
191: if (self::$started) {
192: $this->clean();
193: session_write_close();
194: self::$started = FALSE;
195: }
196: }
197:
198:
199:
200: 201: 202: 203:
204: public function destroy()
205: {
206: if (!self::$started) {
207: throw new InvalidStateException('Session is not started.');
208: }
209:
210: session_destroy();
211: $_SESSION = NULL;
212: self::$started = FALSE;
213: if (!$this->response->isSent()) {
214: $params = session_get_cookie_params();
215: $this->response->deleteCookie(session_name(), $params['path'], $params['domain'], $params['secure']);
216: }
217: }
218:
219:
220:
221: 222: 223: 224:
225: public function exists()
226: {
227: return self::$started || $this->request->getCookie($this->getName()) !== NULL;
228: }
229:
230:
231:
232: 233: 234: 235: 236:
237: public function regenerateId()
238: {
239: if (self::$started && !$this->regenerated) {
240: if (headers_sent($file, $line)) {
241: throw new InvalidStateException("Cannot regenerate session ID after HTTP headers have been sent" . ($file ? " (output started at $file:$line)." : "."));
242: }
243: session_regenerate_id(TRUE);
244: session_write_close();
245: $backup = $_SESSION;
246: session_start();
247: $_SESSION = $backup;
248: $this->response->removeDuplicateCookies();
249: }
250: $this->regenerated = TRUE;
251: }
252:
253:
254:
255: 256: 257: 258:
259: public function getId()
260: {
261: return session_id();
262: }
263:
264:
265:
266: 267: 268: 269: 270:
271: public function setName($name)
272: {
273: if (!is_string($name) || !preg_match('#[^0-9.][^.]*\z#A', $name)) {
274: throw new InvalidArgumentException('Session name must be a string and cannot contain dot.');
275: }
276:
277: session_name($name);
278: return $this->setOptions(array(
279: 'name' => $name,
280: ));
281: }
282:
283:
284:
285: 286: 287: 288:
289: public function getName()
290: {
291: return isset($this->options['name']) ? $this->options['name'] : session_name();
292: }
293:
294:
295:
296:
297:
298:
299:
300: 301: 302: 303: 304: 305: 306:
307: public function getSection($section, $class = 'NSessionSection')
308: {
309: return new $class($this, $section);
310: }
311:
312:
313:
314:
315: function getNamespace($section)
316: {
317: trigger_error(__METHOD__ . '() is deprecated; use getSection() instead.', E_USER_WARNING);
318: return $this->getSection($section);
319: }
320:
321:
322:
323: 324: 325: 326: 327:
328: public function hasSection($section)
329: {
330: if ($this->exists() && !self::$started) {
331: $this->start();
332: }
333:
334: return !empty($_SESSION['__NF']['DATA'][$section]);
335: }
336:
337:
338:
339: 340: 341: 342:
343: public function getIterator()
344: {
345: if ($this->exists() && !self::$started) {
346: $this->start();
347: }
348:
349: if (isset($_SESSION['__NF']['DATA'])) {
350: return new ArrayIterator(array_keys($_SESSION['__NF']['DATA']));
351:
352: } else {
353: return new ArrayIterator;
354: }
355: }
356:
357:
358:
359: 360: 361: 362:
363: public function clean()
364: {
365: if (!self::$started || empty($_SESSION)) {
366: return;
367: }
368:
369: $nf = & $_SESSION['__NF'];
370: if (isset($nf['META']) && is_array($nf['META'])) {
371: foreach ($nf['META'] as $name => $foo) {
372: if (empty($nf['META'][$name])) {
373: unset($nf['META'][$name]);
374: }
375: }
376: }
377:
378: if (empty($nf['META'])) {
379: unset($nf['META']);
380: }
381:
382: if (empty($nf['DATA'])) {
383: unset($nf['DATA']);
384: }
385:
386: if (empty($_SESSION)) {
387:
388: }
389: }
390:
391:
392:
393:
394:
395:
396:
397: 398: 399: 400: 401: 402: 403:
404: public function setOptions(array $options)
405: {
406: if (self::$started) {
407: $this->configure($options);
408: }
409: $this->options = $options + $this->options;
410: if (!empty($options['auto_start'])) {
411: $this->start();
412: }
413: return $this;
414: }
415:
416:
417:
418: 419: 420: 421:
422: public function getOptions()
423: {
424: return $this->options;
425: }
426:
427:
428:
429: 430: 431: 432: 433:
434: private function configure(array $config)
435: {
436: $special = array('cache_expire' => 1, 'cache_limiter' => 1, 'save_path' => 1, 'name' => 1);
437:
438: foreach ($config as $key => $value) {
439: if (!strncmp($key, 'session.', 8)) {
440: $key = substr($key, 8);
441: }
442: $key = strtolower(preg_replace('#(.)(?=[A-Z])#', '$1_', $key));
443:
444: if ($value === NULL || ini_get("session.$key") == $value) {
445: continue;
446:
447: } elseif (strncmp($key, 'cookie_', 7) === 0) {
448: if (!isset($cookie)) {
449: $cookie = session_get_cookie_params();
450: }
451: $cookie[substr($key, 7)] = $value;
452:
453: } else {
454: if (defined('SID')) {
455: 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()."));
456: }
457: if (isset($special[$key])) {
458: $key = "session_$key";
459: $key($value);
460:
461: } elseif (function_exists('ini_set')) {
462: ini_set("session.$key", $value);
463:
464: } elseif (!NFramework::$iAmUsingBadHost) {
465: throw new NotSupportedException('Required function ini_set() is disabled.');
466: }
467: }
468: }
469:
470: if (isset($cookie)) {
471: session_set_cookie_params(
472: $cookie['lifetime'], $cookie['path'], $cookie['domain'],
473: $cookie['secure'], $cookie['httponly']
474: );
475: if (self::$started) {
476: $this->sendCookie();
477: }
478: }
479: }
480:
481:
482:
483: 484: 485: 486: 487:
488: public function setExpiration($time)
489: {
490: if (empty($time)) {
491: return $this->setOptions(array(
492: 'gc_maxlifetime' => self::DEFAULT_FILE_LIFETIME,
493: 'cookie_lifetime' => 0,
494: ));
495:
496: } else {
497: $time = NDateTime53::from($time)->format('U') - time();
498: return $this->setOptions(array(
499: 'gc_maxlifetime' => $time,
500: 'cookie_lifetime' => $time,
501: ));
502: }
503: }
504:
505:
506:
507: 508: 509: 510: 511: 512: 513:
514: public function setCookieParameters($path, $domain = NULL, $secure = NULL)
515: {
516: return $this->setOptions(array(
517: 'cookie_path' => $path,
518: 'cookie_domain' => $domain,
519: 'cookie_secure' => $secure
520: ));
521: }
522:
523:
524:
525: 526: 527: 528:
529: public function getCookieParameters()
530: {
531: return session_get_cookie_params();
532: }
533:
534:
535:
536:
537: function setCookieParams($path, $domain = NULL, $secure = NULL)
538: {
539: trigger_error(__METHOD__ . '() is deprecated; use setCookieParameters() instead.', E_USER_WARNING);
540: return $this->setCookieParameters($path, $domain, $secure);
541: }
542:
543:
544:
545: 546: 547: 548:
549: public function setSavePath($path)
550: {
551: return $this->setOptions(array(
552: 'save_path' => $path,
553: ));
554: }
555:
556:
557:
558: 559: 560: 561:
562: public function setStorage(ISessionStorage $storage)
563: {
564: if (self::$started) {
565: throw new InvalidStateException("Unable to set storage when session has been started.");
566: }
567: session_set_save_handler(
568: array($storage, 'open'), array($storage, 'close'), array($storage, 'read'),
569: array($storage, 'write'), array($storage, 'remove'), array($storage, 'clean')
570: );
571: }
572:
573:
574:
575: 576: 577: 578:
579: private function sendCookie()
580: {
581: $cookie = $this->getCookieParameters();
582: $this->response->setCookie(
583: session_name(), session_id(),
584: $cookie['lifetime'] ? $cookie['lifetime'] + time() : 0,
585: $cookie['path'], $cookie['domain'], $cookie['secure'], $cookie['httponly']
586:
587: )->setCookie(
588: 'nette-browser', $_SESSION['__NF']['B'],
589: NHttpResponse::BROWSER, $cookie['path'], $cookie['domain']
590: );
591: }
592:
593: }
594: