/**
* CI is a session based cookie
*/
class CI_Session {
var $sess_encrypt_cookie = FALSE; // Whether to encrypt session
var $sess_use_database = FALSE; //Whether to save session into the database
var $sess_table_name = ''; //Session table name stored in the data
var $sess_expiration = 7200; //Expiration time of session
var $sess_expire_on_close = FALSE; //Whether the session expires automatically when the browser window is closed
var $sess_match_ip = FALSE;//Whether to read the session data through the user's IP address
var $sess_match_useragent = TRUE; //Whether to read session data according to the corresponding User Agent.
var $sess_cookie_name = 'ci_session'; //cookie name
var $cookie_prefix = ''; //cookie prefix
var $cookie_path = ''; //cookie path
var $cookie_domain = ''; //cookie scope
var $cookie_secure = FALSE; //Is it valid under the secure https protocol
var $sess_time_to_update = 300; //How often does the session cookie be updated
var $encryption_key = ''; //Encryption key
var $flashdata_key = 'flash';
var $time_reference = 'time';
var $gc_probability = 5; //The ability to recycle session
var $userdata = array(); //User session data save variable
var $CI; //CI super handle
var $now; //Current time
public function __construct($params = array())
{
log_message('debug', "Session Class Initialized");
// Get CI super class
$this->CI =& get_instance();
// Get the configuration data in the config file
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)
{
$this->$key = (isset($params[$key])) ? $params[$key] : $this->CI->config->item($key);
}
//Encryption_key must be set
if ($this->encryption_key == '')
{
show_error('In order to use the Session class you are required to set an encryption key in your config file.');
}
// Load the string helper function
$this->CI->load->helper('string');
// D If the cookie is encrypted, the encryption class will be introduced
if ($this->sess_encrypt_cookie == TRUE)
{
$this->CI->load->library('encrypt');
}
// If the session is counted into the database, then db is introduced
if ($this->sess_use_database === TRUE AND $this->sess_table_name != '')
{
$this->CI->load->database();
}
// Get the current time
$this->now = $this->_get_time();
// If the session is not valid, the default is two years.
if ($this->sess_expiration == 0)
{
$this->sess_expiration = (60*60*24*365*2);
}
// Get the cookie name
$this->sess_cookie_name = $this->cookie_prefix.$this->sess_cookie_name;
// If the session does not exist, create a new session
if ( ! $this->sess_read())
{
$this->sess_create();
}
else
{
$this->sess_update();
}
// Flash out the flash data of the old flag
$this->_flashdata_sweep();
//The data marked new flash data as old The data marked old will flash out the next time you request
$this->_flashdata_mark();
//Recycle/Delete Expired session
$this->_sess_gc();
log_message('debug', "Session routines successfully run");
}
// --------------------------------------------------------------------
/**
* Read session data
*/
function sess_read()
{
// Get session
$session = $this->CI->input->cookie($this->sess_cookie_name);
// No session
if ($session === FALSE)
{
log_message('debug', 'A session cookie was not found.');
return FALSE;
}
// If the cookie is encrypted
if ($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 userspace
if ($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;
}
}
// Deserialize the session array stored in the cookie
$session = $this->_unserialize($session);
// Detect session data
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 data expired?
if (($session['last_activity'] + $this->sess_expiration) < $this->now)
{
$this->sess_destroy();
return FALSE;
}
// Whether to read session data based on user ip
if ($this->sess_match_ip == TRUE AND $session['ip_address'] != $this->CI->input->ip_address())
{
$this->sess_destroy();
return FALSE;
}
// Whether to match session data based on ua
if ($this->sess_match_useragent == TRUE AND trim($session['user_agent']) != trim(substr($this->CI->input->user_agent(), 0, 120)))
{
$this->sess_destroy();
return FALSE;
}
// If the session is entered into the database, this method should be safer. Of course, it is not recommended when the load is large, as the database read and write pressure is high.
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 read session data to
*/
function sess_write()
{
// Whether to write to db
if ($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 it
foreach (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 DB
if (count($custom_userdata) === 0)
{
$custom_userdata = '';
}
else
{
// Serialize the custom data array so we can store it
$custom_userdata = $this->_serialize($custom_userdata);
}
// Update session record
$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
*/
function sess_create()
{
//Ensure that sessid is safe and unique
$sessid = '';
while (strlen($sessid) < 32)
{
$sessid .= mt_rand(0, mt_getrandmax());
}
$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, 120),
'last_activity' => $this->now,
'user_data' => ''
);
// Save the data to the DB if needed
if ($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 session
*/
function sess_update()
{
// The default is updated in five minutes
if (($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 probability in the database
if ($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)));
}
// Rewrite session
$this->_set_cookie($cookie_data);
}
// --------------------------------------------------------------------
/**
*Destroy all current session data
*/
function sess_destroy()
{
// Kill the session DB row
if ($this->sess_use_database === TRUE && 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 cookie
setcookie(
$this->sess_cookie_name,
addslashes(serialize(array())),
($this->now - 31500000),
$this->cookie_path,
$this->cookie_domain,
0
);
// Kill session data
$this->userdata = array();
}
// --------------------------------------------------------------------
/**
* Get the value of the specified element in the session array
*/
function userdata($item)
{
return ( ! isset($this->userdata[$item])) ? FALSE : $this->userdata[$item];
}
// --------------------------------------------------------------------
/**
* Get all session data
*/
function all_userdata()
{
return $this->userdata;
}
// --------------------------------------------------------------------
/**
* Add and modify custom session data
*/
function set_userdata($newdata = array(), $newval = '')
{
if (is_string($newdata))
{
$newdata = array($newdata => $newval);
}
//Support array combination method
if (count($newdata) > 0)
{
foreach ($newdata as $key => $val)
{
$this->userdata[$key] = $val;
}
}
$this->sess_write();
}
// --------------------------------------------------------------------
/**
* Delete elements in the session array,
*/
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);
}
}
}
// ------------------------------------------------------------------------
/**
* CI supports flashing data, which means that Session data is only available for the next server request. Sometimes if you still want the request after the next request, it is still valid. . .
* keep_flashdata function is to keep flashing data continuously so that it is also valid in the next request.
*/
function keep_flashdata($key)
{
//Tag the flash data as new
$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);
}
// ------------------------------------------------------------------------
/**
* Get flash data
*/
function flashdata($key)
{
$flashdata_key = $this->flashdata_key.':old:'.$key;
return $this->userdata($flashdata_key);
}
// ------------------------------------------------------------------------
/**
* Mark the flash data as old so that _flashdata_sweep clears the data
*/
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);
}
}
}
// ------------------------------------------------------------------------
/**
* Flash out the flash data marked old
*/
function _flashdata_sweep()
{
$userdata = $this->all_userdata();
foreach ($userdata as $key => $value)
{
if (strpos($key, ':old:'))
{
$this->unset_userdata($key);
}
}
}
//Get the current time
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 session cookie
*
*/
function _set_cookie($cookie_data = NULL)
{
if (is_null($cookie_data))
{
$cookie_data = $this->userdata;
}
// Serialize array
$cookie_data = $this->_serialize($cookie_data);
//Encrypted 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);
}
//Sess_expire_on_close is TRUE, the browser is closed, and the session is invalid
$expire = ($this->sess_expire_on_close === TRUE) ? 0 : $this->sess_expiration + time();
// Set the cookie
setcookie(
$this->sess_cookie_name,
$cookie_data,
$expire,
$this->cookie_path,
$this->cookie_domain,
$this->cookie_secure
);
}
// --------------------------------------------------------------------
/**
* Serialize array
*/
function _serialize($data)
{
if (is_array($data))
{
foreach ($data as $key => $val)
{
if (is_string($val))
{
$data[$key] = str_replace('\\', '{{slash}}', $val);
}
}
}
else
{
if (is_string($data))
{
$data = str_replace('\\', '{{slash}}', $data);
}
}
return serialize($data);
}
// --------------------------------------------------------------------
/**
* Deserialize arrays
*/
function _unserialize($data)
{
$data = @unserialize(strip_slashes($data));
if (is_array($data))
{
foreach ($data as $key => $val)
{
if (is_string($val))
{
$data[$key] = str_replace('{{slash}}', '\\', $val);
}
}
return $data;
}
return (is_string($data)) ? str_replace('{{slash}}', '\\', $data) : $data;
}
// --------------------------------------------------------------------
/**
* Recycle/delete invalid session information in the database
*/
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.');
}
}
}