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