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

/www/libs/nette-dev/Web/Session.php

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