PageRenderTime 44ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/symphony/lib/core/class.session.php

http://github.com/symphonycms/symphony-2
PHP | 331 lines | 160 code | 33 blank | 138 comment | 23 complexity | 5befe03131915bbe5ac11d7dbcde1093 MD5 | raw file
  1. <?php
  2. /**
  3. * @package core
  4. */
  5. /**
  6. * The Session class is a handler for all Session related logic in PHP. The functions
  7. * map directly to all handler functions as defined by session_set_save_handler in
  8. * PHP. In Symphony, this function is used in conjunction with the `Cookie` class.
  9. * Based on: http://php.net/manual/en/function.session-set-save-handler.php#81761
  10. * by klose at openriverbed dot de which was based on
  11. * http://php.net/manual/en/function.session-set-save-handler.php#79706 by
  12. * maria at junkies dot jp
  13. *
  14. * @link http://php.net/manual/en/function.session-set-save-handler.php
  15. */
  16. class Session
  17. {
  18. /**
  19. * If a Session has been created, this will be true, otherwise false
  20. *
  21. * @var boolean
  22. */
  23. private static $_initialized = false;
  24. /**
  25. * Disallow public construction
  26. */
  27. private function __construct()
  28. {
  29. }
  30. /**
  31. * Starts a Session object, only if one doesn't already exist. This function maps
  32. * the Session Handler functions to this classes methods by reading the default
  33. * information from the PHP ini file.
  34. *
  35. * @link http://php.net/manual/en/function.session-set-save-handler.php
  36. * @link http://php.net/manual/en/function.session-set-cookie-params.php
  37. * @param integer $lifetime
  38. * How long a Session is valid for, by default this is 0, which means it
  39. * never expires
  40. * @param string $path
  41. * The path the cookie is valid for on the domain
  42. * @param string $domain
  43. * The domain this cookie is valid for
  44. * @param boolean $httpOnly
  45. * Whether this cookie can be read by Javascript. By default the cookie
  46. * cannot be read by Javascript
  47. * @throws Exception
  48. * @return string|boolean
  49. * Returns the Session ID on success, or false on error.
  50. */
  51. public static function start($lifetime = 0, $path = '/', $domain = null, $httpOnly = true)
  52. {
  53. if (!self::$_initialized) {
  54. if (!is_object(Symphony::Database()) || !Symphony::Database()->isConnected()) {
  55. throw new Exception('Failed to start session, no Database found.');
  56. }
  57. // Get config
  58. $gcDivisor = Symphony::Configuration()->get('session_gc_divisor', 'symphony');
  59. $strictDomain = Symphony::Configuration()->get('session_strict_domain', 'symphony') === 'yes';
  60. // Set php parameters
  61. if (session_id() == '' && !headers_sent()) {
  62. ini_set('session.use_trans_sid', '0');
  63. ini_set('session.use_strict_mode', '1');
  64. ini_set('session.use_only_cookies', '1');
  65. ini_set('session.gc_maxlifetime', $lifetime);
  66. ini_set('session.gc_probability', '1');
  67. ini_set('session.gc_divisor', $gcDivisor);
  68. }
  69. // Register handler
  70. $handler = new Session;
  71. session_set_save_handler(
  72. [$handler ,'open'],
  73. [$handler ,'close'],
  74. [$handler ,'read'],
  75. [$handler ,'write'],
  76. [$handler ,'destroy'],
  77. [$handler ,'gc']
  78. );
  79. // Set cookie parameters
  80. if ($strictDomain) {
  81. // setting the domain to null makes the cookie valid for the current host only
  82. $domain = null;
  83. } else {
  84. $domain = $domain ? : $handler->getDomain();
  85. }
  86. session_set_cookie_params(
  87. $lifetime,
  88. $handler->createCookieSafePath($path),
  89. $domain,
  90. defined('__SECURE__') && __SECURE__,
  91. $httpOnly
  92. );
  93. session_cache_limiter('');
  94. if (!session_id()) {
  95. if (headers_sent()) {
  96. throw new Exception('Headers already sent. Cannot start session.');
  97. }
  98. register_shutdown_function('session_write_close');
  99. session_start();
  100. }
  101. self::$_initialized = true;
  102. }
  103. return session_id();
  104. }
  105. /**
  106. * Returns a properly formatted ascii string for the cookie path.
  107. * Browsers are notoriously bad at parsing the cookie path. They do not
  108. * respect the content-encoding header. So we must be careful when dealing
  109. * with setups with special characters in their paths.
  110. *
  111. * @since Symphony 2.7.0
  112. **/
  113. protected function createCookieSafePath($path)
  114. {
  115. $path = array_filter(explode('/', $path));
  116. if (empty($path)) {
  117. return '/';
  118. }
  119. $path = array_map('rawurlencode', $path);
  120. return '/' . implode('/', $path);
  121. }
  122. /**
  123. * Returns the current domain for the Session to be saved to, if the installation
  124. * is on localhost, this returns null and just allows PHP to take care of setting
  125. * the valid domain for the Session, otherwise it will return the non-www version
  126. * of the domain host.
  127. *
  128. * @return string|null
  129. * Null if on localhost, or HTTP_HOST is not set, a string of the domain name sans
  130. * www otherwise
  131. */
  132. public function getDomain()
  133. {
  134. if (HTTP_HOST) {
  135. if (preg_match('/(localhost|127\.0\.0\.1)/', HTTP_HOST)) {
  136. return null; // prevent problems on local setups
  137. }
  138. // Remove leading www and ending :port
  139. return preg_replace('/(^www\.|:\d+$)/i', null, HTTP_HOST);
  140. }
  141. return null;
  142. }
  143. /**
  144. * Allows the Session to open without any further logic.
  145. *
  146. * @return boolean
  147. * Always returns true
  148. */
  149. public function open()
  150. {
  151. return true;
  152. }
  153. /**
  154. * Allows the Session to close without any further logic. Acts as a
  155. * destructor function for the Session.
  156. *
  157. * @return boolean
  158. * Always returns true
  159. */
  160. public function close()
  161. {
  162. return true;
  163. }
  164. /**
  165. * Given an ID, and some data, save it into `tbl_sessions`. This uses
  166. * the ID as a unique key, and will override any existing data. If the
  167. * `$data` is deemed to be empty, no row will be saved in the database
  168. * unless there is an existing row.
  169. *
  170. * @param string $id
  171. * The ID of the Session, usually a hash
  172. * @param string $data
  173. * The Session information, usually a serialized object of
  174. * `$_SESSION[Cookie->_index]`
  175. * @throws DatabaseException
  176. * @return boolean
  177. * true if the Session information was saved successfully, false otherwise
  178. */
  179. public function write($id, $data)
  180. {
  181. // Only prevent this record from saving if there isn't already a record
  182. // in the database. This prevents empty Sessions from being created, but
  183. // allows them to be nulled.
  184. $session_data = $this->read($id);
  185. if (!$session_data) {
  186. $empty = true;
  187. if (function_exists('session_status') && session_status() === PHP_SESSION_ACTIVE) {
  188. $unserialized_data = $this->unserialize($data);
  189. foreach ($unserialized_data as $d) {
  190. if (!empty($d)) {
  191. $empty = false;
  192. }
  193. }
  194. if ($empty) {
  195. return true;
  196. }
  197. // PHP 7.0 makes the session inactive in write callback,
  198. // so we try to detect empty sessions without decoding them
  199. } elseif ($data === Symphony::Configuration()->get('cookie_prefix', 'symphony') . '|a:0:{}') {
  200. return true;
  201. }
  202. }
  203. $fields = array(
  204. 'session' => $id,
  205. 'session_expires' => time(),
  206. 'session_data' => $data
  207. );
  208. return Symphony::Database()
  209. ->insert('tbl_sessions')
  210. ->values($fields)
  211. ->updateOnDuplicateKey()
  212. ->execute()
  213. ->success();
  214. }
  215. /**
  216. * Given raw session data return the unserialized array.
  217. * Used to check if the session is really empty before writing.
  218. *
  219. * @since Symphony 2.3.3
  220. * @param string $data
  221. * The serialized session data
  222. * @return array
  223. * The unserialised session data
  224. */
  225. private function unserialize($data)
  226. {
  227. $hasBuffer = isset($_SESSION);
  228. $buffer = $_SESSION;
  229. session_decode($data);
  230. $session = $_SESSION;
  231. if ($hasBuffer) {
  232. $_SESSION = $buffer;
  233. } else {
  234. unset($_SESSION);
  235. }
  236. return $session;
  237. }
  238. /**
  239. * Given a session's ID, return it's row from `tbl_sessions`
  240. *
  241. * @param string $id
  242. * The identifier for the Session to fetch
  243. * @return string
  244. * The serialised session data
  245. */
  246. public function read($id)
  247. {
  248. if (!$id) {
  249. return null;
  250. }
  251. return Symphony::Database()
  252. ->select(['session_data'])
  253. ->from('tbl_sessions')
  254. ->where(['session' => $id])
  255. ->limit(1)
  256. ->execute()
  257. ->string('session_data');
  258. }
  259. /**
  260. * Given a session's ID, remove it's row from `tbl_sessions`
  261. *
  262. * @param string $id
  263. * The identifier for the Session to destroy
  264. * @throws DatabaseException
  265. * @return boolean
  266. * true if the Session was deleted successfully, false otherwise
  267. */
  268. public function destroy($id)
  269. {
  270. if (!$id) {
  271. return true;
  272. }
  273. return Symphony::Database()
  274. ->delete('tbl_sessions')
  275. ->where(['session' => $id])
  276. ->execute()
  277. ->success();
  278. }
  279. /**
  280. * The garbage collector, which removes all empty Sessions, or any
  281. * Sessions that have expired. This has a 10% chance of firing based
  282. * off the `gc_probability`/`gc_divisor`.
  283. *
  284. * @param integer $max
  285. * The max session lifetime.
  286. * @throws DatabaseException
  287. * @return boolean
  288. * true on Session deletion, false if an error occurs
  289. */
  290. public function gc($max)
  291. {
  292. return Symphony::Database()
  293. ->delete('tbl_sessions')
  294. ->where(['session_expires' => ['<=' => time() - $max]])
  295. ->execute()
  296. ->success();
  297. }
  298. }