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