Blame | Last modification | View Log | RSS feed
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');/*** CodeIgniter** An open source application development framework for PHP 4.3.2 or newer** @package CodeIgniter* @author ExpressionEngine Dev Team* @copyright Copyright (c) 2008, EllisLab, Inc.* @license http://codeigniter.com/user_guide/license.html* @link http://codeigniter.com* @since Version 1.0* @filesource*/// ------------------------------------------------------------------------/*** Session Class** @package CodeIgniter* @subpackage Libraries* @category Sessions* @author ExpressionEngine Dev Team* @link http://codeigniter.com/user_guide/libraries/sessions.html*/class CI_Session {var $sess_encrypt_cookie = FALSE;var $sess_use_database = FALSE;var $sess_table_name = '';var $sess_expiration = 7200;var $sess_match_ip = FALSE;var $sess_match_useragent = TRUE;var $sess_cookie_name = 'ci_session';var $cookie_prefix = '';var $cookie_path = '';var $cookie_domain = '';var $sess_time_to_update = 300;var $encryption_key = '';var $flashdata_key = 'flash';var $time_reference = 'time';var $gc_probability = 5;var $userdata = array();var $CI;var $now;/*** Session Constructor** The constructor runs the session routines automatically* whenever the class is instantiated.*/function CI_Session($params = array()){log_message('debug', "Session Class Initialized");// Set the super object to a local variable for use throughout the class$this->CI =& get_instance();// Set all the session preferences, which can either be set// manually via the $params array above or via the config fileforeach (array('sess_encrypt_cookie', 'sess_use_database', 'sess_table_name', 'sess_expiration', 'sess_match_ip', 'sess_match_useragent', 'sess_cookie_name', 'cookie_path', 'cookie_domain', 'sess_time_to_update', 'time_reference', 'cookie_prefix', 'encryption_key') as $key){$this->$key = (isset($params[$key])) ? $params[$key] : $this->CI->config->item($key);}// Load the string helper so we can use the strip_slashes() function$this->CI->load->helper('string');// Do we need encryption? If so, load the encryption classif ($this->sess_encrypt_cookie == TRUE){$this->CI->load->library('encrypt');}// Are we using a database? If so, load itif ($this->sess_use_database === TRUE AND $this->sess_table_name != ''){$this->CI->load->database();}// Set the "now" time. Can either be GMT or server time, based on the// config prefs. We use this to set the "last activity" time$this->now = $this->_get_time();// Set the session length. If the session expiration is// set to zero we'll set the expiration two years from now.if ($this->sess_expiration == 0){$this->sess_expiration = (60*60*24*365*2);}// Set the cookie name$this->sess_cookie_name = $this->cookie_prefix.$this->sess_cookie_name;// Run the Session routine. If a session doesn't exist we'll// create a new one. If it does, we'll update it.if ( ! $this->sess_read()){$this->sess_create();}else{$this->sess_update();}// Delete 'old' flashdata (from last request)$this->_flashdata_sweep();// Mark all new flashdata as old (data will be deleted before next request)$this->_flashdata_mark();// Delete expired sessions if necessary$this->_sess_gc();log_message('debug', "Session routines successfully run");}// --------------------------------------------------------------------/*** Fetch the current session data if it exists** @access public* @return void*/function sess_read(){// Fetch the cookie$session = $this->CI->input->cookie($this->sess_cookie_name);// No cookie? Goodbye cruel world!...if ($session === FALSE){log_message('debug', 'A session cookie was not found.');return FALSE;}// Decrypt the cookie dataif ($this->sess_encrypt_cookie == TRUE){$session = $this->CI->encrypt->decode($session);}else{// encryption was not used, so we need to check the md5 hash$hash = substr($session, strlen($session)-32); // get last 32 chars$session = substr($session, 0, strlen($session)-32);// Does the md5 hash match? This is to prevent manipulation of session data in userspaceif ($hash !== md5($session.$this->encryption_key)){log_message('error', 'The session cookie data did not match what was expected. This could be a possible hacking attempt.');$this->sess_destroy();return FALSE;}}// Unserialize the session array$session = $this->_unserialize($session);// Is the session data we unserialized an array with the correct format?if ( ! is_array($session) OR ! isset($session['session_id']) OR ! isset($session['ip_address']) OR ! isset($session['user_agent']) OR ! isset($session['last_activity'])){$this->sess_destroy();return FALSE;}// Is the session current?if (($session['last_activity'] + $this->sess_expiration) < $this->now){$this->sess_destroy();return FALSE;}// Does the IP Match?if ($this->sess_match_ip == TRUE AND $session['ip_address'] != $this->CI->input->ip_address()){$this->sess_destroy();return FALSE;}// Does the User Agent Match?if ($this->sess_match_useragent == TRUE AND trim($session['user_agent']) != trim(substr($this->CI->input->user_agent(), 0, 50))){$this->sess_destroy();return FALSE;}// Is there a corresponding session in the DB?if ($this->sess_use_database === TRUE){$this->CI->db->where('session_id', $session['session_id']);if ($this->sess_match_ip == TRUE){$this->CI->db->where('ip_address', $session['ip_address']);}if ($this->sess_match_useragent == TRUE){$this->CI->db->where('user_agent', $session['user_agent']);}$query = $this->CI->db->get($this->sess_table_name);// No result? Kill it!if ($query->num_rows() == 0){$this->sess_destroy();return FALSE;}// Is there custom data? If so, add it to the main session array$row = $query->row();if (isset($row->user_data) AND $row->user_data != ''){$custom_data = $this->_unserialize($row->user_data);if (is_array($custom_data)){foreach ($custom_data as $key => $val){$session[$key] = $val;}}}}// Session is valid!$this->userdata = $session;unset($session);return TRUE;}// --------------------------------------------------------------------/*** Write the session data** @access public* @return void*/function sess_write(){// Are we saving custom data to the DB? If not, all we do is update the cookieif ($this->sess_use_database === FALSE){$this->_set_cookie();return;}// set the custom userdata, the session data we will set in a second$custom_userdata = $this->userdata;$cookie_userdata = array();// Before continuing, we need to determine if there is any custom data to deal with.// Let's determine this by removing the default indexes to see if there's anything left in the array// and set the session data while we're at itforeach (array('session_id','ip_address','user_agent','last_activity') as $val){unset($custom_userdata[$val]);$cookie_userdata[$val] = $this->userdata[$val];}// Did we find any custom data? If not, we turn the empty array into a string// since there's no reason to serialize and store an empty array in the DBif (count($custom_userdata) === 0){$custom_userdata = '';}else{// Serialize the custom data array so we can store it$custom_userdata = $this->_serialize($custom_userdata);}// Run the update query$this->CI->db->where('session_id', $this->userdata['session_id']);$this->CI->db->update($this->sess_table_name, array('last_activity' => $this->userdata['last_activity'], 'user_data' => $custom_userdata));// Write the cookie. Notice that we manually pass the cookie data array to the// _set_cookie() function. Normally that function will store $this->userdata, but// in this case that array contains custom data, which we do not want in the cookie.$this->_set_cookie($cookie_userdata);}// --------------------------------------------------------------------/*** Create a new session** @access public* @return void*/function sess_create(){$sessid = '';while (strlen($sessid) < 32){$sessid .= mt_rand(0, mt_getrandmax());}// To make the session ID even more secure we'll combine it with the user's IP$sessid .= $this->CI->input->ip_address();$this->userdata = array('session_id' => md5(uniqid($sessid, TRUE)),'ip_address' => $this->CI->input->ip_address(),'user_agent' => substr($this->CI->input->user_agent(), 0, 50),'last_activity' => $this->now);// Save the data to the DB if neededif ($this->sess_use_database === TRUE){$this->CI->db->query($this->CI->db->insert_string($this->sess_table_name, $this->userdata));}// Write the cookie$this->_set_cookie();}// --------------------------------------------------------------------/*** Update an existing session** @access public* @return void*/function sess_update(){// We only update the session every five minutes by defaultif (($this->userdata['last_activity'] + $this->sess_time_to_update) >= $this->now){return;}// Save the old session id so we know which record to// update in the database if we need it$old_sessid = $this->userdata['session_id'];$new_sessid = '';while (strlen($new_sessid) < 32){$new_sessid .= mt_rand(0, mt_getrandmax());}// To make the session ID even more secure we'll combine it with the user's IP$new_sessid .= $this->CI->input->ip_address();// Turn it into a hash$new_sessid = md5(uniqid($new_sessid, TRUE));// Update the session data in the session data array$this->userdata['session_id'] = $new_sessid;$this->userdata['last_activity'] = $this->now;// _set_cookie() will handle this for us if we aren't using database sessions// by pushing all userdata to the cookie.$cookie_data = NULL;// Update the session ID and last_activity field in the DB if neededif ($this->sess_use_database === TRUE){// set cookie explicitly to only have our session data$cookie_data = array();foreach (array('session_id','ip_address','user_agent','last_activity') as $val){$cookie_data[$val] = $this->userdata[$val];}$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)));}// Write the cookie$this->_set_cookie($cookie_data);}// --------------------------------------------------------------------/*** Destroy the current session** @access public* @return void*/function sess_destroy(){// Kill the session DB rowif ($this->sess_use_database === TRUE AND isset($this->userdata['session_id'])){$this->CI->db->where('session_id', $this->userdata['session_id']);$this->CI->db->delete($this->sess_table_name);}// Kill the cookiesetcookie($this->sess_cookie_name,addslashes(serialize(array())),($this->now - 31500000),$this->cookie_path,$this->cookie_domain,0);}// --------------------------------------------------------------------/*** Fetch a specific item from the session array** @access public* @param string* @return string*/function userdata($item){return ( ! isset($this->userdata[$item])) ? FALSE : $this->userdata[$item];}// --------------------------------------------------------------------/*** Fetch all session data** @access public* @return mixed*/function all_userdata(){return ( ! isset($this->userdata)) ? FALSE : $this->userdata;}// --------------------------------------------------------------------/*** Add or change data in the "userdata" array** @access public* @param mixed* @param string* @return void*/function set_userdata($newdata = array(), $newval = ''){if (is_string($newdata)){$newdata = array($newdata => $newval);}if (count($newdata) > 0){foreach ($newdata as $key => $val){$this->userdata[$key] = $val;}}$this->sess_write();}// --------------------------------------------------------------------/*** Delete a session variable from the "userdata" array** @access array* @return void*/function unset_userdata($newdata = array()){if (is_string($newdata)){$newdata = array($newdata => '');}if (count($newdata) > 0){foreach ($newdata as $key => $val){unset($this->userdata[$key]);}}$this->sess_write();}// ------------------------------------------------------------------------/*** Add or change flashdata, only available* until the next request** @access public* @param mixed* @param string* @return void*/function set_flashdata($newdata = array(), $newval = ''){if (is_string($newdata)){$newdata = array($newdata => $newval);}if (count($newdata) > 0){foreach ($newdata as $key => $val){$flashdata_key = $this->flashdata_key.':new:'.$key;$this->set_userdata($flashdata_key, $val);}}}// ------------------------------------------------------------------------/*** Keeps existing flashdata available to next request.** @access public* @param string* @return void*/function keep_flashdata($key){// 'old' flashdata gets removed. Here we mark all// flashdata as 'new' to preserve it from _flashdata_sweep()// Note the function will return FALSE if the $key// provided cannot be found$old_flashdata_key = $this->flashdata_key.':old:'.$key;$value = $this->userdata($old_flashdata_key);$new_flashdata_key = $this->flashdata_key.':new:'.$key;$this->set_userdata($new_flashdata_key, $value);}// ------------------------------------------------------------------------/*** Fetch a specific flashdata item from the session array** @access public* @param string* @return string*/function flashdata($key){$flashdata_key = $this->flashdata_key.':old:'.$key;return $this->userdata($flashdata_key);}// ------------------------------------------------------------------------/*** Identifies flashdata as 'old' for removal* when _flashdata_sweep() runs.** @access private* @return void*/function _flashdata_mark(){$userdata = $this->all_userdata();foreach ($userdata as $name => $value){$parts = explode(':new:', $name);if (is_array($parts) && count($parts) === 2){$new_name = $this->flashdata_key.':old:'.$parts[1];$this->set_userdata($new_name, $value);$this->unset_userdata($name);}}}// ------------------------------------------------------------------------/*** Removes all flashdata marked as 'old'** @access private* @return void*/function _flashdata_sweep(){$userdata = $this->all_userdata();foreach ($userdata as $key => $value){if (strpos($key, ':old:')){$this->unset_userdata($key);}}}// --------------------------------------------------------------------/*** Get the "now" time** @access private* @return string*/function _get_time(){if (strtolower($this->time_reference) == 'gmt'){$now = time();$time = mktime(gmdate("H", $now), gmdate("i", $now), gmdate("s", $now), gmdate("m", $now), gmdate("d", $now), gmdate("Y", $now));}else{$time = time();}return $time;}// --------------------------------------------------------------------/*** Write the session cookie** @access public* @return void*/function _set_cookie($cookie_data = NULL){if (is_null($cookie_data)){$cookie_data = $this->userdata;}// Serialize the userdata for the cookie$cookie_data = $this->_serialize($cookie_data);if ($this->sess_encrypt_cookie == TRUE){$cookie_data = $this->CI->encrypt->encode($cookie_data);}else{// if encryption is not used, we provide an md5 hash to prevent userside tampering$cookie_data = $cookie_data.md5($cookie_data.$this->encryption_key);}// Set the cookiesetcookie($this->sess_cookie_name,$cookie_data,$this->sess_expiration + time(),$this->cookie_path,$this->cookie_domain,0);}// --------------------------------------------------------------------/*** Serialize an array** This function first converts any slashes found in the array to a temporary* marker, so when it gets unserialized the slashes will be preserved** @access private* @param array* @return string*/function _serialize($data){if (is_array($data)){foreach ($data as $key => $val){$data[$key] = str_replace('\\', '{{slash}}', $val);}}else{$data = str_replace('\\', '{{slash}}', $data);}return serialize($data);}// --------------------------------------------------------------------/*** Unserialize** This function unserializes a data string, then converts any* temporary slash markers back to actual slashes** @access private* @param array* @return string*/function _unserialize($data){$data = @unserialize(strip_slashes($data));if (is_array($data)){foreach ($data as $key => $val){$data[$key] = str_replace('{{slash}}', '\\', $val);}return $data;}return str_replace('{{slash}}', '\\', $data);}// --------------------------------------------------------------------/*** Garbage collection** This deletes expired session rows from database* if the probability percentage is met** @access public* @return void*/function _sess_gc(){if ($this->sess_use_database != TRUE){return;}srand(time());if ((rand() % 100) < $this->gc_probability){$expire = $this->now - $this->sess_expiration;$this->CI->db->where("last_activity < {$expire}");$this->CI->db->delete($this->sess_table_name);log_message('debug', 'Session garbage collection performed.');}}}// END Session Class/* End of file Session.php *//* Location: ./system/libraries/Session.php */