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