blob: 696105bc60fd4c18aa42ac34db62dbdc1d211aa6 [file] [log] [blame]
Darren Hillc4e266b2011-08-30 15:40:27 -04001<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
2/**
3 * CodeIgniter
4 *
5 * An open source application development framework for PHP 5.1.6 or newer
6 *
7 * @package CodeIgniter
8 * @author ExpressionEngine Dev Team
9 * @copyright Copyright (c) 2008 - 2010, EllisLab, Inc.
10 * @license http://codeigniter.com/user_guide/license.html
11 * @link http://codeigniter.com
12 * @since Version 2.0
13 * @filesource
14 */
15
16
17/**
18 * Cookie-based session management driver
19 *
20 * This is the CI_Session functionality, as written by EllisLab, abstracted out to a driver.
21 * I have done a little updating for PHP5, and made minor changes to extract this functionality from
22 * the public interface (now in the Session Library), but effectively this code is unchanged.
23 *
24 * @package CodeIgniter
25 * @subpackage Libraries
26 * @category Sessions
Darren Hill5073a372011-08-31 13:54:19 -040027 * @author ExpressionEngine Dev Team
Darren Hillc4e266b2011-08-30 15:40:27 -040028 */
Darren Hill5073a372011-08-31 13:54:19 -040029class CI_Session_cookie extends CI_Session_driver {
Darren Hilla2ae6572011-09-01 07:36:26 -040030 protected $sess_encrypt_cookie = FALSE;
Darren Hill00fcb542011-09-12 07:57:04 -040031 protected $sess_use_database = FALSE;
Darren Hilla2ae6572011-09-01 07:36:26 -040032 protected $sess_table_name = '';
33 protected $sess_expiration = 7200;
34 protected $sess_expire_on_close = FALSE;
Darren Hill00fcb542011-09-12 07:57:04 -040035 protected $sess_match_ip = FALSE;
Darren Hilla2ae6572011-09-01 07:36:26 -040036 protected $sess_match_useragent = TRUE;
37 protected $sess_cookie_name = 'ci_session';
Darren Hill00fcb542011-09-12 07:57:04 -040038 protected $cookie_prefix = '';
Darren Hilla2ae6572011-09-01 07:36:26 -040039 protected $cookie_path = '';
Darren Hill00fcb542011-09-12 07:57:04 -040040 protected $cookie_domain = '';
41 protected $cookie_secure = FALSE;
Darren Hilla2ae6572011-09-01 07:36:26 -040042 protected $sess_time_to_update = 300;
Darren Hill00fcb542011-09-12 07:57:04 -040043 protected $encryption_key = '';
44 protected $time_reference = 'time';
Darren Hilla2ae6572011-09-01 07:36:26 -040045 protected $userdata = array();
Darren Hill00fcb542011-09-12 07:57:04 -040046 protected $CI = null;
Darren Hilla2ae6572011-09-01 07:36:26 -040047 protected $now = 0;
Darren Hillc4e266b2011-08-30 15:40:27 -040048
49 const gc_probability = 5;
50
51 /**
52 * Initialize session driver object
53 *
54 * @access protected
55 * @return void
56 */
57 protected function initialize()
58 {
59 // Set the super object to a local variable for use throughout the class
60 $this->CI =& get_instance();
61
62 // Set all the session preferences, which can either be set
63 // manually via the $params array above or via the config file
64 foreach (array('sess_encrypt_cookie', 'sess_use_database', 'sess_table_name', 'sess_expiration',
65 'sess_expire_on_close', 'sess_match_ip', 'sess_match_useragent', 'sess_cookie_name', 'cookie_path',
Darren Hill00fcb542011-09-12 07:57:04 -040066 'cookie_domain', 'cookie_secure', 'sess_time_to_update', 'time_reference', 'cookie_prefix', 'encryption_key')
67 as $key)
Darren Hillc4e266b2011-08-30 15:40:27 -040068 {
69 $this->$key = (isset($this->parent->params[$key])) ? $this->parent->params[$key] : $this->CI->config->item($key);
70 }
71
72 if ($this->encryption_key == '')
73 {
74 show_error('In order to use the Cookie Session driver you are required to set an encryption key '.
75 'in your config file.');
76 }
77
78 // Load the string helper so we can use the strip_slashes() function
79 $this->CI->load->helper('string');
80
81 // Do we need encryption? If so, load the encryption class
82 if ($this->sess_encrypt_cookie == TRUE)
83 {
84 $this->CI->load->library('encrypt');
85 }
86
87 // Are we using a database? If so, load it
88 if ($this->sess_use_database === TRUE && $this->sess_table_name != '')
89 {
90 $this->CI->load->database();
91 }
92
93 // Set the "now" time. Can either be GMT or server time, based on the config prefs.
94 // We use this to set the "last activity" time
95 $this->now = $this->_get_time();
96
97 // Set the session length. If the session expiration is
98 // set to zero we'll set the expiration two years from now.
99 if ($this->sess_expiration == 0)
100 {
101 $this->sess_expiration = (60*60*24*365*2);
102 }
103
104 // Set the cookie name
105 $this->sess_cookie_name = $this->cookie_prefix.$this->sess_cookie_name;
106
107 // Run the Session routine. If a session doesn't exist we'll
108 // create a new one. If it does, we'll update it.
109 if ( ! $this->_sess_read())
110 {
111 $this->_sess_create();
112 }
113 else
114 {
115 $this->_sess_update();
116 }
117
118 // Delete expired sessions if necessary
119 $this->_sess_gc();
120 }
121
122 /**
123 * Write the session data
124 *
125 * @return void
126 */
127 public function sess_save()
128 {
129 // Are we saving custom data to the DB? If not, all we do is update the cookie
130 if ($this->sess_use_database === FALSE)
131 {
132 $this->_set_cookie();
133 return;
134 }
135
136 // set the custom userdata, the session data we will set in a second
137 $custom_userdata = $this->all_userdata();
138 $cookie_userdata = array();
139
140 // Before continuing, we need to determine if there is any custom data to deal with.
141 // Let's determine this by removing the default indexes to see if there's anything left in the array
142 // and set the session data while we're at it
143 foreach (array('session_id','ip_address','user_agent','last_activity') as $val)
144 {
145 unset($custom_userdata[$val]);
146 $cookie_userdata[$val] = $this->userdata($val);
147 }
148
149 // Did we find any custom data? If not, we turn the empty array into a string
150 // since there's no reason to serialize and store an empty array in the DB
151 if (count($custom_userdata) === 0)
152 {
153 $custom_userdata = '';
154 }
155 else
156 {
157 // Serialize the custom data array so we can store it
158 $custom_userdata = $this->_serialize($custom_userdata);
159 }
160
161 // Run the update query
162 $this->CI->db->where('session_id', $this->userdata('session_id'));
163 $this->CI->db->update($this->sess_table_name,
164 array('last_activity' => $this->userdata('last_activity'), 'user_data' => $custom_userdata));
165
166 // Write the cookie. Notice that we manually pass the cookie data array to the
167 // _set_cookie() function. Normally that function will store $this->userdata, but
168 // in this case that array contains custom data, which we do not want in the cookie.
169 $this->_set_cookie($cookie_userdata);
170 }
171
172 /**
173 * Destroy the current session
174 *
175 * @return void
176 */
177 public function sess_destroy()
178 {
179 // Kill the session DB row
180 if ($this->sess_use_database === TRUE && $this->has_userdata('session_id'))
181 {
182 $this->CI->db->where('session_id', $this->userdata['session_id']);
183 $this->CI->db->delete($this->sess_table_name);
184 }
185
186 // Kill the cookie
187 setcookie($this->sess_cookie_name, addslashes(serialize(array())), ($this->now - 31500000),
188 $this->cookie_path, $this->cookie_domain, 0);
189 }
190
191 /**
192 * Regenerate the current session
193 *
194 * Regenerate the session id
195 *
196 * @param boolean Destroy session data flag (default: false)
197 * @return void
198 */
199 public function sess_regenerate($destroy = false)
200 {
201 // Check destroy flag
202 if ($destroy)
203 {
204 // Destroy old session and create new one
205 $this->sess_destroy();
206 $this->_sess_create();
207 }
208 else
209 {
210 // Just force an update to recreate the id
211 $this->_sess_update(true);
212 }
213 }
214
215 /**
216 * Get a reference to user data array
217 *
218 * @return array - Reference to userdata
219 */
220 public function &get_userdata()
221 {
222 // Return reference to array
223 return $this->userdata;
224 }
225
226 /**
227 * Fetch the current session data if it exists
228 *
Darren Hilla2ae6572011-09-01 07:36:26 -0400229 * @access protected
Darren Hillc4e266b2011-08-30 15:40:27 -0400230 * @return bool
231 */
Darren Hilla2ae6572011-09-01 07:36:26 -0400232 protected function _sess_read()
Darren Hillc4e266b2011-08-30 15:40:27 -0400233 {
234 // Fetch the cookie
235 $session = $this->CI->input->cookie($this->sess_cookie_name);
236
237 // No cookie? Goodbye cruel world!...
238 if ($session === FALSE)
239 {
240 log_message('debug', 'A session cookie was not found.');
241 return FALSE;
242 }
243
244 // Decrypt the cookie data
245 if ($this->sess_encrypt_cookie == TRUE)
246 {
247 $session = $this->CI->encrypt->decode($session);
248 }
249 else
250 {
251 // encryption was not used, so we need to check the md5 hash
252 $hash = substr($session, strlen($session)-32); // get last 32 chars
253 $session = substr($session, 0, strlen($session)-32);
254
255 // Does the md5 hash match? This is to prevent manipulation of session data in userspace
256 if ($hash !== md5($session.$this->encryption_key))
257 {
258 log_message('error', 'The session cookie data did not match what was expected. '.
259 'This could be a possible hacking attempt.');
260 $this->sess_destroy();
261 return FALSE;
262 }
263 }
264
265 // Unserialize the session array
266 $session = $this->_unserialize($session);
267
268 // Is the session data we unserialized an array with the correct format?
269 if ( ! is_array($session) || ! isset($session['session_id']) || ! isset($session['ip_address']) ||
270 ! isset($session['user_agent']) || ! isset($session['last_activity']))
271 {
272 $this->sess_destroy();
273 return FALSE;
274 }
275
276 // Is the session current?
277 if (($session['last_activity'] + $this->sess_expiration) < $this->now())
278 {
279 $this->sess_destroy();
280 return FALSE;
281 }
282
283 // Does the IP Match?
284 if ($this->sess_match_ip == TRUE && $session['ip_address'] != $this->CI->input->ip_address())
285 {
286 $this->sess_destroy();
287 return FALSE;
288 }
289
290 // Does the User Agent Match?
291 if ($this->sess_match_useragent == TRUE &&
292 trim($session['user_agent']) != trim(substr($this->CI->input->user_agent(), 0, 50)))
293 {
294 $this->sess_destroy();
295 return FALSE;
296 }
297
298 // Is there a corresponding session in the DB?
299 if ($this->sess_use_database === TRUE)
300 {
301 $this->CI->db->where('session_id', $session['session_id']);
302
303 if ($this->sess_match_ip == TRUE)
304 {
305 $this->CI->db->where('ip_address', $session['ip_address']);
306 }
307
308 if ($this->sess_match_useragent == TRUE)
309 {
310 $this->CI->db->where('user_agent', $session['user_agent']);
311 }
312
313 $query = $this->CI->db->get($this->sess_table_name);
314
315 // No result? Kill it!
316 if ($query->num_rows() == 0)
317 {
318 $this->sess_destroy();
319 return FALSE;
320 }
321
322 // Is there custom data? If so, add it to the main session array
323 $row = $query->row();
324 if (isset($row->user_data) && $row->user_data != '')
325 {
326 $custom_data = $this->_unserialize($row->user_data);
327
328 if (is_array($custom_data))
329 {
330 foreach ($custom_data as $key => $val)
331 {
332 $session[$key] = $val;
333 }
334 }
335 }
336 }
337
338 // Session is valid!
339 $this->userdata = $session;
340 unset($session);
341
342 return TRUE;
343 }
344
345 /**
346 * Create a new session
347 *
Darren Hilla2ae6572011-09-01 07:36:26 -0400348 * @access protected
Darren Hillc4e266b2011-08-30 15:40:27 -0400349 * @return void
350 */
Darren Hilla2ae6572011-09-01 07:36:26 -0400351 protected function _sess_create()
Darren Hillc4e266b2011-08-30 15:40:27 -0400352 {
353 $sessid = '';
354 while (strlen($sessid) < 32)
355 {
356 $sessid .= mt_rand(0, mt_getrandmax());
357 }
358
359 // To make the session ID even more secure we'll combine it with the user's IP
360 $sessid .= $this->CI->input->ip_address();
361
362 $this->set_userdata('session_id', md5(uniqid($sessid, TRUE)));
363 $this->set_userdata('ip_address', $this->CI->input->ip_address());
364 $this->set_userdata('user_agent', substr($this->CI->input->user_agent(), 0, 50));
365 $this->set_userdata('last_activity',$this->now());
366
367
368 // Save the data to the DB if needed
369 if ($this->sess_use_database === TRUE)
370 {
371 $this->CI->db->query($this->CI->db->insert_string($this->sess_table_name, $this->all_userdata()));
372 }
373
374 // Write the cookie
375 $this->_set_cookie();
376 }
377
378 /**
379 * Update an existing session
380 *
Darren Hilla2ae6572011-09-01 07:36:26 -0400381 * @access protected
Darren Hillc4e266b2011-08-30 15:40:27 -0400382 * @param boolean Force update flag (default: false)
383 * @return void
384 */
Darren Hilla2ae6572011-09-01 07:36:26 -0400385 protected function _sess_update($force = false)
Darren Hillc4e266b2011-08-30 15:40:27 -0400386 {
387 // We only update the session every five minutes by default (unless forced)
388 if (!$force && ($this->userdata['last_activity'] + $this->sess_time_to_update) >= $this->now())
389 {
390 return;
391 }
392
393 // Save the old session id so we know which record to
394 // update in the database if we need it
395 $old_sessid = $this->userdata['session_id'];
396 $new_sessid = '';
397 while (strlen($new_sessid) < 32)
398 {
399 $new_sessid .= mt_rand(0, mt_getrandmax());
400 }
401
402 // To make the session ID even more secure we'll combine it with the user's IP
403 $new_sessid .= $this->CI->input->ip_address();
404
405 // Turn it into a hash
406 $new_sessid = md5(uniqid($new_sessid, TRUE));
407
408 // Update the session data in the session data array
409 $this->set_userdata('session_id', $new_sessid);
410 $this->set_userdata('last_activity', $this->now());
411
412 // _set_cookie() will handle this for us if we aren't using database sessions
413 // by pushing all userdata to the cookie.
414 $cookie_data = NULL;
415
416 // Update the session ID and last_activity field in the DB if needed
417 if ($this->sess_use_database === TRUE)
418 {
419 // set cookie explicitly to only have our session data
420 $cookie_data = array();
421 foreach (array('session_id','ip_address','user_agent','last_activity') as $val)
422 {
423 $cookie_data[$val] = $this->userdata[$val];
424 }
425
426 $this->CI->db->query($this->CI->db->update_string($this->sess_table_name,
427 array('last_activity' => $this->now(), 'session_id' => $new_sessid),
428 array('session_id' => $old_sessid)));
429 }
430
431 // Write the cookie
432 $this->_set_cookie($cookie_data);
433 }
434
435 /**
436 * Get the "now" time
437 *
Darren Hilla2ae6572011-09-01 07:36:26 -0400438 * @access protected
Darren Hillc4e266b2011-08-30 15:40:27 -0400439 * @return int
440 */
Darren Hilla2ae6572011-09-01 07:36:26 -0400441 protected function _get_time()
Darren Hillc4e266b2011-08-30 15:40:27 -0400442 {
443 if (strtolower($this->time_reference) == 'gmt')
444 {
445 $now = time();
446 $time = mktime(gmdate('H', $now), gmdate('i', $now), gmdate('s', $now), gmdate('m', $now),
447 gmdate('d', $now), gmdate('Y', $now));
448 }
449 else
450 {
451 $time = time();
452 }
453
454 return $time;
455 }
456
457 /**
458 * Write the session cookie
459 *
Darren Hilla2ae6572011-09-01 07:36:26 -0400460 * @access protected
Darren Hillc4e266b2011-08-30 15:40:27 -0400461 * @param array Cookie name/value pairs
462 * @return void
463 */
Darren Hilla2ae6572011-09-01 07:36:26 -0400464 protected function _set_cookie(array $cookie_data = NULL)
Darren Hillc4e266b2011-08-30 15:40:27 -0400465 {
466 if (is_null($cookie_data))
467 {
468 $cookie_data = $this->all_userdata();
469 }
470
471 // Serialize the userdata for the cookie
472 $cookie_data = $this->_serialize($cookie_data);
473
474 if ($this->sess_encrypt_cookie == TRUE)
475 {
476 $cookie_data = $this->CI->encrypt->encode($cookie_data);
477 }
478 else
479 {
480 // if encryption is not used, we provide an md5 hash to prevent userside tampering
481 $cookie_data = $cookie_data.md5($cookie_data.$this->encryption_key);
482 }
483
484 $expire = ($this->sess_expire_on_close === TRUE) ? 0 : $this->sess_expiration + time();
485
486 // Set the cookie
Darren Hill00fcb542011-09-12 07:57:04 -0400487 setcookie($this->sess_cookie_name, $cookie_data, $expire, $this->cookie_path, $this->cookie_domain,
488 $this->cookie_secure);
Darren Hillc4e266b2011-08-30 15:40:27 -0400489 }
490
491 /**
492 * Serialize an array
493 *
494 * This function first converts any slashes found in the array to a temporary
495 * marker, so when it gets unserialized the slashes will be preserved
496 *
Darren Hilla2ae6572011-09-01 07:36:26 -0400497 * @access protected
Darren Hillc4e266b2011-08-30 15:40:27 -0400498 * @param mixed Data to serialize
499 * @return string
500 */
Darren Hilla2ae6572011-09-01 07:36:26 -0400501 protected function _serialize($data)
Darren Hillc4e266b2011-08-30 15:40:27 -0400502 {
503 if (is_array($data))
504 {
505 foreach ($data as $key => $val)
506 {
507 if (is_string($val))
508 {
509 $data[$key] = str_replace('\\', '{{slash}}', $val);
510 }
511 }
512 }
513 else
514 {
515 if (is_string($data))
516 {
517 $data = str_replace('\\', '{{slash}}', $data);
518 }
519 }
520
521 return serialize($data);
522 }
523
524 /**
525 * Unserialize
526 *
527 * This function unserializes a data string, then converts any
528 * temporary slash markers back to actual slashes
529 *
Darren Hilla2ae6572011-09-01 07:36:26 -0400530 * @access protected
Darren Hillc4e266b2011-08-30 15:40:27 -0400531 * @param string Data to unserialize
532 * @return mixed
533 */
Darren Hilla2ae6572011-09-01 07:36:26 -0400534 protected function _unserialize($data)
Darren Hillc4e266b2011-08-30 15:40:27 -0400535 {
536 $data = @unserialize(strip_slashes($data));
537
538 if (is_array($data))
539 {
540 foreach ($data as $key => $val)
541 {
542 if (is_string($val))
543 {
544 $data[$key] = str_replace('{{slash}}', '\\', $val);
545 }
546 }
547
548 return $data;
549 }
550
551 return (is_string($data)) ? str_replace('{{slash}}', '\\', $data) : $data;
552 }
553
554 /**
555 * Garbage collection
556 *
557 * This deletes expired session rows from database
558 * if the probability percentage is met
559 *
Darren Hilla2ae6572011-09-01 07:36:26 -0400560 * @access protected
Darren Hillc4e266b2011-08-30 15:40:27 -0400561 * @return void
562 */
Darren Hilla2ae6572011-09-01 07:36:26 -0400563 protected function _sess_gc()
Darren Hillc4e266b2011-08-30 15:40:27 -0400564 {
565 if ($this->sess_use_database != TRUE)
566 {
567 return;
568 }
569
570 srand(time());
571 if ((rand() % 100) < self::gc_probability)
572 {
573 $expire = $this->now() - $this->sess_expiration;
574
575 $this->CI->db->where('last_activity < '.$expire);
576 $this->CI->db->delete($this->sess_table_name);
577
578 log_message('debug', 'Session garbage collection performed.');
579 }
580 }
581}
Darren Hill5073a372011-08-31 13:54:19 -0400582// END CI_Session_cookie Class
Darren Hillc4e266b2011-08-30 15:40:27 -0400583
584/* End of file Session_cookie.php */
Darren Hill5073a372011-08-31 13:54:19 -0400585/* Location: ./system/libraries/Session/drivers/Session_cookie.php */
Darren Hillc4e266b2011-08-30 15:40:27 -0400586?>