blob: 654f3001009be1577f6e2d0d494135415402b1eb [file] [log] [blame]
Andrey Andreev05afe3e2015-02-02 19:04:37 +02001<?php
Andrey Andreev47a47fb2014-05-31 16:08:30 +03002/**
3 * CodeIgniter
4 *
Andrey Andreevbf6b11d2015-01-12 17:27:12 +02005 * An open source application development framework for PHP
Andrey Andreev47a47fb2014-05-31 16:08:30 +03006 *
Andrey Andreev46f2f262014-11-11 14:37:51 +02007 * This content is released under the MIT License (MIT)
Andrey Andreev47a47fb2014-05-31 16:08:30 +03008 *
Andrey Andreevcce6bd12018-01-09 11:32:02 +02009 * Copyright (c) 2014 - 2018, British Columbia Institute of Technology
Andrey Andreev47a47fb2014-05-31 16:08:30 +030010 *
Andrey Andreev46f2f262014-11-11 14:37:51 +020011 * 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:
Andrey Andreev47a47fb2014-05-31 16:08:30 +030017 *
Andrey Andreev46f2f262014-11-11 14:37:51 +020018 * 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
Andrey Andreev1924e872016-01-11 12:55:34 +020031 * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
Andrey Andreevcce6bd12018-01-09 11:32:02 +020032 * @copyright Copyright (c) 2014 - 2018, British Columbia Institute of Technology (http://bcit.ca/)
Andrey Andreev46f2f262014-11-11 14:37:51 +020033 * @license http://opensource.org/licenses/MIT MIT License
Andrey Andreevbd202c92016-01-11 12:50:18 +020034 * @link https://codeigniter.com
Andrey Andreev46f2f262014-11-11 14:37:51 +020035 * @since Version 3.0.0
Andrey Andreev47a47fb2014-05-31 16:08:30 +030036 * @filesource
Andrey Andreev46f2f262014-11-11 14:37:51 +020037*/
Andrey Andreev47a47fb2014-05-31 16:08:30 +030038defined('BASEPATH') OR exit('No direct script access allowed');
39
40/**
41 * CodeIgniter Session Files Driver
42 *
Andrey Andreev46f2f262014-11-11 14:37:51 +020043 * @package CodeIgniter
Andrey Andreev47a47fb2014-05-31 16:08:30 +030044 * @subpackage Libraries
45 * @category Sessions
Andrey Andreev46f2f262014-11-11 14:37:51 +020046 * @author Andrey Andreev
Andrey Andreevbd202c92016-01-11 12:50:18 +020047 * @link https://codeigniter.com/user_guide/libraries/sessions.html
Andrey Andreev47a47fb2014-05-31 16:08:30 +030048 */
49class CI_Session_files_driver extends CI_Session_driver implements SessionHandlerInterface {
50
51 /**
52 * Save path
53 *
54 * @var string
55 */
56 protected $_save_path;
57
58 /**
59 * File handle
60 *
61 * @var resource
62 */
63 protected $_file_handle;
64
65 /**
66 * File name
67 *
68 * @var resource
69 */
70 protected $_file_path;
71
72 /**
73 * File new flag
74 *
75 * @var bool
76 */
77 protected $_file_new;
78
Andrey Andreev103a4262016-10-03 11:19:11 +030079 /**
Andrey Andreev6c6ee1a2016-10-22 16:33:06 +030080 * Validate SID regular expression
81 *
82 * @var string
83 */
84 protected $_sid_regexp;
85
86 /**
Andrey Andreevc0c74d52017-01-19 15:26:35 +020087 * mbstring.func_overload flag
Andrey Andreev103a4262016-10-03 11:19:11 +030088 *
89 * @var bool
90 */
Andrey Andreevc0c74d52017-01-19 15:26:35 +020091 protected static $func_overload;
Andrey Andreev103a4262016-10-03 11:19:11 +030092
Andrey Andreev47a47fb2014-05-31 16:08:30 +030093 // ------------------------------------------------------------------------
94
95 /**
96 * Class constructor
97 *
98 * @param array $params Configuration parameters
99 * @return void
100 */
101 public function __construct(&$params)
102 {
103 parent::__construct($params);
104
Andrey Andreevdfb39be2014-10-06 01:50:14 +0300105 if (isset($this->_config['save_path']))
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300106 {
Andrey Andreevdfb39be2014-10-06 01:50:14 +0300107 $this->_config['save_path'] = rtrim($this->_config['save_path'], '/\\');
108 ini_set('session.save_path', $this->_config['save_path']);
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300109 }
110 else
111 {
Andrey Andreev85dfc2a2016-04-01 22:54:15 +0300112 log_message('debug', 'Session: "sess_save_path" is empty; using "session.save_path" value from php.ini.');
Andrey Andreevdfb39be2014-10-06 01:50:14 +0300113 $this->_config['save_path'] = rtrim(ini_get('session.save_path'), '/\\');
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300114 }
Andrey Andreev103a4262016-10-03 11:19:11 +0300115
Andrey Andreev6c6ee1a2016-10-22 16:33:06 +0300116 $this->_sid_regexp = $this->_config['_sid_regexp'];
117
Andrey Andreevc0c74d52017-01-19 15:26:35 +0200118 isset(self::$func_overload) OR self::$func_overload = (extension_loaded('mbstring') && ini_get('mbstring.func_overload'));
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300119 }
120
121 // ------------------------------------------------------------------------
122
Andrey Andreev10411fc2015-01-19 13:54:53 +0200123 /**
124 * Open
125 *
126 * Sanitizes the save_path directory.
127 *
128 * @param string $save_path Path to session files' directory
Tom Atkinson388ce592015-02-04 17:54:52 +0100129 * @param string $name Session cookie name
Andrey Andreev10411fc2015-01-19 13:54:53 +0200130 * @return bool
131 */
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300132 public function open($save_path, $name)
133 {
Andrey Andreev5f4d01a2015-02-02 18:38:00 +0200134 if ( ! is_dir($save_path))
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300135 {
Andrey Andreev5f4d01a2015-02-02 18:38:00 +0200136 if ( ! mkdir($save_path, 0700, TRUE))
137 {
138 throw new Exception("Session: Configured save path '".$this->_config['save_path']."' is not a directory, doesn't exist or cannot be created.");
139 }
140 }
141 elseif ( ! is_writable($save_path))
142 {
143 throw new Exception("Session: Configured save path '".$this->_config['save_path']."' is not writable by the PHP process.");
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300144 }
145
Andrey Andreevdfb39be2014-10-06 01:50:14 +0300146 $this->_config['save_path'] = $save_path;
147 $this->_file_path = $this->_config['save_path'].DIRECTORY_SEPARATOR
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300148 .$name // we'll use the session cookie name as a prefix to avoid collisions
Andrey Andreevdfb39be2014-10-06 01:50:14 +0300149 .($this->_config['match_ip'] ? md5($_SERVER['REMOTE_ADDR']) : '');
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300150
Andrey Andreeva9da3dd2018-06-12 16:40:12 +0300151 $this->php5_validate_id();
152
Andrey Andreevaf849692015-12-12 14:07:39 +0200153 return $this->_success;
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300154 }
155
156 // ------------------------------------------------------------------------
157
Andrey Andreev10411fc2015-01-19 13:54:53 +0200158 /**
159 * Read
160 *
161 * Reads session data and acquires a lock
162 *
163 * @param string $session_id Session ID
164 * @return string Serialized session data
165 */
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300166 public function read($session_id)
167 {
168 // This might seem weird, but PHP 5.6 introduces session_reset(),
169 // which re-reads session data
170 if ($this->_file_handle === NULL)
171 {
Andrey Andreeva8382792016-07-28 16:40:12 +0300172 $this->_file_new = ! file_exists($this->_file_path.$session_id);
173
174 if (($this->_file_handle = fopen($this->_file_path.$session_id, 'c+b')) === FALSE)
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300175 {
Andrey Andreev7474a672014-10-31 23:35:32 +0200176 log_message('error', "Session: Unable to open file '".$this->_file_path.$session_id."'.");
Andrey Andreevaf849692015-12-12 14:07:39 +0200177 return $this->_failure;
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300178 }
179
180 if (flock($this->_file_handle, LOCK_EX) === FALSE)
181 {
Andrey Andreev7474a672014-10-31 23:35:32 +0200182 log_message('error', "Session: Unable to obtain lock for file '".$this->_file_path.$session_id."'.");
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300183 fclose($this->_file_handle);
184 $this->_file_handle = NULL;
Andrey Andreevaf849692015-12-12 14:07:39 +0200185 return $this->_failure;
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300186 }
187
Andrey Andreev7474a672014-10-31 23:35:32 +0200188 // Needed by write() to detect session_regenerate_id() calls
189 $this->_session_id = $session_id;
190
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300191 if ($this->_file_new)
192 {
Andrey Andreev7474a672014-10-31 23:35:32 +0200193 chmod($this->_file_path.$session_id, 0600);
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300194 $this->_fingerprint = md5('');
195 return '';
196 }
197 }
Andrey Andreev8df6efd2015-12-11 17:55:55 +0200198 // We shouldn't need this, but apparently we do ...
199 // See https://github.com/bcit-ci/CodeIgniter/issues/4039
Andrey Andreev2d6d9ab2015-12-15 12:32:50 +0200200 elseif ($this->_file_handle === FALSE)
Andrey Andreev8df6efd2015-12-11 17:55:55 +0200201 {
Andrey Andreevaf849692015-12-12 14:07:39 +0200202 return $this->_failure;
Andrey Andreev8df6efd2015-12-11 17:55:55 +0200203 }
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300204 else
205 {
206 rewind($this->_file_handle);
207 }
208
209 $session_data = '';
Andrey Andreev103a4262016-10-03 11:19:11 +0300210 for ($read = 0, $length = filesize($this->_file_path.$session_id); $read < $length; $read += self::strlen($buffer))
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300211 {
212 if (($buffer = fread($this->_file_handle, $length - $read)) === FALSE)
213 {
214 break;
215 }
216
217 $session_data .= $buffer;
218 }
219
220 $this->_fingerprint = md5($session_data);
221 return $session_data;
222 }
223
Andrey Andreev10411fc2015-01-19 13:54:53 +0200224 // ------------------------------------------------------------------------
225
226 /**
227 * Write
228 *
229 * Writes (create / update) session data
230 *
231 * @param string $session_id Session ID
232 * @param string $session_data Serialized session data
233 * @return bool
234 */
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300235 public function write($session_id, $session_data)
236 {
Andrey Andreev7474a672014-10-31 23:35:32 +0200237 // If the two IDs don't match, we have a session_regenerate_id() call
238 // and we need to close the old handle and open a new one
Andrey Andreevbb71dba2015-12-15 13:00:52 +0200239 if ($session_id !== $this->_session_id && ($this->close() === $this->_failure OR $this->read($session_id) === $this->_failure))
Andrey Andreev7474a672014-10-31 23:35:32 +0200240 {
Andrey Andreevaf849692015-12-12 14:07:39 +0200241 return $this->_failure;
Andrey Andreev7474a672014-10-31 23:35:32 +0200242 }
243
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300244 if ( ! is_resource($this->_file_handle))
245 {
Andrey Andreevaf849692015-12-12 14:07:39 +0200246 return $this->_failure;
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300247 }
248 elseif ($this->_fingerprint === md5($session_data))
249 {
Andrey Andreevaf849692015-12-12 14:07:39 +0200250 return ( ! $this->_file_new && ! touch($this->_file_path.$session_id))
251 ? $this->_failure
252 : $this->_success;
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300253 }
254
255 if ( ! $this->_file_new)
256 {
257 ftruncate($this->_file_handle, 0);
258 rewind($this->_file_handle);
259 }
260
Andrey Andreev5995e082014-06-03 15:33:51 +0300261 if (($length = strlen($session_data)) > 0)
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300262 {
Andrey Andreev5995e082014-06-03 15:33:51 +0300263 for ($written = 0; $written < $length; $written += $result)
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300264 {
Andrey Andreev5995e082014-06-03 15:33:51 +0300265 if (($result = fwrite($this->_file_handle, substr($session_data, $written))) === FALSE)
266 {
267 break;
268 }
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300269 }
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300270
Andrey Andreev5995e082014-06-03 15:33:51 +0300271 if ( ! is_int($result))
272 {
273 $this->_fingerprint = md5(substr($session_data, 0, $written));
274 log_message('error', 'Session: Unable to write data.');
Andrey Andreevaf849692015-12-12 14:07:39 +0200275 return $this->_failure;
Andrey Andreev5995e082014-06-03 15:33:51 +0300276 }
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300277 }
278
279 $this->_fingerprint = md5($session_data);
Andrey Andreevaf849692015-12-12 14:07:39 +0200280 return $this->_success;
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300281 }
282
283 // ------------------------------------------------------------------------
284
Andrey Andreev10411fc2015-01-19 13:54:53 +0200285 /**
286 * Close
287 *
288 * Releases locks and closes file descriptor.
289 *
Gabriel Potkány1fb50002015-02-04 01:45:59 +0100290 * @return bool
Andrey Andreev10411fc2015-01-19 13:54:53 +0200291 */
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300292 public function close()
293 {
294 if (is_resource($this->_file_handle))
295 {
296 flock($this->_file_handle, LOCK_UN);
297 fclose($this->_file_handle);
298
Andrey Andreev7474a672014-10-31 23:35:32 +0200299 $this->_file_handle = $this->_file_new = $this->_session_id = NULL;
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300300 }
301
Andrey Andreevaf849692015-12-12 14:07:39 +0200302 return $this->_success;
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300303 }
304
305 // ------------------------------------------------------------------------
306
Andrey Andreev10411fc2015-01-19 13:54:53 +0200307 /**
308 * Destroy
309 *
310 * Destroys the current session.
311 *
312 * @param string $session_id Session ID
313 * @return bool
314 */
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300315 public function destroy($session_id)
316 {
Andrey Andreevbb71dba2015-12-15 13:00:52 +0200317 if ($this->close() === $this->_success)
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300318 {
Andrey Andreevaf849692015-12-12 14:07:39 +0200319 if (file_exists($this->_file_path.$session_id))
320 {
321 $this->_cookie_destroy();
322 return unlink($this->_file_path.$session_id)
323 ? $this->_success
324 : $this->_failure;
325 }
326
327 return $this->_success;
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300328 }
329 elseif ($this->_file_path !== NULL)
330 {
331 clearstatcache();
Andrey Andreevaf849692015-12-12 14:07:39 +0200332 if (file_exists($this->_file_path.$session_id))
333 {
334 $this->_cookie_destroy();
335 return unlink($this->_file_path.$session_id)
336 ? $this->_success
337 : $this->_failure;
338 }
339
340 return $this->_success;
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300341 }
342
Andrey Andreevaf849692015-12-12 14:07:39 +0200343 return $this->_failure;
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300344 }
345
346 // ------------------------------------------------------------------------
347
Andrey Andreev10411fc2015-01-19 13:54:53 +0200348 /**
349 * Garbage Collector
350 *
351 * Deletes expired sessions
352 *
353 * @param int $maxlifetime Maximum lifetime of sessions
354 * @return bool
355 */
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300356 public function gc($maxlifetime)
357 {
Andrey Andreev2f79f9a2015-03-26 12:52:05 +0200358 if ( ! is_dir($this->_config['save_path']) OR ($directory = opendir($this->_config['save_path'])) === FALSE)
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300359 {
Andrey Andreevdfb39be2014-10-06 01:50:14 +0300360 log_message('debug', "Session: Garbage collector couldn't list files under directory '".$this->_config['save_path']."'.");
Andrey Andreevaf849692015-12-12 14:07:39 +0200361 return $this->_failure;
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300362 }
363
364 $ts = time() - $maxlifetime;
365
Andrey Andreev6c6ee1a2016-10-22 16:33:06 +0300366 $pattern = ($this->_config['match_ip'] === TRUE)
367 ? '[0-9a-f]{32}'
368 : '';
369
Tom Atkinson388ce592015-02-04 17:54:52 +0100370 $pattern = sprintf(
Andrey Andreev6c6ee1a2016-10-22 16:33:06 +0300371 '#\A%s'.$pattern.$this->_sid_regexp.'\z#',
372 preg_quote($this->_config['cookie_name'])
Tom Atkinson388ce592015-02-04 17:54:52 +0100373 );
374
Andrey Andreev2f79f9a2015-03-26 12:52:05 +0200375 while (($file = readdir($directory)) !== FALSE)
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300376 {
377 // If the filename doesn't match this pattern, it's either not a session file or is not ours
Tom Atkinson388ce592015-02-04 17:54:52 +0100378 if ( ! preg_match($pattern, $file)
Andrey Andreevdfb39be2014-10-06 01:50:14 +0300379 OR ! is_file($this->_config['save_path'].DIRECTORY_SEPARATOR.$file)
Andrey Andreevcd489612014-10-27 16:09:01 +0200380 OR ($mtime = filemtime($this->_config['save_path'].DIRECTORY_SEPARATOR.$file)) === FALSE
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300381 OR $mtime > $ts)
382 {
383 continue;
384 }
385
Andrey Andreevdfb39be2014-10-06 01:50:14 +0300386 unlink($this->_config['save_path'].DIRECTORY_SEPARATOR.$file);
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300387 }
388
Andrey Andreev2f79f9a2015-03-26 12:52:05 +0200389 closedir($directory);
390
Andrey Andreevaf849692015-12-12 14:07:39 +0200391 return $this->_success;
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300392 }
393
Andrey Andreev103a4262016-10-03 11:19:11 +0300394 // --------------------------------------------------------------------
395
396 /**
Andrey Andreeva9da3dd2018-06-12 16:40:12 +0300397 * Validate ID
398 *
399 * Checks whether a session ID record exists server-side,
400 * to enforce session.use_strict_mode.
401 *
402 * @param string $id
403 * @return bool
404 */
405 public function validateId($id)
406 {
407 return is_file($this->_file_path.$id);
408 }
409
410 // --------------------------------------------------------------------
411
412 /**
Andrey Andreev103a4262016-10-03 11:19:11 +0300413 * Byte-safe strlen()
414 *
415 * @param string $str
416 * @return int
417 */
418 protected static function strlen($str)
419 {
Andrey Andreevc0c74d52017-01-19 15:26:35 +0200420 return (self::$func_overload)
Andrey Andreev103a4262016-10-03 11:19:11 +0300421 ? mb_strlen($str, '8bit')
422 : strlen($str);
423 }
424}