PageRenderTime 27ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/system/libraries/Session/drivers/Session_files_driver.php

http://github.com/EllisLab/CodeIgniter
PHP | 432 lines | 216 code | 52 blank | 164 comment | 27 complexity | 52ea0a94abb55c3715623ed65907603c MD5 | raw file
Possible License(s): CC-BY-SA-3.0
  1. <?php
  2. /**
  3. * CodeIgniter
  4. *
  5. * An open source application development framework for PHP
  6. *
  7. * This content is released under the MIT License (MIT)
  8. *
  9. * Copyright (c) 2014 - 2019, British Columbia Institute of Technology
  10. *
  11. * Permission is hereby granted, free of charge, to any person obtaining a copy
  12. * of this software and associated documentation files (the "Software"), to deal
  13. * in the Software without restriction, including without limitation the rights
  14. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  15. * copies of the Software, and to permit persons to whom the Software is
  16. * furnished to do so, subject to the following conditions:
  17. *
  18. * The above copyright notice and this permission notice shall be included in
  19. * all copies or substantial portions of the Software.
  20. *
  21. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  22. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  23. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  24. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  25. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  26. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  27. * THE SOFTWARE.
  28. *
  29. * @package CodeIgniter
  30. * @author EllisLab Dev Team
  31. * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
  32. * @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/)
  33. * @license https://opensource.org/licenses/MIT MIT License
  34. * @link https://codeigniter.com
  35. * @since Version 3.0.0
  36. * @filesource
  37. */
  38. defined('BASEPATH') OR exit('No direct script access allowed');
  39. /**
  40. * CodeIgniter Session Files Driver
  41. *
  42. * @package CodeIgniter
  43. * @subpackage Libraries
  44. * @category Sessions
  45. * @author Andrey Andreev
  46. * @link https://codeigniter.com/userguide3/libraries/sessions.html
  47. */
  48. class CI_Session_files_driver extends CI_Session_driver implements SessionHandlerInterface {
  49. /**
  50. * Save path
  51. *
  52. * @var string
  53. */
  54. protected $_save_path;
  55. /**
  56. * File handle
  57. *
  58. * @var resource
  59. */
  60. protected $_file_handle;
  61. /**
  62. * File name
  63. *
  64. * @var resource
  65. */
  66. protected $_file_path;
  67. /**
  68. * File new flag
  69. *
  70. * @var bool
  71. */
  72. protected $_file_new;
  73. /**
  74. * Validate SID regular expression
  75. *
  76. * @var string
  77. */
  78. protected $_sid_regexp;
  79. /**
  80. * mbstring.func_overload flag
  81. *
  82. * @var bool
  83. */
  84. protected static $func_overload;
  85. // ------------------------------------------------------------------------
  86. /**
  87. * Class constructor
  88. *
  89. * @param array $params Configuration parameters
  90. * @return void
  91. */
  92. public function __construct(&$params)
  93. {
  94. parent::__construct($params);
  95. if (isset($this->_config['save_path']))
  96. {
  97. $this->_config['save_path'] = rtrim($this->_config['save_path'], '/\\');
  98. ini_set('session.save_path', $this->_config['save_path']);
  99. }
  100. else
  101. {
  102. log_message('debug', 'Session: "sess_save_path" is empty; using "session.save_path" value from php.ini.');
  103. $this->_config['save_path'] = rtrim(ini_get('session.save_path'), '/\\');
  104. }
  105. $this->_sid_regexp = $this->_config['_sid_regexp'];
  106. isset(self::$func_overload) OR self::$func_overload = (extension_loaded('mbstring') && ini_get('mbstring.func_overload'));
  107. }
  108. // ------------------------------------------------------------------------
  109. /**
  110. * Open
  111. *
  112. * Sanitizes the save_path directory.
  113. *
  114. * @param string $save_path Path to session files' directory
  115. * @param string $name Session cookie name
  116. * @return bool
  117. */
  118. public function open($save_path, $name)
  119. {
  120. if ( ! is_dir($save_path))
  121. {
  122. if ( ! mkdir($save_path, 0700, TRUE))
  123. {
  124. log_message('error', "Session: Configured save path '".$this->_config['save_path']."' is not a directory, doesn't exist or cannot be created.");
  125. return $this->_failure;
  126. }
  127. }
  128. elseif ( ! is_writable($save_path))
  129. {
  130. log_message('error', "Session: Configured save path '".$this->_config['save_path']."' is not writable by the PHP process.");
  131. return $this->_failure;
  132. }
  133. $this->_config['save_path'] = $save_path;
  134. $this->_file_path = $this->_config['save_path'].DIRECTORY_SEPARATOR
  135. .$name // we'll use the session cookie name as a prefix to avoid collisions
  136. .($this->_config['match_ip'] ? md5($_SERVER['REMOTE_ADDR']) : '');
  137. $this->php5_validate_id();
  138. return $this->_success;
  139. }
  140. // ------------------------------------------------------------------------
  141. /**
  142. * Read
  143. *
  144. * Reads session data and acquires a lock
  145. *
  146. * @param string $session_id Session ID
  147. * @return string Serialized session data
  148. */
  149. public function read($session_id)
  150. {
  151. // This might seem weird, but PHP 5.6 introduces session_reset(),
  152. // which re-reads session data
  153. if ($this->_file_handle === NULL)
  154. {
  155. $this->_file_new = ! file_exists($this->_file_path.$session_id);
  156. if (($this->_file_handle = fopen($this->_file_path.$session_id, 'c+b')) === FALSE)
  157. {
  158. log_message('error', "Session: Unable to open file '".$this->_file_path.$session_id."'.");
  159. return $this->_failure;
  160. }
  161. if (flock($this->_file_handle, LOCK_EX) === FALSE)
  162. {
  163. log_message('error', "Session: Unable to obtain lock for file '".$this->_file_path.$session_id."'.");
  164. fclose($this->_file_handle);
  165. $this->_file_handle = NULL;
  166. return $this->_failure;
  167. }
  168. // Needed by write() to detect session_regenerate_id() calls
  169. $this->_session_id = $session_id;
  170. if ($this->_file_new)
  171. {
  172. chmod($this->_file_path.$session_id, 0600);
  173. $this->_fingerprint = md5('');
  174. return '';
  175. }
  176. // Prevent possible data corruption
  177. // See https://github.com/bcit-ci/CodeIgniter/issues/5857
  178. clearstatcache(TRUE, $this->_file_path.$session_id);
  179. }
  180. // We shouldn't need this, but apparently we do ...
  181. // See https://github.com/bcit-ci/CodeIgniter/issues/4039
  182. elseif ($this->_file_handle === FALSE)
  183. {
  184. return $this->_failure;
  185. }
  186. else
  187. {
  188. rewind($this->_file_handle);
  189. }
  190. $session_data = '';
  191. for ($read = 0, $length = filesize($this->_file_path.$session_id); $read < $length; $read += self::strlen($buffer))
  192. {
  193. if (($buffer = fread($this->_file_handle, $length - $read)) === FALSE)
  194. {
  195. break;
  196. }
  197. $session_data .= $buffer;
  198. }
  199. $this->_fingerprint = md5($session_data);
  200. return $session_data;
  201. }
  202. // ------------------------------------------------------------------------
  203. /**
  204. * Write
  205. *
  206. * Writes (create / update) session data
  207. *
  208. * @param string $session_id Session ID
  209. * @param string $session_data Serialized session data
  210. * @return bool
  211. */
  212. public function write($session_id, $session_data)
  213. {
  214. // If the two IDs don't match, we have a session_regenerate_id() call
  215. // and we need to close the old handle and open a new one
  216. if ($session_id !== $this->_session_id && ($this->close() === $this->_failure OR $this->read($session_id) === $this->_failure))
  217. {
  218. return $this->_failure;
  219. }
  220. if ( ! is_resource($this->_file_handle))
  221. {
  222. return $this->_failure;
  223. }
  224. elseif ($this->_fingerprint === md5($session_data))
  225. {
  226. return ( ! $this->_file_new && ! touch($this->_file_path.$session_id))
  227. ? $this->_failure
  228. : $this->_success;
  229. }
  230. if ( ! $this->_file_new)
  231. {
  232. ftruncate($this->_file_handle, 0);
  233. rewind($this->_file_handle);
  234. }
  235. if (($length = strlen($session_data)) > 0)
  236. {
  237. for ($written = 0; $written < $length; $written += $result)
  238. {
  239. if (($result = fwrite($this->_file_handle, substr($session_data, $written))) === FALSE)
  240. {
  241. break;
  242. }
  243. }
  244. if ( ! is_int($result))
  245. {
  246. $this->_fingerprint = md5(substr($session_data, 0, $written));
  247. log_message('error', 'Session: Unable to write data.');
  248. return $this->_failure;
  249. }
  250. }
  251. $this->_fingerprint = md5($session_data);
  252. return $this->_success;
  253. }
  254. // ------------------------------------------------------------------------
  255. /**
  256. * Close
  257. *
  258. * Releases locks and closes file descriptor.
  259. *
  260. * @return bool
  261. */
  262. public function close()
  263. {
  264. if (is_resource($this->_file_handle))
  265. {
  266. flock($this->_file_handle, LOCK_UN);
  267. fclose($this->_file_handle);
  268. $this->_file_handle = $this->_file_new = $this->_session_id = NULL;
  269. }
  270. return $this->_success;
  271. }
  272. // ------------------------------------------------------------------------
  273. /**
  274. * Destroy
  275. *
  276. * Destroys the current session.
  277. *
  278. * @param string $session_id Session ID
  279. * @return bool
  280. */
  281. public function destroy($session_id)
  282. {
  283. if ($this->close() === $this->_success)
  284. {
  285. if (file_exists($this->_file_path.$session_id))
  286. {
  287. $this->_cookie_destroy();
  288. return unlink($this->_file_path.$session_id)
  289. ? $this->_success
  290. : $this->_failure;
  291. }
  292. return $this->_success;
  293. }
  294. elseif ($this->_file_path !== NULL)
  295. {
  296. clearstatcache();
  297. if (file_exists($this->_file_path.$session_id))
  298. {
  299. $this->_cookie_destroy();
  300. return unlink($this->_file_path.$session_id)
  301. ? $this->_success
  302. : $this->_failure;
  303. }
  304. return $this->_success;
  305. }
  306. return $this->_failure;
  307. }
  308. // ------------------------------------------------------------------------
  309. /**
  310. * Garbage Collector
  311. *
  312. * Deletes expired sessions
  313. *
  314. * @param int $maxlifetime Maximum lifetime of sessions
  315. * @return bool
  316. */
  317. public function gc($maxlifetime)
  318. {
  319. if ( ! is_dir($this->_config['save_path']) OR ($directory = opendir($this->_config['save_path'])) === FALSE)
  320. {
  321. log_message('debug', "Session: Garbage collector couldn't list files under directory '".$this->_config['save_path']."'.");
  322. return $this->_failure;
  323. }
  324. $ts = time() - $maxlifetime;
  325. $pattern = ($this->_config['match_ip'] === TRUE)
  326. ? '[0-9a-f]{32}'
  327. : '';
  328. $pattern = sprintf(
  329. '#\A%s'.$pattern.$this->_sid_regexp.'\z#',
  330. preg_quote($this->_config['cookie_name'])
  331. );
  332. while (($file = readdir($directory)) !== FALSE)
  333. {
  334. // If the filename doesn't match this pattern, it's either not a session file or is not ours
  335. if ( ! preg_match($pattern, $file)
  336. OR ! is_file($this->_config['save_path'].DIRECTORY_SEPARATOR.$file)
  337. OR ($mtime = filemtime($this->_config['save_path'].DIRECTORY_SEPARATOR.$file)) === FALSE
  338. OR $mtime > $ts)
  339. {
  340. continue;
  341. }
  342. unlink($this->_config['save_path'].DIRECTORY_SEPARATOR.$file);
  343. }
  344. closedir($directory);
  345. return $this->_success;
  346. }
  347. // --------------------------------------------------------------------
  348. /**
  349. * Validate ID
  350. *
  351. * Checks whether a session ID record exists server-side,
  352. * to enforce session.use_strict_mode.
  353. *
  354. * @param string $id
  355. * @return bool
  356. */
  357. public function validateSessionId($id)
  358. {
  359. $result = is_file($this->_file_path.$id);
  360. clearstatcache(TRUE, $this->_file_path.$id);
  361. return $result;
  362. }
  363. // --------------------------------------------------------------------
  364. /**
  365. * Byte-safe strlen()
  366. *
  367. * @param string $str
  368. * @return int
  369. */
  370. protected static function strlen($str)
  371. {
  372. return (self::$func_overload)
  373. ? mb_strlen($str, '8bit')
  374. : strlen($str);
  375. }
  376. }