PageRenderTime 418ms CodeModel.GetById 88ms app.highlight 110ms RepoModel.GetById 178ms app.codeStats 1ms

/system/libraries/Session.php

https://bitbucket.org/ebottabi/ci-resque
PHP | 776 lines | 413 code | 113 blank | 250 comment | 61 complexity | 5ab5b188757819bf6ebaeb1466d738e6 MD5 | raw file
  1<?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 - 2011, EllisLab, Inc.
 10 * @license		http://codeigniter.com/user_guide/license.html
 11 * @link		http://codeigniter.com
 12 * @since		Version 1.0
 13 * @filesource
 14 */
 15
 16// ------------------------------------------------------------------------
 17
 18/**
 19 * Session Class
 20 *
 21 * @package		CodeIgniter
 22 * @subpackage	Libraries
 23 * @category	Sessions
 24 * @author		ExpressionEngine Dev Team
 25 * @link		http://codeigniter.com/user_guide/libraries/sessions.html
 26 */
 27class CI_Session {
 28
 29	var $sess_encrypt_cookie		= FALSE;
 30	var $sess_use_database			= FALSE;
 31	var $sess_table_name			= '';
 32	var $sess_expiration			= 7200;
 33	var $sess_expire_on_close		= FALSE;
 34	var $sess_match_ip				= FALSE;
 35	var $sess_match_useragent		= TRUE;
 36	var $sess_cookie_name			= 'ci_session';
 37	var $cookie_prefix				= '';
 38	var $cookie_path				= '';
 39	var $cookie_domain				= '';
 40	var $cookie_secure				= FALSE;
 41	var $sess_time_to_update		= 300;
 42	var $encryption_key				= '';
 43	var $flashdata_key				= 'flash';
 44	var $time_reference				= 'time';
 45	var $gc_probability				= 5;
 46	var $userdata					= array();
 47	var $CI;
 48	var $now;
 49
 50	/**
 51	 * Session Constructor
 52	 *
 53	 * The constructor runs the session routines automatically
 54	 * whenever the class is instantiated.
 55	 */
 56	public function __construct($params = array())
 57	{
 58		log_message('debug', "Session Class Initialized");
 59
 60		// Set the super object to a local variable for use throughout the class
 61		$this->CI =& get_instance();
 62
 63		// Set all the session preferences, which can either be set
 64		// manually via the $params array above or via the config file
 65		foreach (array('sess_encrypt_cookie', 'sess_use_database', 'sess_table_name', 'sess_expiration', 'sess_expire_on_close', 'sess_match_ip', 'sess_match_useragent', 'sess_cookie_name', 'cookie_path', 'cookie_domain', 'cookie_secure', 'sess_time_to_update', 'time_reference', 'cookie_prefix', 'encryption_key') as $key)
 66		{
 67			$this->$key = (isset($params[$key])) ? $params[$key] : $this->CI->config->item($key);
 68		}
 69
 70		if ($this->encryption_key == '')
 71		{
 72			show_error('In order to use the Session class you are required to set an encryption key in your config file.');
 73		}
 74
 75		// Load the string helper so we can use the strip_slashes() function
 76		$this->CI->load->helper('string');
 77
 78		// Do we need encryption? If so, load the encryption class
 79		if ($this->sess_encrypt_cookie == TRUE)
 80		{
 81			$this->CI->load->library('encrypt');
 82		}
 83
 84		// Are we using a database?  If so, load it
 85		if ($this->sess_use_database === TRUE AND $this->sess_table_name != '')
 86		{
 87			$this->CI->load->database();
 88		}
 89
 90		// Set the "now" time.  Can either be GMT or server time, based on the
 91		// config prefs.  We use this to set the "last activity" time
 92		$this->now = $this->_get_time();
 93
 94		// Set the session length. If the session expiration is
 95		// set to zero we'll set the expiration two years from now.
 96		if ($this->sess_expiration == 0)
 97		{
 98			$this->sess_expiration = (60*60*24*365*2);
 99		}
100		
101		// Set the cookie name
102		$this->sess_cookie_name = $this->cookie_prefix.$this->sess_cookie_name;
103
104		// Run the Session routine. If a session doesn't exist we'll
105		// create a new one.  If it does, we'll update it.
106		if ( ! $this->sess_read())
107		{
108			$this->sess_create();
109		}
110		else
111		{
112			$this->sess_update();
113		}
114
115		// Delete 'old' flashdata (from last request)
116		$this->_flashdata_sweep();
117
118		// Mark all new flashdata as old (data will be deleted before next request)
119		$this->_flashdata_mark();
120
121		// Delete expired sessions if necessary
122		$this->_sess_gc();
123
124		log_message('debug', "Session routines successfully run");
125	}
126
127	// --------------------------------------------------------------------
128
129	/**
130	 * Fetch the current session data if it exists
131	 *
132	 * @access	public
133	 * @return	bool
134	 */
135	function sess_read()
136	{
137		// Fetch the cookie
138		$session = $this->CI->input->cookie($this->sess_cookie_name);
139
140		// No cookie?  Goodbye cruel world!...
141		if ($session === FALSE)
142		{
143			log_message('debug', 'A session cookie was not found.');
144			return FALSE;
145		}
146
147		// Decrypt the cookie data
148		if ($this->sess_encrypt_cookie == TRUE)
149		{
150			$session = $this->CI->encrypt->decode($session);
151		}
152		else
153		{
154			// encryption was not used, so we need to check the md5 hash
155			$hash	 = substr($session, strlen($session)-32); // get last 32 chars
156			$session = substr($session, 0, strlen($session)-32);
157
158			// Does the md5 hash match?  This is to prevent manipulation of session data in userspace
159			if ($hash !==  md5($session.$this->encryption_key))
160			{
161				log_message('error', 'The session cookie data did not match what was expected. This could be a possible hacking attempt.');
162				$this->sess_destroy();
163				return FALSE;
164			}
165		}
166
167		// Unserialize the session array
168		$session = $this->_unserialize($session);
169
170		// Is the session data we unserialized an array with the correct format?
171		if ( ! is_array($session) OR ! isset($session['session_id']) OR ! isset($session['ip_address']) OR ! isset($session['user_agent']) OR ! isset($session['last_activity']))
172		{
173			$this->sess_destroy();
174			return FALSE;
175		}
176
177		// Is the session current?
178		if (($session['last_activity'] + $this->sess_expiration) < $this->now)
179		{
180			$this->sess_destroy();
181			return FALSE;
182		}
183
184		// Does the IP Match?
185		if ($this->sess_match_ip == TRUE AND $session['ip_address'] != $this->CI->input->ip_address())
186		{
187			$this->sess_destroy();
188			return FALSE;
189		}
190
191		// Does the User Agent Match?
192		if ($this->sess_match_useragent == TRUE AND trim($session['user_agent']) != trim(substr($this->CI->input->user_agent(), 0, 120)))
193		{
194			$this->sess_destroy();
195			return FALSE;
196		}
197
198		// Is there a corresponding session in the DB?
199		if ($this->sess_use_database === TRUE)
200		{
201			$this->CI->db->where('session_id', $session['session_id']);
202
203			if ($this->sess_match_ip == TRUE)
204			{
205				$this->CI->db->where('ip_address', $session['ip_address']);
206			}
207
208			if ($this->sess_match_useragent == TRUE)
209			{
210				$this->CI->db->where('user_agent', $session['user_agent']);
211			}
212
213			$query = $this->CI->db->get($this->sess_table_name);
214
215			// No result?  Kill it!
216			if ($query->num_rows() == 0)
217			{
218				$this->sess_destroy();
219				return FALSE;
220			}
221
222			// Is there custom data?  If so, add it to the main session array
223			$row = $query->row();
224			if (isset($row->user_data) AND $row->user_data != '')
225			{
226				$custom_data = $this->_unserialize($row->user_data);
227
228				if (is_array($custom_data))
229				{
230					foreach ($custom_data as $key => $val)
231					{
232						$session[$key] = $val;
233					}
234				}
235			}
236		}
237
238		// Session is valid!
239		$this->userdata = $session;
240		unset($session);
241
242		return TRUE;
243	}
244
245	// --------------------------------------------------------------------
246
247	/**
248	 * Write the session data
249	 *
250	 * @access	public
251	 * @return	void
252	 */
253	function sess_write()
254	{
255		// Are we saving custom data to the DB?  If not, all we do is update the cookie
256		if ($this->sess_use_database === FALSE)
257		{
258			$this->_set_cookie();
259			return;
260		}
261
262		// set the custom userdata, the session data we will set in a second
263		$custom_userdata = $this->userdata;
264		$cookie_userdata = array();
265
266		// Before continuing, we need to determine if there is any custom data to deal with.
267		// Let's determine this by removing the default indexes to see if there's anything left in the array
268		// and set the session data while we're at it
269		foreach (array('session_id','ip_address','user_agent','last_activity') as $val)
270		{
271			unset($custom_userdata[$val]);
272			$cookie_userdata[$val] = $this->userdata[$val];
273		}
274
275		// Did we find any custom data?  If not, we turn the empty array into a string
276		// since there's no reason to serialize and store an empty array in the DB
277		if (count($custom_userdata) === 0)
278		{
279			$custom_userdata = '';
280		}
281		else
282		{
283			// Serialize the custom data array so we can store it
284			$custom_userdata = $this->_serialize($custom_userdata);
285		}
286
287		// Run the update query
288		$this->CI->db->where('session_id', $this->userdata['session_id']);
289		$this->CI->db->update($this->sess_table_name, array('last_activity' => $this->userdata['last_activity'], 'user_data' => $custom_userdata));
290
291		// Write the cookie.  Notice that we manually pass the cookie data array to the
292		// _set_cookie() function. Normally that function will store $this->userdata, but
293		// in this case that array contains custom data, which we do not want in the cookie.
294		$this->_set_cookie($cookie_userdata);
295	}
296
297	// --------------------------------------------------------------------
298
299	/**
300	 * Create a new session
301	 *
302	 * @access	public
303	 * @return	void
304	 */
305	function sess_create()
306	{
307		$sessid = '';
308		while (strlen($sessid) < 32)
309		{
310			$sessid .= mt_rand(0, mt_getrandmax());
311		}
312
313		// To make the session ID even more secure we'll combine it with the user's IP
314		$sessid .= $this->CI->input->ip_address();
315
316		$this->userdata = array(
317							'session_id'	=> md5(uniqid($sessid, TRUE)),
318							'ip_address'	=> $this->CI->input->ip_address(),
319							'user_agent'	=> substr($this->CI->input->user_agent(), 0, 120),
320							'last_activity'	=> $this->now
321							);
322
323
324		// Save the data to the DB if needed
325		if ($this->sess_use_database === TRUE)
326		{
327			$this->CI->db->query($this->CI->db->insert_string($this->sess_table_name, $this->userdata));
328		}
329
330		// Write the cookie
331		$this->_set_cookie();
332	}
333
334	// --------------------------------------------------------------------
335
336	/**
337	 * Update an existing session
338	 *
339	 * @access	public
340	 * @return	void
341	 */
342	function sess_update()
343	{
344		// We only update the session every five minutes by default
345		if (($this->userdata['last_activity'] + $this->sess_time_to_update) >= $this->now)
346		{
347			return;
348		}
349
350		// Save the old session id so we know which record to
351		// update in the database if we need it
352		$old_sessid = $this->userdata['session_id'];
353		$new_sessid = '';
354		while (strlen($new_sessid) < 32)
355		{
356			$new_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		$new_sessid .= $this->CI->input->ip_address();
361
362		// Turn it into a hash
363		$new_sessid = md5(uniqid($new_sessid, TRUE));
364
365		// Update the session data in the session data array
366		$this->userdata['session_id'] = $new_sessid;
367		$this->userdata['last_activity'] = $this->now;
368
369		// _set_cookie() will handle this for us if we aren't using database sessions
370		// by pushing all userdata to the cookie.
371		$cookie_data = NULL;
372
373		// Update the session ID and last_activity field in the DB if needed
374		if ($this->sess_use_database === TRUE)
375		{
376			// set cookie explicitly to only have our session data
377			$cookie_data = array();
378			foreach (array('session_id','ip_address','user_agent','last_activity') as $val)
379			{
380				$cookie_data[$val] = $this->userdata[$val];
381			}
382
383			$this->CI->db->query($this->CI->db->update_string($this->sess_table_name, array('last_activity' => $this->now, 'session_id' => $new_sessid), array('session_id' => $old_sessid)));
384		}
385
386		// Write the cookie
387		$this->_set_cookie($cookie_data);
388	}
389
390	// --------------------------------------------------------------------
391
392	/**
393	 * Destroy the current session
394	 *
395	 * @access	public
396	 * @return	void
397	 */
398	function sess_destroy()
399	{
400		// Kill the session DB row
401		if ($this->sess_use_database === TRUE AND isset($this->userdata['session_id']))
402		{
403			$this->CI->db->where('session_id', $this->userdata['session_id']);
404			$this->CI->db->delete($this->sess_table_name);
405		}
406
407		// Kill the cookie
408		setcookie(
409					$this->sess_cookie_name,
410					addslashes(serialize(array())),
411					($this->now - 31500000),
412					$this->cookie_path,
413					$this->cookie_domain,
414					0
415				);
416	}
417
418	// --------------------------------------------------------------------
419
420	/**
421	 * Fetch a specific item from the session array
422	 *
423	 * @access	public
424	 * @param	string
425	 * @return	string
426	 */
427	function userdata($item)
428	{
429		return ( ! isset($this->userdata[$item])) ? FALSE : $this->userdata[$item];
430	}
431
432	// --------------------------------------------------------------------
433
434	/**
435	 * Fetch all session data
436	 *
437	 * @access	public
438	 * @return	array
439	 */
440	function all_userdata()
441	{
442		return $this->userdata;
443	}
444
445	// --------------------------------------------------------------------
446
447	/**
448	 * Add or change data in the "userdata" array
449	 *
450	 * @access	public
451	 * @param	mixed
452	 * @param	string
453	 * @return	void
454	 */
455	function set_userdata($newdata = array(), $newval = '')
456	{
457		if (is_string($newdata))
458		{
459			$newdata = array($newdata => $newval);
460		}
461
462		if (count($newdata) > 0)
463		{
464			foreach ($newdata as $key => $val)
465			{
466				$this->userdata[$key] = $val;
467			}
468		}
469
470		$this->sess_write();
471	}
472
473	// --------------------------------------------------------------------
474
475	/**
476	 * Delete a session variable from the "userdata" array
477	 *
478	 * @access	array
479	 * @return	void
480	 */
481	function unset_userdata($newdata = array())
482	{
483		if (is_string($newdata))
484		{
485			$newdata = array($newdata => '');
486		}
487
488		if (count($newdata) > 0)
489		{
490			foreach ($newdata as $key => $val)
491			{
492				unset($this->userdata[$key]);
493			}
494		}
495
496		$this->sess_write();
497	}
498
499	// ------------------------------------------------------------------------
500
501	/**
502	 * Add or change flashdata, only available
503	 * until the next request
504	 *
505	 * @access	public
506	 * @param	mixed
507	 * @param	string
508	 * @return	void
509	 */
510	function set_flashdata($newdata = array(), $newval = '')
511	{
512		if (is_string($newdata))
513		{
514			$newdata = array($newdata => $newval);
515		}
516
517		if (count($newdata) > 0)
518		{
519			foreach ($newdata as $key => $val)
520			{
521				$flashdata_key = $this->flashdata_key.':new:'.$key;
522				$this->set_userdata($flashdata_key, $val);
523			}
524		}
525	}
526
527	// ------------------------------------------------------------------------
528
529	/**
530	 * Keeps existing flashdata available to next request.
531	 *
532	 * @access	public
533	 * @param	string
534	 * @return	void
535	 */
536	function keep_flashdata($key)
537	{
538		// 'old' flashdata gets removed.  Here we mark all
539		// flashdata as 'new' to preserve it from _flashdata_sweep()
540		// Note the function will return FALSE if the $key
541		// provided cannot be found
542		$old_flashdata_key = $this->flashdata_key.':old:'.$key;
543		$value = $this->userdata($old_flashdata_key);
544
545		$new_flashdata_key = $this->flashdata_key.':new:'.$key;
546		$this->set_userdata($new_flashdata_key, $value);
547	}
548
549	// ------------------------------------------------------------------------
550
551	/**
552	 * Fetch a specific flashdata item from the session array
553	 *
554	 * @access	public
555	 * @param	string
556	 * @return	string
557	 */
558	function flashdata($key)
559	{
560		$flashdata_key = $this->flashdata_key.':old:'.$key;
561		return $this->userdata($flashdata_key);
562	}
563
564	// ------------------------------------------------------------------------
565
566	/**
567	 * Identifies flashdata as 'old' for removal
568	 * when _flashdata_sweep() runs.
569	 *
570	 * @access	private
571	 * @return	void
572	 */
573	function _flashdata_mark()
574	{
575		$userdata = $this->all_userdata();
576		foreach ($userdata as $name => $value)
577		{
578			$parts = explode(':new:', $name);
579			if (is_array($parts) && count($parts) === 2)
580			{
581				$new_name = $this->flashdata_key.':old:'.$parts[1];
582				$this->set_userdata($new_name, $value);
583				$this->unset_userdata($name);
584			}
585		}
586	}
587
588	// ------------------------------------------------------------------------
589
590	/**
591	 * Removes all flashdata marked as 'old'
592	 *
593	 * @access	private
594	 * @return	void
595	 */
596
597	function _flashdata_sweep()
598	{
599		$userdata = $this->all_userdata();
600		foreach ($userdata as $key => $value)
601		{
602			if (strpos($key, ':old:'))
603			{
604				$this->unset_userdata($key);
605			}
606		}
607
608	}
609
610	// --------------------------------------------------------------------
611
612	/**
613	 * Get the "now" time
614	 *
615	 * @access	private
616	 * @return	string
617	 */
618	function _get_time()
619	{
620		if (strtolower($this->time_reference) == 'gmt')
621		{
622			$now = time();
623			$time = mktime(gmdate("H", $now), gmdate("i", $now), gmdate("s", $now), gmdate("m", $now), gmdate("d", $now), gmdate("Y", $now));
624		}
625		else
626		{
627			$time = time();
628		}
629
630		return $time;
631	}
632
633	// --------------------------------------------------------------------
634
635	/**
636	 * Write the session cookie
637	 *
638	 * @access	public
639	 * @return	void
640	 */
641	function _set_cookie($cookie_data = NULL)
642	{
643		if (is_null($cookie_data))
644		{
645			$cookie_data = $this->userdata;
646		}
647
648		// Serialize the userdata for the cookie
649		$cookie_data = $this->_serialize($cookie_data);
650
651		if ($this->sess_encrypt_cookie == TRUE)
652		{
653			$cookie_data = $this->CI->encrypt->encode($cookie_data);
654		}
655		else
656		{
657			// if encryption is not used, we provide an md5 hash to prevent userside tampering
658			$cookie_data = $cookie_data.md5($cookie_data.$this->encryption_key);
659		}
660
661		$expire = ($this->sess_expire_on_close === TRUE) ? 0 : $this->sess_expiration + time();
662
663		// Set the cookie
664		setcookie(
665					$this->sess_cookie_name,
666					$cookie_data,
667					$expire,
668					$this->cookie_path,
669					$this->cookie_domain,
670					$this->cookie_secure
671				);
672	}
673
674	// --------------------------------------------------------------------
675
676	/**
677	 * Serialize an array
678	 *
679	 * This function first converts any slashes found in the array to a temporary
680	 * marker, so when it gets unserialized the slashes will be preserved
681	 *
682	 * @access	private
683	 * @param	array
684	 * @return	string
685	 */
686	function _serialize($data)
687	{
688		if (is_array($data))
689		{
690			foreach ($data as $key => $val)
691			{
692				if (is_string($val))
693				{
694					$data[$key] = str_replace('\\', '{{slash}}', $val);
695				}
696			}
697		}
698		else
699		{
700			if (is_string($data))
701			{
702				$data = str_replace('\\', '{{slash}}', $data);
703			}
704		}
705
706		return serialize($data);
707	}
708
709	// --------------------------------------------------------------------
710
711	/**
712	 * Unserialize
713	 *
714	 * This function unserializes a data string, then converts any
715	 * temporary slash markers back to actual slashes
716	 *
717	 * @access	private
718	 * @param	array
719	 * @return	string
720	 */
721	function _unserialize($data)
722	{
723		$data = @unserialize(strip_slashes($data));
724
725		if (is_array($data))
726		{
727			foreach ($data as $key => $val)
728			{
729				if (is_string($val))
730				{
731					$data[$key] = str_replace('{{slash}}', '\\', $val);
732				}
733			}
734
735			return $data;
736		}
737
738		return (is_string($data)) ? str_replace('{{slash}}', '\\', $data) : $data;
739	}
740
741	// --------------------------------------------------------------------
742
743	/**
744	 * Garbage collection
745	 *
746	 * This deletes expired session rows from database
747	 * if the probability percentage is met
748	 *
749	 * @access	public
750	 * @return	void
751	 */
752	function _sess_gc()
753	{
754		if ($this->sess_use_database != TRUE)
755		{
756			return;
757		}
758
759		srand(time());
760		if ((rand() % 100) < $this->gc_probability)
761		{
762			$expire = $this->now - $this->sess_expiration;
763
764			$this->CI->db->where("last_activity < {$expire}");
765			$this->CI->db->delete($this->sess_table_name);
766
767			log_message('debug', 'Session garbage collection performed.');
768		}
769	}
770
771
772}
773// END Session Class
774
775/* End of file Session.php */
776/* Location: ./system/libraries/Session.php */