blob: afa43348ef3fd4edd9bc2d6fa6a63a71f0cb08ee [file] [log] [blame]
Derek Allardd2df9bc2007-04-15 17:41:17 +00001<?php if (!defined('BASEPATH')) exit('No direct script access allowed');
2/**
3 * CodeIgniter
4 *
5 * An open source application development framework for PHP 4.3.2 or newer
6 *
7 * @package CodeIgniter
Derek Allard3d879d52008-01-18 19:41:32 +00008 * @author ExpressionEngine Dev Team
Derek Allardd2df9bc2007-04-15 17:41:17 +00009 * @copyright Copyright (c) 2006, EllisLab, Inc.
Derek Jones7a9193a2008-01-21 18:39:20 +000010 * @license http://codeigniter.com/user_guide/license.html
11 * @link http://codeigniter.com
Derek Allardd2df9bc2007-04-15 17:41:17 +000012 * @since Version 1.0
13 * @filesource
14 */
15
16// ------------------------------------------------------------------------
17
18/**
19 * Session Class
20 *
21 * @package CodeIgniter
22 * @subpackage Libraries
23 * @category Sessions
Derek Allard3d879d52008-01-18 19:41:32 +000024 * @author ExpressionEngine Dev Team
Derek Jones7a9193a2008-01-21 18:39:20 +000025 * @link http://codeigniter.com/user_guide/libraries/sessions.html
Derek Allardd2df9bc2007-04-15 17:41:17 +000026 */
27class CI_Session {
28
29 var $CI;
30 var $now;
31 var $encryption = TRUE;
32 var $use_database = FALSE;
33 var $session_table = FALSE;
34 var $sess_length = 7200;
35 var $sess_cookie = 'ci_session';
36 var $userdata = array();
37 var $gc_probability = 5;
Derek Allard428e9642007-08-10 03:16:16 +000038 var $flashdata_key = 'flash';
Derek Allard34bc8f82007-08-10 03:26:42 +000039 var $time_to_update = 300;
Derek Allardd2df9bc2007-04-15 17:41:17 +000040
41 /**
42 * Session Constructor
43 *
44 * The constructor runs the session routines automatically
45 * whenever the class is instantiated.
46 */
47 function CI_Session()
48 {
49 $this->CI =& get_instance();
50
51 log_message('debug', "Session Class Initialized");
52 $this->sess_run();
53 }
54
55 // --------------------------------------------------------------------
56
57 /**
58 * Run the session routines
59 *
60 * @access public
61 * @return void
62 */
63 function sess_run()
64 {
65 /*
66 * Set the "now" time
67 *
68 * It can either set to GMT or time(). The pref
69 * is set in the config file. If the developer
70 * is doing any sort of time localization they
71 * might want to set the session time to GMT so
Derek Jones63df95e2008-01-30 15:59:12 +000072 * they can offset the "last_activity" time
73 * based on each user's locale.
Derek Allardd2df9bc2007-04-15 17:41:17 +000074 *
75 */
Derek Allard428e9642007-08-10 03:16:16 +000076
77 if (is_numeric($this->CI->config->item('sess_time_to_update')))
78 {
79 $this->time_to_update = $this->CI->config->item('sess_time_to_update');
80 }
81
Derek Allardd2df9bc2007-04-15 17:41:17 +000082 if (strtolower($this->CI->config->item('time_reference')) == 'gmt')
83 {
84 $now = time();
85 $this->now = mktime(gmdate("H", $now), gmdate("i", $now), gmdate("s", $now), gmdate("m", $now), gmdate("d", $now), gmdate("Y", $now));
86
87 if (strlen($this->now) < 10)
88 {
89 $this->now = time();
90 log_message('error', 'The session class could not set a proper GMT timestamp so the local time() value was used.');
91 }
92 }
93 else
94 {
95 $this->now = time();
96 }
97
98 /*
99 * Set the session length
100 *
101 * If the session expiration is set to zero in
102 * the config file we'll set the expiration
103 * two years from now.
104 *
105 */
106 $expiration = $this->CI->config->item('sess_expiration');
107
108 if (is_numeric($expiration))
109 {
110 if ($expiration > 0)
111 {
112 $this->sess_length = $this->CI->config->item('sess_expiration');
113 }
114 else
115 {
116 $this->sess_length = (60*60*24*365*2);
117 }
118 }
119
120 // Do we need encryption?
121 $this->encryption = $this->CI->config->item('sess_encrypt_cookie');
122
123 if ($this->encryption == TRUE)
124 {
125 $this->CI->load->library('encrypt');
126 }
127
128 // Are we using a database?
129 if ($this->CI->config->item('sess_use_database') === TRUE AND $this->CI->config->item('sess_table_name') != '')
130 {
131 $this->use_database = TRUE;
132 $this->session_table = $this->CI->config->item('sess_table_name');
133 $this->CI->load->database();
134 }
135
136 // Set the cookie name
137 if ($this->CI->config->item('sess_cookie_name') != FALSE)
138 {
139 $this->sess_cookie = $this->CI->config->item('cookie_prefix').$this->CI->config->item('sess_cookie_name');
140 }
141
142 /*
143 * Fetch the current session
144 *
145 * If a session doesn't exist we'll create
146 * a new one. If it does, we'll update it.
147 *
148 */
149 if ( ! $this->sess_read())
150 {
151 $this->sess_create();
152 }
153 else
154 {
155 // We only update the session every five minutes
Derek Allard428e9642007-08-10 03:16:16 +0000156 if (($this->userdata['last_activity'] + $this->time_to_update) < $this->now)
Derek Allardd2df9bc2007-04-15 17:41:17 +0000157 {
158 $this->sess_update();
159 }
160 }
161
162 // Delete expired sessions if necessary
163 if ($this->use_database === TRUE)
164 {
165 $this->sess_gc();
Derek Allard428e9642007-08-10 03:16:16 +0000166 }
167
168 // Delete 'old' flashdata (from last request)
169 $this->_flashdata_sweep();
170
171 // Mark all new flashdata as old (data will be deleted before next request)
172 $this->_flashdata_mark();
Derek Allardd2df9bc2007-04-15 17:41:17 +0000173 }
174
175 // --------------------------------------------------------------------
176
177 /**
178 * Fetch the current session data if it exists
179 *
180 * @access public
181 * @return void
182 */
183 function sess_read()
184 {
185 // Fetch the cookie
186 $session = $this->CI->input->cookie($this->sess_cookie);
187
188 if ($session === FALSE)
189 {
190 log_message('debug', 'A session cookie was not found.');
191 return FALSE;
192 }
193
194 // Decrypt and unserialize the data
195 if ($this->encryption == TRUE)
196 {
197 $session = $this->CI->encrypt->decode($session);
198 }
Derek Allard9c4280b2008-03-18 00:01:52 +0000199 else
200 {
201 // encryption was not used, so we need to check the md5 hash
202 $hash = substr($session, strlen($session)-32); // get last 32 chars
203 $session = substr($session, 0, strlen($session)-32);
Derek Allardd2df9bc2007-04-15 17:41:17 +0000204
Derek Allard9c4280b2008-03-18 00:01:52 +0000205 // Does the md5 hash match? This is to prevent manipulation of session data
206 // in userspace
207 if ($hash !== md5($session.$this->CI->config->item('encryption_key')))
208 {
209 log_message('error', 'The session cookie data did not match what was expected. This could be a possible hacking attempt.');
210 $this->sess_destroy();
211 return FALSE;
212 }
213 }
214
Derek Allardd2df9bc2007-04-15 17:41:17 +0000215 $session = @unserialize($this->strip_slashes($session));
216
217 if ( ! is_array($session) OR ! isset($session['last_activity']))
218 {
219 log_message('error', 'The session cookie data did not contain a valid array. This could be a possible hacking attempt.');
220 return FALSE;
221 }
222
223 // Is the session current?
224 if (($session['last_activity'] + $this->sess_length) < $this->now)
225 {
226 $this->sess_destroy();
227 return FALSE;
228 }
229
230 // Does the IP Match?
231 if ($this->CI->config->item('sess_match_ip') == TRUE AND $session['ip_address'] != $this->CI->input->ip_address())
232 {
233 $this->sess_destroy();
234 return FALSE;
235 }
236
237 // Does the User Agent Match?
Derek Allard59c26332007-10-01 12:17:39 +0000238 if ($this->CI->config->item('sess_match_useragent') == TRUE AND trim($session['user_agent']) != trim(substr($this->CI->input->user_agent(), 0, 50)))
Derek Allardd2df9bc2007-04-15 17:41:17 +0000239 {
240 $this->sess_destroy();
241 return FALSE;
242 }
243
244 // Is there a corresponding session in the DB?
245 if ($this->use_database === TRUE)
246 {
247 $this->CI->db->where('session_id', $session['session_id']);
248
249 if ($this->CI->config->item('sess_match_ip') == TRUE)
250 {
251 $this->CI->db->where('ip_address', $session['ip_address']);
252 }
253
254 if ($this->CI->config->item('sess_match_useragent') == TRUE)
255 {
256 $this->CI->db->where('user_agent', $session['user_agent']);
257 }
258
259 $query = $this->CI->db->get($this->session_table);
260
261 if ($query->num_rows() == 0)
262 {
263 $this->sess_destroy();
264 return FALSE;
265 }
266 else
267 {
268 $row = $query->row();
269 if (($row->last_activity + $this->sess_length) < $this->now)
270 {
271 $this->CI->db->where('session_id', $session['session_id']);
272 $this->CI->db->delete($this->session_table);
273 $this->sess_destroy();
274 return FALSE;
275 }
276 }
277 }
278
279 // Session is valid!
280 $this->userdata = $session;
281 unset($session);
282
283 return TRUE;
284 }
285
286 // --------------------------------------------------------------------
287
288 /**
289 * Write the session cookie
290 *
291 * @access public
292 * @return void
293 */
294 function sess_write()
295 {
296 $cookie_data = serialize($this->userdata);
297
298 if ($this->encryption == TRUE)
299 {
300 $cookie_data = $this->CI->encrypt->encode($cookie_data);
301 }
Derek Allard9c4280b2008-03-18 00:01:52 +0000302 else
303 {
304 // if encryption is not used, we provide an md5 hash to prevent userside tampering
305 $cookie_data = $cookie_data . md5($cookie_data.$this->CI->config->item('encryption_key'));
306 }
Derek Allardd2df9bc2007-04-15 17:41:17 +0000307
308 setcookie(
309 $this->sess_cookie,
310 $cookie_data,
311 $this->sess_length + time(),
312 $this->CI->config->item('cookie_path'),
313 $this->CI->config->item('cookie_domain'),
314 0
315 );
316 }
317
318 // --------------------------------------------------------------------
319
320 /**
321 * Create a new session
322 *
323 * @access public
324 * @return void
325 */
326 function sess_create()
327 {
328 $sessid = '';
329 while (strlen($sessid) < 32)
330 {
331 $sessid .= mt_rand(0, mt_getrandmax());
332 }
333
334 $this->userdata = array(
335 'session_id' => md5(uniqid($sessid, TRUE)),
336 'ip_address' => $this->CI->input->ip_address(),
337 'user_agent' => substr($this->CI->input->user_agent(), 0, 50),
338 'last_activity' => $this->now
339 );
340
341
342 // Save the session in the DB if needed
343 if ($this->use_database === TRUE)
344 {
345 $this->CI->db->query($this->CI->db->insert_string($this->session_table, $this->userdata));
346 }
347
348 // Write the cookie
Derek Allardd2df9bc2007-04-15 17:41:17 +0000349 $this->sess_write();
350 }
351
352 // --------------------------------------------------------------------
353
354 /**
355 * Update an existing session
356 *
357 * @access public
358 * @return void
359 */
360 function sess_update()
361 {
Derek Allard428e9642007-08-10 03:16:16 +0000362 // Save the old session id so we know which record to
363 // update in the database if we need it
364 $old_sessid = $this->userdata['session_id'];
365 $new_sessid = '';
366 while (strlen($new_sessid) < 32)
367 {
368 $new_sessid .= mt_rand(0, mt_getrandmax());
369 }
370 $new_sessid = md5(uniqid($new_sessid, TRUE));
371
372 // Update the session data in the session data array
373 $this->userdata['session_id'] = $new_sessid;
Derek Allardd2df9bc2007-04-15 17:41:17 +0000374 $this->userdata['last_activity'] = $this->now;
375
376 // Update the session in the DB if needed
377 if ($this->use_database === TRUE)
378 {
Derek Allard428e9642007-08-10 03:16:16 +0000379 $this->CI->db->query($this->CI->db->update_string($this->session_table, array('last_activity' => $this->now, 'session_id' => $new_sessid), array('session_id' => $old_sessid)));
Derek Allardd2df9bc2007-04-15 17:41:17 +0000380 }
381
382 // Write the cookie
383 $this->sess_write();
384 }
385
386 // --------------------------------------------------------------------
387
388 /**
389 * Destroy the current session
390 *
391 * @access public
392 * @return void
393 */
394 function sess_destroy()
395 {
396 setcookie(
397 $this->sess_cookie,
398 addslashes(serialize(array())),
399 ($this->now - 31500000),
400 $this->CI->config->item('cookie_path'),
401 $this->CI->config->item('cookie_domain'),
402 0
403 );
404 }
405
406 // --------------------------------------------------------------------
407
408 /**
409 * Garbage collection
410 *
411 * This deletes expired session rows from database
412 * if the probability percentage is met
413 *
414 * @access public
415 * @return void
416 */
417 function sess_gc()
418 {
419 srand(time());
420 if ((rand() % 100) < $this->gc_probability)
421 {
422 $expire = $this->now - $this->sess_length;
423
424 $this->CI->db->where("last_activity < {$expire}");
425 $this->CI->db->delete($this->session_table);
426
427 log_message('debug', 'Session garbage collection performed.');
428 }
429 }
430
431 // --------------------------------------------------------------------
432
433 /**
Derek Allard428e9642007-08-10 03:16:16 +0000434 * Fetch a specific item from the session array
Derek Allardd2df9bc2007-04-15 17:41:17 +0000435 *
436 * @access public
437 * @param string
438 * @return string
439 */
440 function userdata($item)
441 {
442 return ( ! isset($this->userdata[$item])) ? FALSE : $this->userdata[$item];
443 }
Derek Allard428e9642007-08-10 03:16:16 +0000444
445 // --------------------------------------------------------------------
446
447 /**
448 * Fetch all session data
449 *
450 * @access public
451 * @return mixed
452 */
453 function all_userdata()
454 {
455 return ( ! isset($this->userdata)) ? FALSE : $this->userdata;
456 }
Derek Allardd2df9bc2007-04-15 17:41:17 +0000457
458 // --------------------------------------------------------------------
459
460 /**
461 * Add or change data in the "userdata" array
462 *
463 * @access public
464 * @param mixed
465 * @param string
466 * @return void
467 */
468 function set_userdata($newdata = array(), $newval = '')
469 {
470 if (is_string($newdata))
471 {
472 $newdata = array($newdata => $newval);
473 }
474
475 if (count($newdata) > 0)
476 {
477 foreach ($newdata as $key => $val)
478 {
479 $this->userdata[$key] = $val;
480 }
481 }
Derek Allard428e9642007-08-10 03:16:16 +0000482
Derek Allardd2df9bc2007-04-15 17:41:17 +0000483 $this->sess_write();
484 }
485
486 // --------------------------------------------------------------------
487
488 /**
489 * Delete a session variable from the "userdata" array
490 *
Derek Allard428e9642007-08-10 03:16:16 +0000491 * @access array
Derek Allardd2df9bc2007-04-15 17:41:17 +0000492 * @return void
493 */
494 function unset_userdata($newdata = array())
495 {
496 if (is_string($newdata))
497 {
498 $newdata = array($newdata => '');
499 }
500
501 if (count($newdata) > 0)
502 {
503 foreach ($newdata as $key => $val)
504 {
505 unset($this->userdata[$key]);
506 }
507 }
508
509 $this->sess_write();
510 }
511
512 // --------------------------------------------------------------------
513
514 /**
515 * Strip slashes
516 *
517 * @access public
518 * @param mixed
519 * @return mixed
520 */
Derek Allard428e9642007-08-10 03:16:16 +0000521 function strip_slashes($vals)
522 {
523 if (is_array($vals))
Derek Allardd2df9bc2007-04-15 17:41:17 +0000524 {
525 foreach ($vals as $key=>$val)
526 {
527 $vals[$key] = $this->strip_slashes($val);
528 }
529 }
530 else
531 {
532 $vals = stripslashes($vals);
533 }
534
535 return $vals;
536 }
537
Derek Allard428e9642007-08-10 03:16:16 +0000538
539 // ------------------------------------------------------------------------
540
541 /**
542 * Add or change flashdata, only available
543 * until the next request
544 *
545 * @access public
546 * @param mixed
547 * @param string
548 * @return void
549 */
550 function set_flashdata($newdata = array(), $newval = '')
551 {
552 if (is_string($newdata))
553 {
554 $newdata = array($newdata => $newval);
555 }
556
557 if (count($newdata) > 0)
558 {
559 foreach ($newdata as $key => $val)
560 {
561 $flashdata_key = $this->flashdata_key.':new:'.$key;
562 $this->set_userdata($flashdata_key, $val);
563 }
564 }
565 }
566
567 // ------------------------------------------------------------------------
568
569 /**
570 * Keeps existing flashdata available to next request.
571 *
572 * @access public
573 * @param string
574 * @return void
575 */
576 function keep_flashdata($key)
577 {
578 // 'old' flashdata gets removed. Here we mark all
579 // flashdata as 'new' to preserve it from _flashdata_sweep()
580 // Note the function will return FALSE if the $key
581 // provided cannot be found
582 $old_flashdata_key = $this->flashdata_key.':old:'.$key;
583 $value = $this->userdata($old_flashdata_key);
584
585 $new_flashdata_key = $this->flashdata_key.':new:'.$key;
586 $this->set_userdata($new_flashdata_key, $value);
587 }
588
589 // ------------------------------------------------------------------------
590
591 /**
592 * Fetch a specific flashdata item from the session array
593 *
594 * @access public
595 * @param string
596 * @return string
597 */
598 function flashdata($key)
599 {
600 $flashdata_key = $this->flashdata_key.':old:'.$key;
601 return $this->userdata($flashdata_key);
602 }
603
604 // ------------------------------------------------------------------------
605
606 /**
607 * Identifies flashdata as 'old' for removal
608 * when _flashdata_sweep() runs.
609 *
610 * @access private
611 * @return void
612 */
613 function _flashdata_mark()
614 {
615 $userdata = $this->all_userdata();
616 foreach ($userdata as $name => $value)
617 {
618 $parts = explode(':new:', $name);
619 if (is_array($parts) && count($parts) === 2)
620 {
621 $new_name = $this->flashdata_key.':old:'.$parts[1];
622 $this->set_userdata($new_name, $value);
623 $this->unset_userdata($name);
624 }
625 }
626 }
627
628 // ------------------------------------------------------------------------
629
630 /**
631 * Removes all flashdata marked as 'old'
632 *
633 * @access private
634 * @return void
635 */
636
637 function _flashdata_sweep()
638 {
639 $userdata = $this->all_userdata();
640 foreach ($userdata as $key => $value)
641 {
642 if (strpos($key, ':old:'))
643 {
644 $this->unset_userdata($key);
645 }
646 }
647
648 }
649
Derek Allardd2df9bc2007-04-15 17:41:17 +0000650}
651// END Session Class
adminb0dd10f2006-08-25 17:25:49 +0000652?>