blob: 1a943d5c921a21fb5c29aeda4763bd61e8074d8f [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 Andreevbf6b11d2015-01-12 17:27:12 +02009 * Copyright (c) 2014 - 2015, 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 Andreev47a47fb2014-05-31 16:08:30 +030031 * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (http://ellislab.com/)
Andrey Andreevbf6b11d2015-01-12 17:27:12 +020032 * @copyright Copyright (c) 2014 - 2015, British Columbia Institute of Technology (http://bcit.ca/)
Andrey Andreev46f2f262014-11-11 14:37:51 +020033 * @license http://opensource.org/licenses/MIT MIT License
34 * @link http://codeigniter.com
35 * @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
47 * @link http://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
79 // ------------------------------------------------------------------------
80
81 /**
82 * Class constructor
83 *
84 * @param array $params Configuration parameters
85 * @return void
86 */
87 public function __construct(&$params)
88 {
89 parent::__construct($params);
90
Andrey Andreevdfb39be2014-10-06 01:50:14 +030091 if (isset($this->_config['save_path']))
Andrey Andreev47a47fb2014-05-31 16:08:30 +030092 {
Andrey Andreevdfb39be2014-10-06 01:50:14 +030093 $this->_config['save_path'] = rtrim($this->_config['save_path'], '/\\');
94 ini_set('session.save_path', $this->_config['save_path']);
Andrey Andreev47a47fb2014-05-31 16:08:30 +030095 }
96 else
97 {
Andrey Andreevdfb39be2014-10-06 01:50:14 +030098 $this->_config['save_path'] = rtrim(ini_get('session.save_path'), '/\\');
Andrey Andreev47a47fb2014-05-31 16:08:30 +030099 }
100 }
101
102 // ------------------------------------------------------------------------
103
Andrey Andreev10411fc2015-01-19 13:54:53 +0200104 /**
105 * Open
106 *
107 * Sanitizes the save_path directory.
108 *
109 * @param string $save_path Path to session files' directory
Tom Atkinson388ce592015-02-04 17:54:52 +0100110 * @param string $name Session cookie name
Andrey Andreev10411fc2015-01-19 13:54:53 +0200111 * @return bool
112 */
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300113 public function open($save_path, $name)
114 {
Andrey Andreev5f4d01a2015-02-02 18:38:00 +0200115 if ( ! is_dir($save_path))
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300116 {
Andrey Andreev5f4d01a2015-02-02 18:38:00 +0200117 if ( ! mkdir($save_path, 0700, TRUE))
118 {
119 throw new Exception("Session: Configured save path '".$this->_config['save_path']."' is not a directory, doesn't exist or cannot be created.");
120 }
121 }
122 elseif ( ! is_writable($save_path))
123 {
124 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 +0300125 }
126
Andrey Andreevdfb39be2014-10-06 01:50:14 +0300127 $this->_config['save_path'] = $save_path;
128 $this->_file_path = $this->_config['save_path'].DIRECTORY_SEPARATOR
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300129 .$name // we'll use the session cookie name as a prefix to avoid collisions
Andrey Andreevdfb39be2014-10-06 01:50:14 +0300130 .($this->_config['match_ip'] ? md5($_SERVER['REMOTE_ADDR']) : '');
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300131
Andrey Andreevaf849692015-12-12 14:07:39 +0200132 return $this->_success;
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300133 }
134
135 // ------------------------------------------------------------------------
136
Andrey Andreev10411fc2015-01-19 13:54:53 +0200137 /**
138 * Read
139 *
140 * Reads session data and acquires a lock
141 *
142 * @param string $session_id Session ID
143 * @return string Serialized session data
144 */
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300145 public function read($session_id)
146 {
147 // This might seem weird, but PHP 5.6 introduces session_reset(),
148 // which re-reads session data
149 if ($this->_file_handle === NULL)
150 {
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300151 // Just using fopen() with 'c+b' mode would be perfect, but it is only
152 // available since PHP 5.2.6 and we have to set permissions for new files,
153 // so we'd have to hack around this ...
Andrey Andreev7474a672014-10-31 23:35:32 +0200154 if (($this->_file_new = ! file_exists($this->_file_path.$session_id)) === TRUE)
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300155 {
Andrey Andreev7474a672014-10-31 23:35:32 +0200156 if (($this->_file_handle = fopen($this->_file_path.$session_id, 'w+b')) === FALSE)
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300157 {
Andrey Andreev7474a672014-10-31 23:35:32 +0200158 log_message('error', "Session: File '".$this->_file_path.$session_id."' doesn't exist and cannot be created.");
Andrey Andreevaf849692015-12-12 14:07:39 +0200159 return $this->_failure;
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300160 }
161 }
Andrey Andreev7474a672014-10-31 23:35:32 +0200162 elseif (($this->_file_handle = fopen($this->_file_path.$session_id, 'r+b')) === FALSE)
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300163 {
Andrey Andreev7474a672014-10-31 23:35:32 +0200164 log_message('error', "Session: Unable to open file '".$this->_file_path.$session_id."'.");
Andrey Andreevaf849692015-12-12 14:07:39 +0200165 return $this->_failure;
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300166 }
167
168 if (flock($this->_file_handle, LOCK_EX) === FALSE)
169 {
Andrey Andreev7474a672014-10-31 23:35:32 +0200170 log_message('error', "Session: Unable to obtain lock for file '".$this->_file_path.$session_id."'.");
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300171 fclose($this->_file_handle);
172 $this->_file_handle = NULL;
Andrey Andreevaf849692015-12-12 14:07:39 +0200173 return $this->_failure;
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300174 }
175
Andrey Andreev7474a672014-10-31 23:35:32 +0200176 // Needed by write() to detect session_regenerate_id() calls
177 $this->_session_id = $session_id;
178
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300179 if ($this->_file_new)
180 {
Andrey Andreev7474a672014-10-31 23:35:32 +0200181 chmod($this->_file_path.$session_id, 0600);
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300182 $this->_fingerprint = md5('');
183 return '';
184 }
185 }
Andrey Andreev8df6efd2015-12-11 17:55:55 +0200186 // We shouldn't need this, but apparently we do ...
187 // See https://github.com/bcit-ci/CodeIgniter/issues/4039
Andrey Andreev2d6d9ab2015-12-15 12:32:50 +0200188 elseif ($this->_file_handle === FALSE)
Andrey Andreev8df6efd2015-12-11 17:55:55 +0200189 {
Andrey Andreevaf849692015-12-12 14:07:39 +0200190 return $this->_failure;
Andrey Andreev8df6efd2015-12-11 17:55:55 +0200191 }
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300192 else
193 {
194 rewind($this->_file_handle);
195 }
196
197 $session_data = '';
Andrey Andreev7474a672014-10-31 23:35:32 +0200198 for ($read = 0, $length = filesize($this->_file_path.$session_id); $read < $length; $read += strlen($buffer))
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300199 {
200 if (($buffer = fread($this->_file_handle, $length - $read)) === FALSE)
201 {
202 break;
203 }
204
205 $session_data .= $buffer;
206 }
207
208 $this->_fingerprint = md5($session_data);
209 return $session_data;
210 }
211
Andrey Andreev10411fc2015-01-19 13:54:53 +0200212 // ------------------------------------------------------------------------
213
214 /**
215 * Write
216 *
217 * Writes (create / update) session data
218 *
219 * @param string $session_id Session ID
220 * @param string $session_data Serialized session data
221 * @return bool
222 */
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300223 public function write($session_id, $session_data)
224 {
Andrey Andreev7474a672014-10-31 23:35:32 +0200225 // If the two IDs don't match, we have a session_regenerate_id() call
226 // and we need to close the old handle and open a new one
227 if ($session_id !== $this->_session_id && ( ! $this->close() OR $this->read($session_id) === FALSE))
228 {
Andrey Andreevaf849692015-12-12 14:07:39 +0200229 return $this->_failure;
Andrey Andreev7474a672014-10-31 23:35:32 +0200230 }
231
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300232 if ( ! is_resource($this->_file_handle))
233 {
Andrey Andreevaf849692015-12-12 14:07:39 +0200234 return $this->_failure;
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300235 }
236 elseif ($this->_fingerprint === md5($session_data))
237 {
Andrey Andreevaf849692015-12-12 14:07:39 +0200238 return ( ! $this->_file_new && ! touch($this->_file_path.$session_id))
239 ? $this->_failure
240 : $this->_success;
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300241 }
242
243 if ( ! $this->_file_new)
244 {
245 ftruncate($this->_file_handle, 0);
246 rewind($this->_file_handle);
247 }
248
Andrey Andreev5995e082014-06-03 15:33:51 +0300249 if (($length = strlen($session_data)) > 0)
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300250 {
Andrey Andreev5995e082014-06-03 15:33:51 +0300251 for ($written = 0; $written < $length; $written += $result)
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300252 {
Andrey Andreev5995e082014-06-03 15:33:51 +0300253 if (($result = fwrite($this->_file_handle, substr($session_data, $written))) === FALSE)
254 {
255 break;
256 }
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300257 }
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300258
Andrey Andreev5995e082014-06-03 15:33:51 +0300259 if ( ! is_int($result))
260 {
261 $this->_fingerprint = md5(substr($session_data, 0, $written));
262 log_message('error', 'Session: Unable to write data.');
Andrey Andreevaf849692015-12-12 14:07:39 +0200263 return $this->_failure;
Andrey Andreev5995e082014-06-03 15:33:51 +0300264 }
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300265 }
266
267 $this->_fingerprint = md5($session_data);
Andrey Andreevaf849692015-12-12 14:07:39 +0200268 return $this->_success;
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300269 }
270
271 // ------------------------------------------------------------------------
272
Andrey Andreev10411fc2015-01-19 13:54:53 +0200273 /**
274 * Close
275 *
276 * Releases locks and closes file descriptor.
277 *
Gabriel Potkány1fb50002015-02-04 01:45:59 +0100278 * @return bool
Andrey Andreev10411fc2015-01-19 13:54:53 +0200279 */
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300280 public function close()
281 {
282 if (is_resource($this->_file_handle))
283 {
284 flock($this->_file_handle, LOCK_UN);
285 fclose($this->_file_handle);
286
Andrey Andreev7474a672014-10-31 23:35:32 +0200287 $this->_file_handle = $this->_file_new = $this->_session_id = NULL;
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300288 }
289
Andrey Andreevaf849692015-12-12 14:07:39 +0200290 return $this->_success;
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300291 }
292
293 // ------------------------------------------------------------------------
294
Andrey Andreev10411fc2015-01-19 13:54:53 +0200295 /**
296 * Destroy
297 *
298 * Destroys the current session.
299 *
300 * @param string $session_id Session ID
301 * @return bool
302 */
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300303 public function destroy($session_id)
304 {
305 if ($this->close())
306 {
Andrey Andreevaf849692015-12-12 14:07:39 +0200307 if (file_exists($this->_file_path.$session_id))
308 {
309 $this->_cookie_destroy();
310 return unlink($this->_file_path.$session_id)
311 ? $this->_success
312 : $this->_failure;
313 }
314
315 return $this->_success;
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300316 }
317 elseif ($this->_file_path !== NULL)
318 {
319 clearstatcache();
Andrey Andreevaf849692015-12-12 14:07:39 +0200320 if (file_exists($this->_file_path.$session_id))
321 {
322 $this->_cookie_destroy();
323 return unlink($this->_file_path.$session_id)
324 ? $this->_success
325 : $this->_failure;
326 }
327
328 return $this->_success;
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300329 }
330
Andrey Andreevaf849692015-12-12 14:07:39 +0200331 return $this->_failure;
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300332 }
333
334 // ------------------------------------------------------------------------
335
Andrey Andreev10411fc2015-01-19 13:54:53 +0200336 /**
337 * Garbage Collector
338 *
339 * Deletes expired sessions
340 *
341 * @param int $maxlifetime Maximum lifetime of sessions
342 * @return bool
343 */
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300344 public function gc($maxlifetime)
345 {
Andrey Andreev2f79f9a2015-03-26 12:52:05 +0200346 if ( ! is_dir($this->_config['save_path']) OR ($directory = opendir($this->_config['save_path'])) === FALSE)
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300347 {
Andrey Andreevdfb39be2014-10-06 01:50:14 +0300348 log_message('debug', "Session: Garbage collector couldn't list files under directory '".$this->_config['save_path']."'.");
Andrey Andreevaf849692015-12-12 14:07:39 +0200349 return $this->_failure;
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300350 }
351
352 $ts = time() - $maxlifetime;
353
Tom Atkinson388ce592015-02-04 17:54:52 +0100354 $pattern = sprintf(
355 '/^%s[0-9a-f]{%d}$/',
356 preg_quote($this->_config['cookie_name'], '/'),
357 ($this->_config['match_ip'] === TRUE ? 72 : 40)
358 );
359
Andrey Andreev2f79f9a2015-03-26 12:52:05 +0200360 while (($file = readdir($directory)) !== FALSE)
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300361 {
362 // 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 +0100363 if ( ! preg_match($pattern, $file)
Andrey Andreevdfb39be2014-10-06 01:50:14 +0300364 OR ! is_file($this->_config['save_path'].DIRECTORY_SEPARATOR.$file)
Andrey Andreevcd489612014-10-27 16:09:01 +0200365 OR ($mtime = filemtime($this->_config['save_path'].DIRECTORY_SEPARATOR.$file)) === FALSE
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300366 OR $mtime > $ts)
367 {
368 continue;
369 }
370
Andrey Andreevdfb39be2014-10-06 01:50:14 +0300371 unlink($this->_config['save_path'].DIRECTORY_SEPARATOR.$file);
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300372 }
373
Andrey Andreev2f79f9a2015-03-26 12:52:05 +0200374 closedir($directory);
375
Andrey Andreevaf849692015-12-12 14:07:39 +0200376 return $this->_success;
Andrey Andreev47a47fb2014-05-31 16:08:30 +0300377 }
378
Andrey Andreevaf849692015-12-12 14:07:39 +0200379}