PageRenderTime 40ms CodeModel.GetById 10ms RepoModel.GetById 0ms app.codeStats 0ms

/libs/Nette/Http/Session.php

https://github.com/premiumcombination/nts
PHP | 565 lines | 304 code | 122 blank | 139 comment | 57 complexity | f3feae037f56e3a5c1f1802e13963418 MD5 | raw file
  1. <?php
  2. /**
  3. * This file is part of the Nette Framework (http://nette.org)
  4. *
  5. * Copyright (c) 2004, 2011 David Grudl (http://davidgrudl.com)
  6. *
  7. * For the full copyright and license information, please view
  8. * the file license.txt that was distributed with this source code.
  9. */
  10. namespace Nette\Http;
  11. use Nette;
  12. /**
  13. * Provides access to session sections as well as session settings and management methods.
  14. *
  15. * @author David Grudl
  16. */
  17. class Session extends Nette\Object
  18. {
  19. /** Default file lifetime is 3 hours */
  20. const DEFAULT_FILE_LIFETIME = 10800;
  21. /** @var bool is required session ID regeneration? */
  22. private $regenerationNeeded;
  23. /** @var bool has been session started? */
  24. private static $started;
  25. /** @var array default configuration */
  26. private $options = array(
  27. // security
  28. 'referer_check' => '', // must be disabled because PHP implementation is invalid
  29. 'use_cookies' => 1, // must be enabled to prevent Session Hijacking and Fixation
  30. 'use_only_cookies' => 1, // must be enabled to prevent Session Fixation
  31. 'use_trans_sid' => 0, // must be disabled to prevent Session Hijacking and Fixation
  32. // cookies
  33. 'cookie_lifetime' => 0, // until the browser is closed
  34. 'cookie_path' => '/', // cookie is available within the entire domain
  35. 'cookie_domain' => '', // cookie is available on current subdomain only
  36. 'cookie_secure' => FALSE, // cookie is available on HTTP & HTTPS
  37. 'cookie_httponly' => TRUE,// must be enabled to prevent Session Hijacking
  38. // other
  39. 'gc_maxlifetime' => self::DEFAULT_FILE_LIFETIME,// 3 hours
  40. 'cache_limiter' => NULL, // (default "nocache", special value "\0")
  41. 'cache_expire' => NULL, // (default "180")
  42. 'hash_function' => NULL, // (default "0", means MD5)
  43. 'hash_bits_per_character' => NULL, // (default "4")
  44. );
  45. /** @var IRequest */
  46. private $request;
  47. /** @var IResponse */
  48. private $response;
  49. public function __construct(IRequest $request, IResponse $response)
  50. {
  51. $this->request = $request;
  52. $this->response = $response;
  53. }
  54. /**
  55. * Starts and initializes session data.
  56. * @throws Nette\InvalidStateException
  57. * @return void
  58. */
  59. public function start()
  60. {
  61. if (self::$started) {
  62. return;
  63. } elseif (self::$started === NULL && defined('SID')) {
  64. throw new Nette\InvalidStateException('A session had already been started by session.auto_start or session_start().');
  65. }
  66. $this->configure($this->options);
  67. Nette\Diagnostics\Debugger::tryError();
  68. session_start();
  69. if (Nette\Diagnostics\Debugger::catchError($e)) {
  70. @session_write_close(); // this is needed
  71. throw new Nette\InvalidStateException('session_start(): ' . $e->getMessage(), 0, $e);
  72. }
  73. self::$started = TRUE;
  74. if ($this->regenerationNeeded) {
  75. session_regenerate_id(TRUE);
  76. $this->regenerationNeeded = FALSE;
  77. }
  78. /* structure:
  79. __NF: Counter, BrowserKey, Data, Meta
  80. DATA: section->variable = data
  81. META: section->variable = Timestamp, Browser, Version
  82. */
  83. unset($_SESSION['__NT'], $_SESSION['__NS'], $_SESSION['__NM']); // old unused structures
  84. // initialize structures
  85. $nf = & $_SESSION['__NF'];
  86. if (empty($nf)) { // new session
  87. $nf = array('C' => 0);
  88. } else {
  89. $nf['C']++;
  90. }
  91. // browser closing detection
  92. $browserKey = $this->request->getCookie('nette-browser');
  93. if (!$browserKey) {
  94. $browserKey = Nette\Utils\Strings::random();
  95. }
  96. $browserClosed = !isset($nf['B']) || $nf['B'] !== $browserKey;
  97. $nf['B'] = $browserKey;
  98. // resend cookie
  99. $this->sendCookie();
  100. // process meta metadata
  101. if (isset($nf['META'])) {
  102. $now = time();
  103. // expire section variables
  104. foreach ($nf['META'] as $section => $metadata) {
  105. if (is_array($metadata)) {
  106. foreach ($metadata as $variable => $value) {
  107. if ((!empty($value['B']) && $browserClosed) || (!empty($value['T']) && $now > $value['T']) // whenBrowserIsClosed || Time
  108. || ($variable !== '' && is_object($nf['DATA'][$section][$variable]) && (isset($value['V']) ? $value['V'] : NULL) // Version
  109. !== Nette\Reflection\ClassType::from($nf['DATA'][$section][$variable])->getAnnotation('serializationVersion'))
  110. ) {
  111. if ($variable === '') { // expire whole section
  112. unset($nf['META'][$section], $nf['DATA'][$section]);
  113. continue 2;
  114. }
  115. unset($nf['META'][$section][$variable], $nf['DATA'][$section][$variable]);
  116. }
  117. }
  118. }
  119. }
  120. }
  121. register_shutdown_function(array($this, 'clean'));
  122. }
  123. /**
  124. * Has been session started?
  125. * @return bool
  126. */
  127. public function isStarted()
  128. {
  129. return (bool) self::$started;
  130. }
  131. /**
  132. * Ends the current session and store session data.
  133. * @return void
  134. */
  135. public function close()
  136. {
  137. if (self::$started) {
  138. $this->clean();
  139. session_write_close();
  140. self::$started = FALSE;
  141. }
  142. }
  143. /**
  144. * Destroys all data registered to a session.
  145. * @return void
  146. */
  147. public function destroy()
  148. {
  149. if (!self::$started) {
  150. throw new Nette\InvalidStateException('Session is not started.');
  151. }
  152. session_destroy();
  153. $_SESSION = NULL;
  154. self::$started = FALSE;
  155. if (!$this->response->isSent()) {
  156. $params = session_get_cookie_params();
  157. $this->response->deleteCookie(session_name(), $params['path'], $params['domain'], $params['secure']);
  158. }
  159. }
  160. /**
  161. * Does session exists for the current request?
  162. * @return bool
  163. */
  164. public function exists()
  165. {
  166. return self::$started || $this->request->getCookie(session_name()) !== NULL;
  167. }
  168. /**
  169. * Regenerates the session ID.
  170. * @throws Nette\InvalidStateException
  171. * @return void
  172. */
  173. public function regenerateId()
  174. {
  175. if (self::$started) {
  176. if (headers_sent($file, $line)) {
  177. throw new Nette\InvalidStateException("Cannot regenerate session ID after HTTP headers have been sent" . ($file ? " (output started at $file:$line)." : "."));
  178. }
  179. session_regenerate_id(TRUE);
  180. } else {
  181. $this->regenerationNeeded = TRUE;
  182. }
  183. }
  184. /**
  185. * Returns the current session ID. Don't make dependencies, can be changed for each request.
  186. * @return string
  187. */
  188. public function getId()
  189. {
  190. return session_id();
  191. }
  192. /**
  193. * Sets the session name to a specified one.
  194. * @param string
  195. * @return Session provides a fluent interface
  196. */
  197. public function setName($name)
  198. {
  199. if (!is_string($name) || !preg_match('#[^0-9.][^.]*$#A', $name)) {
  200. throw new Nette\InvalidArgumentException('Session name must be a string and cannot contain dot.');
  201. }
  202. session_name($name);
  203. return $this->setOptions(array(
  204. 'name' => $name,
  205. ));
  206. }
  207. /**
  208. * Gets the session name.
  209. * @return string
  210. */
  211. public function getName()
  212. {
  213. return session_name();
  214. }
  215. /********************* sections management ****************d*g**/
  216. /**
  217. * Returns specified session section.
  218. * @param string
  219. * @param string
  220. * @return SessionSection
  221. * @throws Nette\InvalidArgumentException
  222. */
  223. public function getSection($section, $class = 'Nette\Http\SessionSection')
  224. {
  225. return new $class($this, $section);
  226. }
  227. /** @deprecated */
  228. function getNamespace($section)
  229. {
  230. trigger_error(__METHOD__ . '() is deprecated; use getSection() instead.', E_USER_WARNING);
  231. return $this->getSection($section);
  232. }
  233. /**
  234. * Checks if a session section exist and is not empty.
  235. * @param string
  236. * @return bool
  237. */
  238. public function hasSection($section)
  239. {
  240. if ($this->exists() && !self::$started) {
  241. $this->start();
  242. }
  243. return !empty($_SESSION['__NF']['DATA'][$section]);
  244. }
  245. /**
  246. * Iteration over all sections.
  247. * @return \ArrayIterator
  248. */
  249. public function getIterator()
  250. {
  251. if ($this->exists() && !self::$started) {
  252. $this->start();
  253. }
  254. if (isset($_SESSION['__NF']['DATA'])) {
  255. return new \ArrayIterator(array_keys($_SESSION['__NF']['DATA']));
  256. } else {
  257. return new \ArrayIterator;
  258. }
  259. }
  260. /**
  261. * Cleans and minimizes meta structures.
  262. * @return void
  263. */
  264. public function clean()
  265. {
  266. if (!self::$started || empty($_SESSION)) {
  267. return;
  268. }
  269. $nf = & $_SESSION['__NF'];
  270. if (isset($nf['META']) && is_array($nf['META'])) {
  271. foreach ($nf['META'] as $name => $foo) {
  272. if (empty($nf['META'][$name])) {
  273. unset($nf['META'][$name]);
  274. }
  275. }
  276. }
  277. if (empty($nf['META'])) {
  278. unset($nf['META']);
  279. }
  280. if (empty($nf['DATA'])) {
  281. unset($nf['DATA']);
  282. }
  283. if (empty($_SESSION)) {
  284. //$this->destroy(); only when shutting down
  285. }
  286. }
  287. /********************* configuration ****************d*g**/
  288. /**
  289. * Sets session options.
  290. * @param array
  291. * @return Session provides a fluent interface
  292. * @throws Nette\NotSupportedException
  293. * @throws Nette\InvalidStateException
  294. */
  295. public function setOptions(array $options)
  296. {
  297. if (self::$started) {
  298. $this->configure($options);
  299. }
  300. $this->options = $options + $this->options;
  301. if (!empty($options['auto_start'])) {
  302. $this->start();
  303. }
  304. return $this;
  305. }
  306. /**
  307. * Returns all session options.
  308. * @return array
  309. */
  310. public function getOptions()
  311. {
  312. return $this->options;
  313. }
  314. /**
  315. * Configurates session environment.
  316. * @param array
  317. * @return void
  318. */
  319. private function configure(array $config)
  320. {
  321. $special = array('cache_expire' => 1, 'cache_limiter' => 1, 'save_path' => 1, 'name' => 1);
  322. foreach ($config as $key => $value) {
  323. if (!strncmp($key, 'session.', 8)) { // back compatibility
  324. $key = substr($key, 8);
  325. }
  326. if ($value === NULL) {
  327. continue;
  328. } elseif (isset($special[$key])) {
  329. if (self::$started) {
  330. throw new Nette\InvalidStateException("Unable to set '$key' when session has been started.");
  331. }
  332. $key = "session_$key";
  333. $key($value);
  334. } elseif (strncmp($key, 'cookie_', 7) === 0) {
  335. if (!isset($cookie)) {
  336. $cookie = session_get_cookie_params();
  337. }
  338. $cookie[substr($key, 7)] = $value;
  339. } elseif (!function_exists('ini_set')) {
  340. if (ini_get($key) != $value && !Nette\Framework::$iAmUsingBadHost) { // intentionally ==
  341. throw new Nette\NotSupportedException('Required function ini_set() is disabled.');
  342. }
  343. } else {
  344. if (self::$started) {
  345. throw new Nette\InvalidStateException("Unable to set '$key' when session has been started.");
  346. }
  347. ini_set("session.$key", $value);
  348. }
  349. }
  350. if (isset($cookie)) {
  351. session_set_cookie_params(
  352. $cookie['lifetime'], $cookie['path'], $cookie['domain'],
  353. $cookie['secure'], $cookie['httponly']
  354. );
  355. if (self::$started) {
  356. $this->sendCookie();
  357. }
  358. }
  359. }
  360. /**
  361. * Sets the amount of time allowed between requests before the session will be terminated.
  362. * @param string|int|DateTime time, value 0 means "until the browser is closed"
  363. * @return Session provides a fluent interface
  364. */
  365. public function setExpiration($time)
  366. {
  367. if (empty($time)) {
  368. return $this->setOptions(array(
  369. 'gc_maxlifetime' => self::DEFAULT_FILE_LIFETIME,
  370. 'cookie_lifetime' => 0,
  371. ));
  372. } else {
  373. $time = Nette\DateTime::from($time)->format('U') - time();
  374. return $this->setOptions(array(
  375. 'gc_maxlifetime' => $time,
  376. 'cookie_lifetime' => $time,
  377. ));
  378. }
  379. }
  380. /**
  381. * Sets the session cookie parameters.
  382. * @param string path
  383. * @param string domain
  384. * @param bool secure
  385. * @return Session provides a fluent interface
  386. */
  387. public function setCookieParams($path, $domain = NULL, $secure = NULL)
  388. {
  389. return $this->setOptions(array(
  390. 'cookie_path' => $path,
  391. 'cookie_domain' => $domain,
  392. 'cookie_secure' => $secure
  393. ));
  394. }
  395. /**
  396. * Returns the session cookie parameters.
  397. * @return array containing items: lifetime, path, domain, secure, httponly
  398. */
  399. public function getCookieParams()
  400. {
  401. return session_get_cookie_params();
  402. }
  403. /**
  404. * Sets path of the directory used to save session data.
  405. * @return Session provides a fluent interface
  406. */
  407. public function setSavePath($path)
  408. {
  409. return $this->setOptions(array(
  410. 'save_path' => $path,
  411. ));
  412. }
  413. /**
  414. * Sets user session storage.
  415. * @return Session provides a fluent interface
  416. */
  417. public function setStorage(ISessionStorage $storage)
  418. {
  419. if (self::$started) {
  420. throw new Nette\InvalidStateException("Unable to set storage when session has been started.");
  421. }
  422. session_set_save_handler(
  423. array($storage, 'open'), array($storage, 'close'), array($storage, 'read'),
  424. array($storage, 'write'), array($storage, 'remove'), array($storage, 'clean')
  425. );
  426. }
  427. /**
  428. * Sends the session cookies.
  429. * @return void
  430. */
  431. private function sendCookie()
  432. {
  433. $cookie = $this->getCookieParams();
  434. $this->response->setCookie(
  435. session_name(), session_id(),
  436. $cookie['lifetime'] ? $cookie['lifetime'] + time() : 0,
  437. $cookie['path'], $cookie['domain'], $cookie['secure'], $cookie['httponly']
  438. )->setCookie(
  439. 'nette-browser', $_SESSION['__NF']['B'],
  440. Response::BROWSER, $cookie['path'], $cookie['domain']
  441. );
  442. }
  443. }