Session.php 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  1. <?php
  2. /**
  3. * @package Grav\Framework\Session
  4. *
  5. * @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
  6. * @license MIT License; see LICENSE file for details.
  7. */
  8. namespace Grav\Framework\Session;
  9. /**
  10. * Class Session
  11. * @package Grav\Framework\Session
  12. */
  13. class Session implements SessionInterface
  14. {
  15. /**
  16. * @var bool
  17. */
  18. protected $started = false;
  19. /**
  20. * @var Session
  21. */
  22. protected static $instance;
  23. /**
  24. * @inheritdoc
  25. */
  26. public static function getInstance()
  27. {
  28. if (null === self::$instance) {
  29. throw new \RuntimeException("Session hasn't been initialized.", 500);
  30. }
  31. return self::$instance;
  32. }
  33. /**
  34. * @inheritdoc
  35. */
  36. public function __construct(array $options = [])
  37. {
  38. // Session is a singleton.
  39. if (\PHP_SAPI === 'cli') {
  40. self::$instance = $this;
  41. return;
  42. }
  43. if (null !== self::$instance) {
  44. throw new \RuntimeException('Session has already been initialized.', 500);
  45. }
  46. // Destroy any existing sessions started with session.auto_start
  47. if ($this->isSessionStarted()) {
  48. session_unset();
  49. session_destroy();
  50. }
  51. // Set default options.
  52. $options += array(
  53. 'cache_limiter' => 'nocache',
  54. 'use_trans_sid' => 0,
  55. 'use_cookies' => 1,
  56. 'lazy_write' => 1,
  57. 'use_strict_mode' => 1
  58. );
  59. $this->setOptions($options);
  60. session_register_shutdown();
  61. self::$instance = $this;
  62. }
  63. /**
  64. * @inheritdoc
  65. */
  66. public function getId()
  67. {
  68. return session_id();
  69. }
  70. /**
  71. * @inheritdoc
  72. */
  73. public function setId($id)
  74. {
  75. session_id($id);
  76. return $this;
  77. }
  78. /**
  79. * @inheritdoc
  80. */
  81. public function getName()
  82. {
  83. return session_name();
  84. }
  85. /**
  86. * @inheritdoc
  87. */
  88. public function setName($name)
  89. {
  90. session_name($name);
  91. return $this;
  92. }
  93. /**
  94. * @inheritdoc
  95. */
  96. public function setOptions(array $options)
  97. {
  98. if (headers_sent() || \PHP_SESSION_ACTIVE === session_status()) {
  99. return;
  100. }
  101. $allowedOptions = [
  102. 'save_path' => true,
  103. 'name' => true,
  104. 'save_handler' => true,
  105. 'gc_probability' => true,
  106. 'gc_divisor' => true,
  107. 'gc_maxlifetime' => true,
  108. 'serialize_handler' => true,
  109. 'cookie_lifetime' => true,
  110. 'cookie_path' => true,
  111. 'cookie_domain' => true,
  112. 'cookie_secure' => true,
  113. 'cookie_httponly' => true,
  114. 'use_strict_mode' => true,
  115. 'use_cookies' => true,
  116. 'use_only_cookies' => true,
  117. 'referer_check' => true,
  118. 'cache_limiter' => true,
  119. 'cache_expire' => true,
  120. 'use_trans_sid' => true,
  121. 'trans_sid_tags' => true, // PHP 7.1
  122. 'trans_sid_hosts' => true, // PHP 7.1
  123. 'sid_length' => true, // PHP 7.1
  124. 'sid_bits_per_character' => true, // PHP 7.1
  125. 'upload_progress.enabled' => true,
  126. 'upload_progress.cleanup' => true,
  127. 'upload_progress.prefix' => true,
  128. 'upload_progress.name' => true,
  129. 'upload_progress.freq' => true,
  130. 'upload_progress.min-freq' => true,
  131. 'lazy_write' => true,
  132. 'url_rewriter.tags' => true, // Not used in PHP 7.1
  133. 'hash_function' => true, // Not used in PHP 7.1
  134. 'hash_bits_per_character' => true, // Not used in PHP 7.1
  135. 'entropy_file' => true, // Not used in PHP 7.1
  136. 'entropy_length' => true, // Not used in PHP 7.1
  137. ];
  138. foreach ($options as $key => $value) {
  139. if (is_array($value)) {
  140. // Allow nested options.
  141. foreach ($value as $key2 => $value2) {
  142. $ckey = "{$key}.{$key2}";
  143. if (isset($value2, $allowedOptions[$ckey])) {
  144. $this->ini_set("session.{$ckey}", $value2);
  145. }
  146. }
  147. } elseif (isset($value, $allowedOptions[$key])) {
  148. $this->ini_set("session.{$key}", $value);
  149. }
  150. }
  151. }
  152. /**
  153. * @inheritdoc
  154. */
  155. public function start($readonly = false)
  156. {
  157. // Protection against invalid session cookie names throwing exception: http://php.net/manual/en/function.session-id.php#116836
  158. if (isset($_COOKIE[session_name()]) && !preg_match('/^[-,a-zA-Z0-9]{1,128}$/', $_COOKIE[session_name()])) {
  159. unset($_COOKIE[session_name()]);
  160. }
  161. $options = $readonly ? ['read_and_close' => '1'] : [];
  162. $success = @session_start($options);
  163. if (!$success) {
  164. $last = error_get_last();
  165. $error = $last ? $last['message'] : 'Unknown error';
  166. throw new \RuntimeException('Failed to start session: ' . $error, 500);
  167. }
  168. $params = session_get_cookie_params();
  169. setcookie(
  170. session_name(),
  171. session_id(),
  172. time() + $params['lifetime'],
  173. $params['path'],
  174. $params['domain'],
  175. $params['secure'],
  176. $params['httponly']
  177. );
  178. $this->started = true;
  179. return $this;
  180. }
  181. /**
  182. * @inheritdoc
  183. */
  184. public function invalidate()
  185. {
  186. $params = session_get_cookie_params();
  187. setcookie(
  188. session_name(),
  189. '',
  190. time() - 42000,
  191. $params['path'],
  192. $params['domain'],
  193. $params['secure'],
  194. $params['httponly']
  195. );
  196. session_unset();
  197. session_destroy();
  198. $this->started = false;
  199. return $this;
  200. }
  201. /**
  202. * @inheritdoc
  203. */
  204. public function close()
  205. {
  206. if ($this->started) {
  207. session_write_close();
  208. }
  209. $this->started = false;
  210. return $this;
  211. }
  212. /**
  213. * @inheritdoc
  214. */
  215. public function clear()
  216. {
  217. session_unset();
  218. return $this;
  219. }
  220. /**
  221. * @inheritdoc
  222. */
  223. public function getAll()
  224. {
  225. return $_SESSION;
  226. }
  227. /**
  228. * @inheritdoc
  229. */
  230. public function getIterator()
  231. {
  232. return new \ArrayIterator($_SESSION);
  233. }
  234. /**
  235. * @inheritdoc
  236. */
  237. public function isStarted()
  238. {
  239. return $this->started;
  240. }
  241. /**
  242. * @inheritdoc
  243. */
  244. public function __isset($name)
  245. {
  246. return isset($_SESSION[$name]);
  247. }
  248. /**
  249. * @inheritdoc
  250. */
  251. public function __get($name)
  252. {
  253. return isset($_SESSION[$name]) ? $_SESSION[$name] : null;
  254. }
  255. /**
  256. * @inheritdoc
  257. */
  258. public function __set($name, $value)
  259. {
  260. $_SESSION[$name] = $value;
  261. }
  262. /**
  263. * @inheritdoc
  264. */
  265. public function __unset($name)
  266. {
  267. unset($_SESSION[$name]);
  268. }
  269. /**
  270. * http://php.net/manual/en/function.session-status.php#113468
  271. * Check if session is started nicely.
  272. * @return bool
  273. */
  274. protected function isSessionStarted()
  275. {
  276. return \PHP_SAPI !== 'cli' ? \PHP_SESSION_ACTIVE === session_status() : false;
  277. }
  278. /**
  279. * @param string $key
  280. * @param mixed $value
  281. */
  282. protected function ini_set($key, $value)
  283. {
  284. if (!is_string($value)) {
  285. if (is_bool($value)) {
  286. $value = $value ? '1' : '0';
  287. }
  288. $value = (string)$value;
  289. }
  290. ini_set($key, $value);
  291. }
  292. }