diff options
author | Phil Sturgeon <email@philsturgeon.co.uk> | 2012-08-29 15:53:57 +0200 |
---|---|---|
committer | Phil Sturgeon <email@philsturgeon.co.uk> | 2012-08-29 15:53:57 +0200 |
commit | 1e40c213610a4bb643e51c2b298629471664163b (patch) | |
tree | 79a3e038ab0a8e3ec7a3c878eb05eb9cd9c44fa1 /system/libraries | |
parent | 2d5c209943ef1e2644504e8d5cb32d3d66b45632 (diff) | |
parent | 88b636b06dd91807706e2d442d910fe8b6a3b50c (diff) |
Merge pull request #353 from dchill42/session
Switched Sessions to use Drivers
Adding native PHP sessions and keeping original-flavor CI cookie sessions
Diffstat (limited to 'system/libraries')
-rw-r--r-- | system/libraries/Driver.php | 22 | ||||
-rw-r--r-- | system/libraries/Session.php | 955 | ||||
-rwxr-xr-x | system/libraries/Session/Session.php | 630 | ||||
-rwxr-xr-x | system/libraries/Session/drivers/Session_cookie.php | 799 | ||||
-rwxr-xr-x | system/libraries/Session/drivers/Session_native.php | 211 |
5 files changed, 1659 insertions, 958 deletions
diff --git a/system/libraries/Driver.php b/system/libraries/Driver.php index d67ee2549..1d084c8e4 100644 --- a/system/libraries/Driver.php +++ b/system/libraries/Driver.php @@ -54,13 +54,29 @@ class CI_Driver_Library { protected $lib_name; /** + * Get magic method + * * The first time a child is used it won't exist, so we instantiate it * subsequents calls will go straight to the proper child. * - * @param mixed $child - * @return mixed + * @param string Child class name + * @return object Child class */ public function __get($child) + { + // Try to load the driver + return $this->load_driver($child); + } + + /** + * Load driver + * + * Separate load_driver call to support explicit driver load by library or user + * + * @param string Child class name + * @return object Child class + */ + public function load_driver($child) { if ( ! isset($this->lib_name)) { @@ -268,4 +284,4 @@ class CI_Driver { } /* End of file Driver.php */ -/* Location: ./system/libraries/Driver.php */
\ No newline at end of file +/* Location: ./system/libraries/Driver.php */ diff --git a/system/libraries/Session.php b/system/libraries/Session.php deleted file mode 100644 index af38dc366..000000000 --- a/system/libraries/Session.php +++ /dev/null @@ -1,955 +0,0 @@ -<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); -/** - * CodeIgniter - * - * An open source application development framework for PHP 5.2.4 or newer - * - * NOTICE OF LICENSE - * - * Licensed under the Open Software License version 3.0 - * - * This source file is subject to the Open Software License (OSL 3.0) that is - * bundled with this package in the files license.txt / license.rst. It is - * also available through the world wide web at this URL: - * http://opensource.org/licenses/OSL-3.0 - * If you did not receive a copy of the license and are unable to obtain it - * through the world wide web, please send an email to - * licensing@ellislab.com so we can send you a copy immediately. - * - * @package CodeIgniter - * @author EllisLab Dev Team - * @copyright Copyright (c) 2008 - 2012, EllisLab, Inc. (http://ellislab.com/) - * @license http://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) - * @link http://codeigniter.com - * @since Version 1.0 - * @filesource - */ - -/** - * Session Class - * - * @package CodeIgniter - * @subpackage Libraries - * @category Sessions - * @author EllisLab Dev Team - * @link http://codeigniter.com/user_guide/libraries/sessions.html - */ -class CI_Session { - - /** - * Whether to encrypt the session cookie - * - * @var bool - */ - public $sess_encrypt_cookie = FALSE; - - /** - * Whether to use to the database for session storage - * - * @var bool - */ - public $sess_use_database = FALSE; - - /** - * Name of the database table in which to store sessions - * - * @var string - */ - public $sess_table_name = ''; - - /** - * Length of time (in seconds) for sessions to expire - * - * @var int - */ - public $sess_expiration = 7200; - - /** - * Whether to kill session on close of browser window - * - * @var bool - */ - public $sess_expire_on_close = FALSE; - - /** - * Whether to match session on ip address - * - * @var bool - */ - public $sess_match_ip = FALSE; - - /** - * Whether to match session on user-agent - * - * @var bool - */ - public $sess_match_useragent = TRUE; - - /** - * Name of session cookie - * - * @var string - */ - public $sess_cookie_name = 'ci_session'; - - /** - * Session cookie prefix - * - * @var string - */ - public $cookie_prefix = ''; - - /** - * Session cookie path - * - * @var string - */ - public $cookie_path = ''; - - /** - * Session cookie domain - * - * @var string - */ - public $cookie_domain = ''; - - /** - * Whether to set the cookie only on HTTPS connections - * - * @var bool - */ - public $cookie_secure = FALSE; - - /** - * Whether cookie should be allowed only to be sent by the server - * - * @var bool - */ - public $cookie_httponly = FALSE; - - /** - * Interval at which to update session - * - * @var int - */ - public $sess_time_to_update = 300; - - /** - * Key with which to encrypt the session cookie - * - * @var string - */ - public $encryption_key = ''; - - /** - * String to indicate flash data cookies - * - * @var string - */ - public $flashdata_key = 'flash'; - - /** - * Timezone to use for the current time - * - * @var string - */ - public $time_reference = 'local'; - - - /** - * Session data - * - * @var array - */ - public $userdata = array(); - - /** - * Reference to CodeIgniter instance - * - * @var object - */ - public $CI; - - /** - * Current time - * - * @var int - */ - public $now; - - /** - * Session Constructor - * - * The constructor runs the session routines automatically - * whenever the class is instantiated. - * - * @param array - * @return void - */ - public function __construct($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 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', 'cookie_httponly', 'sess_time_to_update', 'time_reference', 'cookie_prefix', 'encryption_key') as $key) - { - $this->$key = isset($params[$key]) ? $params[$key] : $this->CI->config->item($key); - } - - 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 so we can use the strip_slashes() function - $this->CI->load->helper('string'); - - // Do we need encryption? If so, load the encryption class - if ($this->sess_encrypt_cookie === TRUE) - { - $this->CI->load->library('encrypt'); - } - - // Are we using a database? If so, load it - if ($this->sess_use_database === TRUE && $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 - * - * @return bool - */ - public function sess_read() - { - // Fetch the cookie - $session = $this->CI->input->cookie($this->sess_cookie_name); - - // No cookie? Goodbye cruel world!... - if ($session === NULL) - { - log_message('debug', 'A session cookie was not found.'); - return FALSE; - } - - // Decrypt the cookie data - 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; - } - } - - // 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'], $session['ip_address'], $session['user_agent'], $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 && $session['ip_address'] !== $this->CI->input->ip_address()) - { - $this->sess_destroy(); - return FALSE; - } - - // Does the User Agent Match? - if ($this->sess_match_useragent === TRUE && trim($session['user_agent']) !== trim(substr($this->CI->input->user_agent(), 0, 120))) - { - $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->limit(1)->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 ( ! empty($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 - * - * @return void - */ - public function sess_write() - { - // Are we saving custom data to the DB? If not, all we do is update the cookie - 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); - } - - // 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 - * - * @return void - */ - public function sess_create() - { - $sessid = ''; - do - { - $sessid .= mt_rand(0, mt_getrandmax()); - } - while (strlen($sessid) < 32); - - // 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, 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 an existing session - * - * @return void - */ - public function sess_update() - { - // We only update the session every five minutes by default - if (($this->userdata['last_activity'] + $this->sess_time_to_update) >= $this->now) - { - return; - } - - // _set_cookie() will handle this for us if we aren't using database sessions - // by pushing all userdata to the cookie. - $cookie_data = NULL; - - /* Changing the session ID during an AJAX call causes problems, - * so we'll only update our last_activity - */ - if ($this->CI->input->is_ajax_request()) - { - $this->userdata['last_activity'] = $this->now; - - // Update the session ID and last_activity field in the DB if needed - 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->userdata['last_activity']), - array('session_id' => $this->userdata['session_id']))); - } - - return $this->_set_cookie($cookie_data); - } - - // 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 = ''; - do - { - $new_sessid .= mt_rand(0, mt_getrandmax()); - } - while (strlen($new_sessid) < 32); - - // 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 and update the session data array - $this->userdata['session_id'] = $new_sessid = md5(uniqid($new_sessid, TRUE)); - $this->userdata['last_activity'] = $this->now; - - // Update the session ID and last_activity field in the DB if needed - 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))); - } - - // Write the cookie - $this->_set_cookie($cookie_data); - } - - // -------------------------------------------------------------------- - - /** - * Destroy the current session - * - * @return void - */ - public 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(); - } - - // -------------------------------------------------------------------- - - /** - * Fetch a specific item from the session array - * - * @param string - * @return string - */ - public function userdata($item) - { - return isset($this->userdata[$item]) ? $this->userdata[$item] : NULL; - } - - // -------------------------------------------------------------------- - - /** - * Fetch all session data - * - * @return array - */ - public function all_userdata() - { - return $this->userdata; - } - - // -------------------------------------------------------------------------- - - /** - * Fetch all flashdata - * - * @return array - */ - public function all_flashdata() - { - $out = array(); - - // loop through all userdata - foreach ($this->all_userdata() as $key => $val) - { - // if it contains flashdata, add it - if (strpos($key, 'flash:old:') !== FALSE) - { - $out[$key] = $val; - } - } - return $out; - } - - // -------------------------------------------------------------------- - - /** - * Add or change data in the "userdata" array - * - * @param mixed - * @param string - * @return void - */ - public 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 - * - * @param array - * @return void - */ - public 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 - * - * @param mixed - * @param string - * @return void - */ - public function set_flashdata($newdata = array(), $newval = '') - { - if (is_string($newdata)) - { - $newdata = array($newdata => $newval); - } - - if (count($newdata) > 0) - { - foreach ($newdata as $key => $val) - { - $this->set_userdata($this->flashdata_key.':new:'.$key, $val); - } - } - } - - // ------------------------------------------------------------------------ - - /** - * Keeps existing flashdata available to next request. - * - * @param string - * @return void - */ - public 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 NULL if the $key - // provided cannot be found - $value = $this->userdata($this->flashdata_key.':old:'.$key); - - $this->set_userdata($this->flashdata_key.':new:'.$key, $value); - } - - // ------------------------------------------------------------------------ - - /** - * Fetch a specific flashdata item from the session array - * - * @param string - * @return string - */ - public function flashdata($key) - { - return $this->userdata($this->flashdata_key.':old:'.$key); - } - - // ------------------------------------------------------------------------ - - /** - * Identifies flashdata as 'old' for removal - * when _flashdata_sweep() runs. - * - * @return void - */ - protected function _flashdata_mark() - { - $userdata = $this->all_userdata(); - foreach ($userdata as $name => $value) - { - $parts = explode(':new:', $name); - if (is_array($parts) && count($parts) === 2) - { - $this->set_userdata($this->flashdata_key.':old:'.$parts[1], $value); - $this->unset_userdata($name); - } - } - } - - // ------------------------------------------------------------------------ - - /** - * Removes all flashdata marked as 'old' - * - * @return void - */ - protected function _flashdata_sweep() - { - $userdata = $this->all_userdata(); - foreach ($userdata as $key => $value) - { - if (strpos($key, ':old:')) - { - $this->unset_userdata($key); - } - } - - } - - // -------------------------------------------------------------------- - - /** - * Get the "now" time - * - * @return string - */ - protected function _get_time() - { - if ($this->time_reference === 'local' OR $this->time_reference === date_default_timezone_get()) - { - return time(); - } - - $datetime = new DateTime('now', new DateTimeZone($this->time_reference)); - sscanf($datetime->format('j-n-Y G:i:s'), '%d-%d-%d %d:%d:%d', $day, $month, $year, $hour, $minute, $second); - - return mktime($hour, $minute, $second, $month, $day, $year); - } - - // -------------------------------------------------------------------- - - /** - * Write the session cookie - * - * @param mixed - * @return void - */ - protected 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); - } - - $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, - $this->cookie_httponly - ); - } - - // -------------------------------------------------------------------- - - /** - * 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 - * - * @param array - * @return string - */ - protected function _serialize($data) - { - if (is_array($data)) - { - array_walk_recursive($data, array(&$this, '_escape_slashes')); - } - elseif (is_string($data)) - { - $data = str_replace('\\', '{{slash}}', $data); - } - return serialize($data); - } - - /** - * Escape slashes - * - * This function converts any slashes found into a temporary marker - * - * @param string - * @param string - * @return void - */ - protected function _escape_slashes(&$val, $key) - { - if (is_string($val)) - { - $val = str_replace('\\', '{{slash}}', $val); - } - } - - // -------------------------------------------------------------------- - - /** - * Unserialize - * - * This function unserializes a data string, then converts any - * temporary slash markers back to actual slashes - * - * @param array - * @return string - */ - protected function _unserialize($data) - { - $data = @unserialize(strip_slashes(trim($data))); - - if (is_array($data)) - { - array_walk_recursive($data, array(&$this, '_unescape_slashes')); - return $data; - } - - return is_string($data) ? str_replace('{{slash}}', '\\', $data) : $data; - } - - // -------------------------------------------------------------------- - - /** - * Unescape slashes - * - * This function converts any slash markers back into actual slashes - * - * @param string - * @param string - * @return void - */ - protected function _unescape_slashes(&$val, $key) - { - if (is_string($val)) - { - $val= str_replace('{{slash}}', '\\', $val); - } - } - - // -------------------------------------------------------------------- - - /** - * Garbage collection - * - * This deletes expired session rows from database - * if the probability percentage is met - * - * @return void - */ - protected function _sess_gc() - { - if ($this->sess_use_database !== TRUE) - { - return; - } - - $probability = ini_get('session.gc_probability'); - $divisor = ini_get('session.gc_divisor'); - - srand(time()); - if ((mt_rand(0, $divisor) / $divisor) < $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 of file Session.php */ -/* Location: ./system/libraries/Session.php */
\ No newline at end of file diff --git a/system/libraries/Session/Session.php b/system/libraries/Session/Session.php new file mode 100755 index 000000000..1f24456a4 --- /dev/null +++ b/system/libraries/Session/Session.php @@ -0,0 +1,630 @@ +<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); +/** + * CodeIgniter + * + * An open source application development framework for PHP 5.1.6 or newer + * + * @package CodeIgniter + * @author ExpressionEngine Dev Team + * @copyright Copyright (c) 2008 - 2010, EllisLab, Inc. + * @license http://codeigniter.com/user_guide/license.html + * @link http://codeigniter.com + * @since Version 2.0 + * @filesource + */ + + +/** + * CI_Session Class + * + * The user interface defined by EllisLabs, now with puggable drivers to manage different storage mechanisms. + * By default, the cookie session driver will load, but the 'sess_driver' config/param item (see above) can be + * used to specify the 'native' driver, or any other you might create. + * Once loaded, this driver setup is a drop-in replacement for the former CI_Session library, taking its place as the + * 'session' member of the global controller framework (e.g.: $CI->session or $this->session). + * In keeping with the CI_Driver methodology, multiple drivers may be loaded, although this might be a bit confusing. + * The CI_Session library class keeps track of the most recently loaded driver as "current" to call for driver methods. + * Ideally, one driver is loaded and all calls go directly through the main library interface. However, any methods + * called through the specific driver will switch the "current" driver to itself before invoking the library method + * (which will then call back into the driver for low-level operations). So, alternation between two drivers can be + * achieved by specifying which driver to use for each call (e.g.: $this->session->native->set_userdata('foo', 'bar'); + * $this->session->cookie->userdata('foo'); $this->session->native->unset_userdata('foo');). Notice in the previous + * example that the _native_ userdata value 'foo' would be set to 'bar', which would NOT be returned by the call for + * the _cookie_ userdata 'foo', nor would the _cookie_ value be unset by the call to unset the _native_ 'foo' value. + * + * @package CodeIgniter + * @subpackage Libraries + * @category Sessions + * @author ExpressionEngine Dev Team + * @link http://codeigniter.com/user_guide/libraries/sessions.html + */ +class CI_Session extends CI_Driver_Library { + public $params = array(); + protected $current = null; + protected $userdata = array(); + + const FLASHDATA_KEY = 'flash'; + const FLASHDATA_NEW = ':new:'; + const FLASHDATA_OLD = ':old:'; + const FLASHDATA_EXP = ':exp:'; + const EXPIRATION_KEY = '__expirations'; + const TEMP_EXP_DEF = 300; + + /** + * CI_Session constructor + * + * The constructor loads the configured driver ('sess_driver' in config.php or as a parameter), running + * routines in its constructor, and manages flashdata aging. + * + * @param array Configuration parameters + */ + public function __construct(array $params = array()) + { + log_message('debug', 'CI_Session Class Initialized'); + + // Get valid drivers list + $CI =& get_instance(); + $this->valid_drivers = array( + 'Session_native', + 'Session_cookie' + ); + $key = 'sess_valid_drivers'; + $drivers = (isset($params[$key])) ? $params[$key] : $CI->config->item($key); + if ($drivers) + { + if ( ! is_array($drivers)) $drivers = array($drivers); + + // Add driver names to valid list + foreach ($drivers as $driver) + { + if ( ! in_array(strtolower($driver), array_map('strtolower', $this->valid_drivers))) + { + $this->valid_drivers[] = $driver; + } + } + } + + // Get driver to load + $key = 'sess_driver'; + $driver = (isset($params[$key])) ? $params[$key] : $CI->config->item($key); + if ( ! $driver) $driver = 'cookie'; + if ( ! in_array('session_'.strtolower($driver), array_map('strtolower', $this->valid_drivers))) + { + $this->valid_drivers[] = 'Session_'.$driver; + } + + // Save a copy of parameters in case drivers need access + $this->params = $params; + + // Load driver and get array reference + $this->load_driver($driver); + + // 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 tempdata + $this->_tempdata_sweep(); + + log_message('debug', 'CI_Session routines successfully run'); + } + + /** + * Loads session storage driver + * + * @param string Driver classname + * @return object Loaded driver object + */ + public function load_driver($driver) + { + // Save reference to most recently loaded driver as library default and sync userdata + $this->current = parent::load_driver($driver); + $this->userdata =& $this->current->get_userdata(); + return $this->current; + } + + /** + * Select default session storage driver + * + * @param string Driver classname + * @return void + */ + public function select_driver($driver) + { + // Validate driver name + $lowername = strtolower(str_replace('CI_', '', $driver)); + if (in_array($lowername, array_map('strtolower', $this->valid_drivers))) + { + // See if driver is loaded + $child = str_replace($this->lib_name.'_', '', $driver); + if (isset($this->$child)) + { + // See if driver is already current + if ($this->$child !== $this->current) { + // Make driver current and sync userdata + $this->current = $this->$child; + $this->userdata =& $this->current->get_userdata(); + } + } + else + { + // Load new driver + $this->load_driver($child); + } + } + } + + /** + * Destroy the current session + * + * @return void + */ + public function sess_destroy() + { + // Just call destroy on driver + $this->current->sess_destroy(); + } + + /** + * Regenerate the current session + * + * @param boolean Destroy session data flag (default: false) + * @return void + */ + public function sess_regenerate($destroy = false) + { + // Call regenerate on driver and resync userdata + $this->current->sess_regenerate($destroy); + $this->userdata =& $this->current->get_userdata(); + } + + /** + * Fetch a specific item from the session array + * + * @param string Item key + * @return string Item value or NULL if not found + */ + public function userdata($item) + { + // Return value or NULL if not found + return ( ! isset($this->userdata[$item])) ? NULL : $this->userdata[$item]; + } + + /** + * Fetch all session data + * + * @return array User data array + */ + public function all_userdata() + { + // Return entire array + return ( ! isset($this->userdata)) ? NULL : $this->userdata; + } + + /** + * Fetch all flashdata + * + * @return array Flash data array + */ + public function all_flashdata() + { + $out = array(); + + // loop through all userdata + foreach ($this->all_userdata() as $key => $val) + { + // if it contains flashdata, add it + if (strpos($key, self::FLASHDATA_KEY.self::FLASHDATA_OLD) !== FALSE) + { + $key = str_replace(self::FLASHDATA_KEY.self::FLASHDATA_OLD, '', $key); + $out[$key] = $val; + } + } + return $out; + } + + /** + * Add or change data in the "userdata" array + * + * @param mixed Item name or array of items + * @param string Item value or empty string + * @return void + */ + public function set_userdata($newdata = array(), $newval = '') + { + // Wrap params as array if singular + if (is_string($newdata)) + { + $newdata = array($newdata => $newval); + } + + // Set each name/value pair + if (count($newdata) > 0) + { + foreach ($newdata as $key => $val) + { + $this->userdata[$key] = $val; + } + } + + // Tell driver data changed + $this->current->sess_save(); + } + + /** + * Delete a session variable from the "userdata" array + * + * @param mixed Item name or array of item names + * @return void + */ + public function unset_userdata($newdata = array()) + { + // Wrap single name as array + if (is_string($newdata)) + { + $newdata = array($newdata => ''); + } + + // Unset each item name + if (count($newdata) > 0) + { + foreach ($newdata as $key => $val) + { + unset($this->userdata[$key]); + } + } + + // Tell driver data changed + $this->current->sess_save(); + } + + /** + * Determine if an item exists + * + * @param string Item name + * @return boolean + */ + public function has_userdata($item) + { + // Check for item name + return isset($this->userdata[$item]); + } + + /** + * Add or change flashdata, only available until the next request + * + * @param mixed Item name or array of items + * @param string Item value or empty string + * @return void + */ + public function set_flashdata($newdata = array(), $newval = '') + { + // Wrap item as array if singular + if (is_string($newdata)) + { + $newdata = array($newdata => $newval); + } + + // Prepend each key name and set value + if (count($newdata) > 0) + { + foreach ($newdata as $key => $val) + { + $flashdata_key = self::FLASHDATA_KEY.self::FLASHDATA_NEW.$key; + $this->set_userdata($flashdata_key, $val); + } + } + } + + /** + * Keeps existing flashdata available to next request. + * + * @param string Item key + * @return void + */ + public 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 NULL if the $key provided cannot be found + $old_flashdata_key = self::FLASHDATA_KEY.self::FLASHDATA_OLD.$key; + $value = $this->userdata($old_flashdata_key); + + $new_flashdata_key = self::FLASHDATA_KEY.self::FLASHDATA_NEW.$key; + $this->set_userdata($new_flashdata_key, $value); + } + + /** + * Fetch a specific flashdata item from the session array + * + * @param string Item key + * @return string + */ + public function flashdata($key) + { + // Prepend key and retrieve value + $flashdata_key = self::FLASHDATA_KEY.self::FLASHDATA_OLD.$key; + return $this->userdata($flashdata_key); + } + + /** + * Add or change tempdata, only available + * until expiration + * + * @param mixed Item name or array of items + * @param string Item value or empty string + * @param int Item lifetime in seconds or 0 for default + * @return void + */ + public function set_tempdata($newdata = array(), $newval = '', $expire = 0) + { + // Set expiration time + $expire = time() + ($expire ? $expire : self::TEMP_EXP_DEF); + + // Wrap item as array if singular + if (is_string($newdata)) + { + $newdata = array($newdata => $newval); + } + + // Get or create expiration list + $expirations = $this->userdata(self::EXPIRATION_KEY); + if ( ! $expirations) + { + $expirations = array(); + } + + // Prepend each key name and set value + if (count($newdata) > 0) + { + foreach ($newdata as $key => $val) + { + $tempdata_key = self::FLASHDATA_KEY.self::FLASHDATA_EXP.$key; + $expirations[$tempdata_key] = $expire; + $this->set_userdata($tempdata_key, $val); + } + } + + // Update expiration list + $this->set_userdata(self::EXPIRATION_KEY, $expirations); + } + + /** + * Delete a temporary session variable from the "userdata" array + * + * @param mixed Item name or array of item names + * @return void + */ + public function unset_tempdata($newdata = array()) + { + // Get expirations list + $expirations = $this->userdata(self::EXPIRATION_KEY); + if ( ! $expirations || ! count($expirations)) + { + // Nothing to do + return; + } + + // Wrap single name as array + if (is_string($newdata)) + { + $newdata = array($newdata => ''); + } + + // Prepend each item name and unset + if (count($newdata) > 0) + { + foreach ($newdata as $key => $val) + { + $tempdata_key = self::FLASHDATA_KEY.self::FLASHDATA_EXP.$key; + unset($expirations[$tempdata_key]); + $this->unset_userdata($tempdata_key); + } + } + + // Update expiration list + $this->set_userdata(self::EXPIRATION_KEY, $expirations); + } + + /** + * Fetch a specific tempdata item from the session array + * + * @param string Item key + * @return string + */ + public function tempdata($key) + { + // Prepend key and return value + $tempdata_key = self::FLASHDATA_KEY.self::FLASHDATA_EXP.$key; + return $this->userdata($tempdata_key); + } + + /** + * Identifies flashdata as 'old' for removal + * when _flashdata_sweep() runs. + * + * @access protected + * @return void + */ + protected function _flashdata_mark() + { + $userdata = $this->all_userdata(); + foreach ($userdata as $name => $value) + { + $parts = explode(self::FLASHDATA_NEW, $name); + if (is_array($parts) && count($parts) === 2) + { + $new_name = self::FLASHDATA_KEY.self::FLASHDATA_OLD.$parts[1]; + $this->set_userdata($new_name, $value); + $this->unset_userdata($name); + } + } + } + + /** + * Removes all flashdata marked as 'old' + * + * @access protected + * @return void + */ + protected function _flashdata_sweep() + { + $userdata = $this->all_userdata(); + foreach ($userdata as $key => $value) + { + if (strpos($key, self::FLASHDATA_OLD)) + { + $this->unset_userdata($key); + } + } + } + + /** + * Removes all expired tempdata + * + * @access protected + * @return void + */ + protected function _tempdata_sweep() + { + // Get expirations list + $expirations = $this->userdata(self::EXPIRATION_KEY); + if ( ! $expirations || ! count($expirations)) + { + // Nothing to do + return; + } + + // Unset expired elements + $now = time(); + $userdata = $this->all_userdata(); + foreach ($userdata as $key => $value) + { + if (strpos($key, self::FLASHDATA_EXP) && $expirations[$key] < $now) + { + unset($expirations[$key]); + $this->unset_userdata($key); + } + } + + // Update expiration list + $this->set_userdata(self::EXPIRATION_KEY, $expirations); + } +} +// END CI_Session Class + + +/** + * CI_Session_driver Class + * + * Extend this class to make a new CI_Session driver. + * A CI_Session driver basically manages an array of name/value pairs with some sort of storage mechanism. + * To make a new driver, derive from (extend) CI_Session_driver. Overload the initialize method and read or create + * session data. Then implement a save handler to write changed data to storage (sess_save), a destroy handler + * to remove deleted data (sess_destroy), and an access handler to expose the data (get_userdata). + * Put your driver in the libraries/Session/drivers folder anywhere in the loader paths. This includes the + * application directory, the system directory, or any path you add with $CI->load->add_package_path(). + * Your driver must be named CI_Session_<name>, and your filename must be Session_<name>.php, + * preferably also capitalized. (e.g.: CI_Session_foo in libraries/Session/drivers/Session_foo.php) + * Then specify the driver by setting 'sess_driver' in your config file or as a parameter when loading the CI_Session + * object. (e.g.: $config['sess_driver'] = 'foo'; OR $CI->load->driver('session', array('sess_driver' => 'foo')); ) + * Already provided are the Native driver, which manages the native PHP $_SESSION array, and + * the Cookie driver, which manages the data in a browser cookie, with optional extra storage in a database table. + * + * @package CodeIgniter + * @subpackage Libraries + * @category Sessions + * @author ExpressionEngine Dev Team + */ +abstract class CI_Session_driver extends CI_Driver { + /** + * Decorate + * + * Decorates the child with the parent driver lib's methods and properties + * + * @param object Parent library object + * @return void + */ + public function decorate($parent) + { + // Call base class decorate first + parent::decorate($parent); + + // Call initialize method now that driver has access to $this->_parent + $this->initialize(); + } + + /** + * __call magic method + * + * Handles access to the parent driver library's methods + * + * @param string Library method name + * @param array Method arguments (default: none) + * @return mixed + */ + public function __call($method, $args = array()) + { + // Make sure the parent library uses this driver + $this->_parent->select_driver(get_class($this)); + return parent::__call($method, $args); + } + + /** + * Initialize driver + * + * @return void + */ + protected function initialize() + { + // Overload this method to implement initialization + } + + /** + * Save the session data + * + * Data in the array has changed - perform any storage synchronization necessary + * The child class MUST implement this abstract method! + * + * @return void + */ + abstract public function sess_save(); + + /** + * Destroy the current session + * + * Clean up storage for this session - it has been terminated + * The child class MUST implement this abstract method! + * + * @return void + */ + abstract public function sess_destroy(); + + /** + * Regenerate the current session + * + * Regenerate the session id + * The child class MUST implement this abstract method! + * + * @param boolean Destroy session data flag (default: false) + * @return void + */ + abstract public function sess_regenerate($destroy = false); + + /** + * Get a reference to user data array + * + * Give array access to the main CI_Session object + * The child class MUST implement this abstract method! + * + * @return array Reference to userdata + */ + abstract public function &get_userdata(); +} +// END CI_Session_driver Class + + +/* End of file Session.php */ +/* Location: ./system/libraries/Session/Session.php */ +?> diff --git a/system/libraries/Session/drivers/Session_cookie.php b/system/libraries/Session/drivers/Session_cookie.php new file mode 100755 index 000000000..69e5fde14 --- /dev/null +++ b/system/libraries/Session/drivers/Session_cookie.php @@ -0,0 +1,799 @@ +<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); +/** + * CodeIgniter + * + * An open source application development framework for PHP 5.2.4 or newer + * + * NOTICE OF LICENSE + * + * Licensed under the Open Software License version 3.0 + * + * This source file is subject to the Open Software License (OSL 3.0) that is + * bundled with this package in the files license.txt / license.rst. It is + * also available through the world wide web at this URL: + * http://opensource.org/licenses/OSL-3.0 + * If you did not receive a copy of the license and are unable to obtain it + * through the world wide web, please send an email to + * licensing@ellislab.com so we can send you a copy immediately. + * + * @package CodeIgniter + * @author EllisLab Dev Team + * @copyright Copyright (c) 2008 - 2012, EllisLab, Inc. (http://ellislab.com/) + * @license http://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + * @link http://codeigniter.com + * @since Version 1.0 + * @filesource + */ + +/** + * Cookie-based session management driver + * + * This is the classic CI_Session functionality, as written by EllisLab, abstracted out to a driver. + * + * @package CodeIgniter + * @subpackage Libraries + * @category Sessions + * @author EllisLab Dev Team + * @link http://codeigniter.com/user_guide/libraries/sessions.html + */ +class CI_Session_cookie extends CI_Session_driver { + /** + * Whether to encrypt the session cookie + * + * @var bool + */ + public $sess_encrypt_cookie = FALSE; + + /** + * Whether to use to the database for session storage + * + * @var bool + */ + public $sess_use_database = FALSE; + + /** + * Name of the database table in which to store sessions + * + * @var string + */ + public $sess_table_name = ''; + + /** + * Length of time (in seconds) for sessions to expire + * + * @var int + */ + public $sess_expiration = 7200; + + /** + * Whether to kill session on close of browser window + * + * @var bool + */ + public $sess_expire_on_close = FALSE; + + /** + * Whether to match session on ip address + * + * @var bool + */ + public $sess_match_ip = FALSE; + + /** + * Whether to match session on user-agent + * + * @var bool + */ + public $sess_match_useragent = TRUE; + + /** + * Name of session cookie + * + * @var string + */ + public $sess_cookie_name = 'ci_session'; + + /** + * Session cookie prefix + * + * @var string + */ + public $cookie_prefix = ''; + + /** + * Session cookie path + * + * @var string + */ + public $cookie_path = ''; + + /** + * Session cookie domain + * + * @var string + */ + public $cookie_domain = ''; + + /** + * Whether to set the cookie only on HTTPS connections + * + * @var bool + */ + public $cookie_secure = FALSE; + + /** + * Whether cookie should be allowed only to be sent by the server + * + * @var bool + */ + public $cookie_httponly = FALSE; + + /** + * Interval at which to update session + * + * @var int + */ + public $sess_time_to_update = 300; + + /** + * Key with which to encrypt the session cookie + * + * @var string + */ + public $encryption_key = ''; + + /** + * Timezone to use for the current time + * + * @var string + */ + public $time_reference = 'local'; + + /** + * Session data + * + * @var array + */ + public $userdata = array(); + + /** + * Reference to CodeIgniter instance + * + * @var object + */ + public $CI; + + /** + * Current time + * + * @var int + */ + public $now; + + /** + * Default userdata keys + * + * @var array + */ + protected $defaults = array( + 'session_id' => NULL, + 'ip_address' => NULL, + 'user_agent' => NULL, + 'last_activity' => NULL + ); + + /** + * Data needs DB update flag + * + * @var bool + */ + protected $data_dirty = FALSE; + + /** + * Initialize session driver object + * + * @access protected + * @return void + */ + protected function initialize() + { + // 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 or via the config file + $prefs = 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', + 'cookie_httponly', + 'sess_time_to_update', + 'time_reference', + 'cookie_prefix', + 'encryption_key' + ); + foreach ($prefs as $key) + { + $this->$key = isset($this->_parent->params[$key]) ? $this->_parent->params[$key] : + $this->CI->config->item($key); + } + + if ($this->encryption_key === '') + { + show_error('In order to use the Cookie Session driver you are required to set an encryption key '. + 'in your config file.'); + } + + // 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 class + if ($this->sess_encrypt_cookie === TRUE) + { + $this->CI->load->library('encrypt'); + } + + // Check for database + if ($this->sess_use_database === TRUE && $this->sess_table_name !== '') + { + // Load database driver + $this->CI->load->database(); + + // Register shutdown function + register_shutdown_function(array($this, '_update_db')); + } + + // 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 expired sessions if necessary + $this->_sess_gc(); + } + + /** + * Write the session data + * + * @return void + */ + public function sess_save() + { + // Check for database + if ($this->sess_use_database === TRUE) + { + // Mark custom data as dirty so we know to update the DB + $this->data_dirty = TRUE; + } + + // Write the cookie + $this->_set_cookie(); + } + + /** + * Destroy the current session + * + * @return void + */ + public function sess_destroy() + { + // Kill the session DB row + if ($this->sess_use_database === TRUE && isset($this->userdata['session_id'])) + { + $this->CI->db->delete($this->sess_table_name, array('session_id' => $this->userdata['session_id'])); + } + + // Kill the cookie + $this->_setcookie($this->sess_cookie_name, addslashes(serialize(array())), ($this->now - 31500000), + $this->cookie_path, $this->cookie_domain, 0); + + // Kill session data + $this->userdata = array(); + } + + /** + * Regenerate the current session + * + * Regenerate the session id + * + * @param boolean Destroy session data flag (default: false) + * @return void + */ + public function sess_regenerate($destroy = false) + { + // Check destroy flag + if ($destroy) + { + // Destroy old session and create new one + $this->sess_destroy(); + $this->_sess_create(); + } + else + { + // Just force an update to recreate the id + $this->_sess_update(true); + } + } + + /** + * Get a reference to user data array + * + * @return array - Reference to userdata + */ + public function &get_userdata() + { + // Return reference to array + return $this->userdata; + } + + /** + * Fetch the current session data if it exists + * + * @access protected + * @return bool + */ + protected function _sess_read() + { + // Fetch the cookie + $session = $this->CI->input->cookie($this->sess_cookie_name); + + // No cookie? Goodbye cruel world!... + if ($session === NULL) + { + log_message('debug', 'A session cookie was not found.'); + return FALSE; + } + + // Check for encryption + if ($this->sess_encrypt_cookie === TRUE) + { + // Decrypt the cookie data + $session = $this->CI->encrypt->decode($session); + } + else + { + // Encryption was not used, so we need to check the md5 hash in the last 32 chars + $len = strlen($session)-32; + $hash = substr($session, $len); + $session = substr($session, 0, $len); + + // 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; + } + } + + // Unserialize the session array + $session = $this->_unserialize($session); + + // Is the session data we unserialized an array with the correct format? + if ( ! is_array($session) || ! isset($session['session_id'], $session['ip_address'], $session['user_agent'], + $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 && $session['ip_address'] !== $this->CI->input->ip_address()) + { + $this->sess_destroy(); + return FALSE; + } + + // Does the User Agent Match? + if ($this->sess_match_useragent === TRUE && + trim($session['user_agent']) !== trim(substr($this->CI->input->user_agent(), 0, 120))) + { + $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->limit(1)->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 ( ! empty($row->user_data)) + { + $custom_data = $this->_unserialize($row->user_data); + + if (is_array($custom_data)) + { + $session = $session + $custom_data; + } + } + } + + // Session is valid! + $this->userdata = $session; + return TRUE; + } + + /** + * Create a new session + * + * @access protected + * @return void + */ + protected function _sess_create() + { + // Initialize userdata + $this->userdata = array( + 'session_id' => $this->_make_sess_id(), + 'ip_address' => $this->CI->input->ip_address(), + 'user_agent' => substr($this->CI->input->user_agent(), 0, 120), + 'last_activity' => $this->now, + ); + + // Check for database + if ($this->sess_use_database === TRUE) + { + // Add empty user_data field and save the data to the DB + $this->CI->db->set('user_data', '')->insert($this->sess_table_name, $this->userdata); + } + + // Write the cookie + $this->_set_cookie(); + } + + /** + * Update an existing session + * + * @access protected + * @param boolean Force update flag (default: false) + * @return void + */ + protected function _sess_update($force = FALSE) + { + // We only update the session every five minutes by default (unless forced) + if ( ! $force && ($this->userdata['last_activity'] + $this->sess_time_to_update) >= $this->now) + { + return; + } + + // Update last activity to now + $this->userdata['last_activity'] = $this->now; + + // Save the old session id so we know which DB record to update + $old_sessid = $this->userdata['session_id']; + + // Changing the session ID during an AJAX call causes problems + if ( ! $this->CI->input->is_ajax_request()) + { + // Get new id + $this->userdata['session_id'] = $this->_make_sess_id(); + } + + // Check for database + if ($this->sess_use_database === TRUE) + { + // Update the session ID and last_activity field in the DB + $this->CI->db->update($this->sess_table_name, array( + 'last_activity' => $this->now, + 'session_id' => $this->userdata['session_id'] + ), array('session_id' => $old_sessid)); + } + + // Write the cookie + $this->_set_cookie(); + } + + /** + * Update database with current data + * + * This gets called from the shutdown function and also + * registered with PHP to run at the end of the request + * so it's guaranteed to update even when a fatal error + * occurs. The first call makes the update and clears the + * dirty flag so it won't happen twice. + */ + public function _update_db() + { + // Check for database and dirty flag and unsaved + if ($this->sess_use_database === TRUE && $this->data_dirty === TRUE) + { + // Set up activity and data fields to be set + // If we don't find custom data, user_data will remain an empty string + $set = array( + 'last_activity' => $this->userdata['last_activity'], + 'user_data' => '' + ); + + // Get the custom userdata, leaving out the defaults + // (which get stored in the cookie) + $userdata = array_diff_key($this->userdata, $this->defaults); + + // Did we find any custom data? + if ( ! empty($userdata)) + { + // Serialize the custom data array so we can store it + $set['user_data'] = $this->_serialize($userdata); + } + + // Run the update query + // Any time we change the session id, it gets updated immediately, + // so our where clause below is always safe + $this->CI->db->update($this->sess_table_name, $set, array('session_id' => $this->userdata['session_id'])); + + // Clear dirty flag to prevent double updates + $this->data_dirty = FALSE; + + log_message('debug', 'CI_Session Data Saved To DB'); + } + } + + /** + * Generate a new session id + * + * @return string Hashed session id + */ + protected function _make_sess_id() + { + $new_sessid = ''; + do + { + $new_sessid .= mt_rand(0, mt_getrandmax()); + } + while (strlen($new_sessid) < 32); + + // 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 and return + return md5(uniqid($new_sessid, TRUE)); + } + + /** + * Get the "now" time + * + * @access protected + * @return int Time + */ + protected function _get_time() + { + if ($this->time_reference === 'local' || $this->time_reference === date_default_timezone_get()) + { + return time(); + } + + $datetime = new DateTime('now', new DateTimeZone($this->time_reference)); + sscanf($datetime->format('j-n-Y G:i:s'), '%d-%d-%d %d:%d:%d', $day, $month, $year, $hour, $minute, $second); + + return mktime($hour, $minute, $second, $month, $day, $year); + } + + /** + * Write the session cookie + * + * @access protected + * @return void + */ + protected function _set_cookie() + { + // Get userdata (only defaults if database) + if ($this->sess_use_database === TRUE) + { + $cookie_data = array_intersect_key($this->userdata, $this->defaults); + } + else + { + $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); + } + + $expire = ($this->sess_expire_on_close === TRUE) ? 0 : $this->sess_expiration + time(); + + // Set the cookie + $this->_setcookie($this->sess_cookie_name, $cookie_data, $expire, $this->cookie_path, $this->cookie_domain, + $this->cookie_secure, $this->cookie_httponly); + } + + /** + * Set a cookie with the system + * + * This abstraction of the setcookie call allows overriding for unit testing + * + * @access protected + * @param string Cookie name + * @param string Cookie value + * @param int Expiration time + * @param string Cookie path + * @param string Cookie domain + * @param bool Secure connection flag + * @param bool HTTP protocol only flag + * @return void + */ + protected function _setcookie($name, $value = '', $expire = 0, $path = '', $domain = '', $secure = false, + $httponly = false) + { + // Set the cookie + setcookie($name, $value, $expire, $path, $domain, $secure, $httponly); + } + + /** + * 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 protected + * @param mixed Data to serialize + * @return string Serialized data + */ + protected function _serialize($data) + { + if (is_array($data)) + { + array_walk_recursive($data, array(&$this, '_escape_slashes')); + } + elseif (is_string($data)) + { + $data = str_replace('\\', '{{slash}}', $data); + } + return serialize($data); + } + + /** + * Escape slashes + * + * This function converts any slashes found into a temporary marker + * + * @access protected + * @param string Value + * @param string Key + * @return void + */ + protected function _escape_slashes(&$val, $key) + { + if (is_string($val)) + { + $val = str_replace('\\', '{{slash}}', $val); + } + } + + /** + * Unserialize + * + * This function unserializes a data string, then converts any + * temporary slash markers back to actual slashes + * + * @access protected + * @param mixed Data to unserialize + * @return mixed Unserialized data + */ + protected function _unserialize($data) + { + $data = @unserialize(strip_slashes(trim($data))); + + if (is_array($data)) + { + array_walk_recursive($data, array(&$this, '_unescape_slashes')); + return $data; + } + + return is_string($data) ? str_replace('{{slash}}', '\\', $data) : $data; + } + + /** + * Unescape slashes + * + * This function converts any slash markers back into actual slashes + * + * @access protected + * @param string Value + * @param string Key + * @return void + */ + protected function _unescape_slashes(&$val, $key) + { + if (is_string($val)) + { + $val= str_replace('{{slash}}', '\\', $val); + } + } + + /** + * Garbage collection + * + * This deletes expired session rows from database + * if the probability percentage is met + * + * @access protected + * @return void + */ + protected function _sess_gc() + { + if ($this->sess_use_database !== TRUE) + { + return; + } + + $probability = ini_get('session.gc_probability'); + $divisor = ini_get('session.gc_divisor'); + + srand(time()); + if ((mt_rand(0, $divisor) / $divisor) < $probability) + { + $expire = $this->now - $this->sess_expiration; + $this->CI->db->delete($this->sess_table_name, 'last_activity < '.$expire); + + log_message('debug', 'Session garbage collection performed.'); + } + } +} + +/* End of file Session_cookie.php */ +/* Location: ./system/libraries/Session/drivers/Session_cookie.php */ diff --git a/system/libraries/Session/drivers/Session_native.php b/system/libraries/Session/drivers/Session_native.php new file mode 100755 index 000000000..8ba8e749a --- /dev/null +++ b/system/libraries/Session/drivers/Session_native.php @@ -0,0 +1,211 @@ +<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); +/** + * CodeIgniter + * + * An open source application development framework for PHP 5.1.6 or newer + * + * @package CodeIgniter + * @author ExpressionEngine Dev Team + * @copyright Copyright (c) 2008 - 2010, EllisLab, Inc. + * @license http://codeigniter.com/user_guide/license.html + * @link http://codeigniter.com + * @since Version 2.0 + * @filesource + */ + + +/** + * Native PHP session management driver + * + * This is the driver that uses the native PHP $_SESSION array through the Session driver library. + * + * @package CodeIgniter + * @subpackage Libraries + * @category Sessions + * @author ExpressionEngine Dev Team + */ +class CI_Session_native extends CI_Session_driver { + /** + * Initialize session driver object + * + * @access protected + * @return void + */ + protected function initialize() + { + // Get config parameters + $config = array(); + $CI =& get_instance(); + $prefs = array( + 'sess_cookie_name', + 'sess_expire_on_close', + 'sess_expiration', + 'sess_match_ip', + 'sess_match_useragent', + 'sess_time_to_update', + 'cookie_prefix', + 'cookie_path', + 'cookie_domain' + ); + foreach ($prefs as $key) + { + $config[$key] = isset($this->_parent->params[$key]) ? $this->_parent->params[$key] : + $CI->config->item($key); + } + + // Set session name, if specified + if ($config['sess_cookie_name']) + { + // Differentiate name from cookie driver with '_id' suffix + $name = $config['sess_cookie_name'].'_id'; + if ($config['cookie_prefix']) + { + // Prepend cookie prefix + $name = $config['cookie_prefix'].$name; + } + session_name($name); + } + + // Set expiration, path, and domain + $expire = 7200; + $path = '/'; + $domain = ''; + if ($config['sess_expiration'] !== FALSE) + { + // Default to 2 years if expiration is "0" + $expire = ($config['sess_expiration'] == 0) ? (60*60*24*365*2) : $config['sess_expiration']; + } + if ($config['cookie_path']) + { + // Use specified path + $path = $config['cookie_path']; + } + if ($config['cookie_domain']) + { + // Use specified domain + $domain = $config['cookie_domain']; + } + session_set_cookie_params($config['sess_expire_on_close'] ? 0 : $expire, $path, $domain); + + // Start session + session_start(); + + // Check session expiration, ip, and agent + $now = time(); + $destroy = FALSE; + if (isset($_SESSION['last_activity']) && ($_SESSION['last_activity'] + $expire) < $now) + { + // Expired - destroy + $destroy = TRUE; + } + else if ($config['sess_match_ip'] == TRUE && isset($_SESSION['ip_address']) && + $_SESSION['ip_address'] != $CI->input->ip_address()) + { + // IP doesn't match - destroy + $destroy = TRUE; + } + else if ($config['sess_match_useragent'] == TRUE && isset($_SESSION['user_agent']) && + $_SESSION['user_agent'] != trim(substr($CI->input->user_agent(), 0, 50))) + { + // Agent doesn't match - destroy + $destroy = TRUE; + } + + // Destroy expired or invalid session + if ($destroy) + { + // Clear old session and start new + $this->sess_destroy(); + session_start(); + } + + // Check for update time + if ($config['sess_time_to_update'] && isset($_SESSION['last_activity']) && + ($_SESSION['last_activity'] + $config['sess_time_to_update']) < $now) + { + // Regenerate ID, but don't destroy session + $this->sess_regenerate(FALSE); + } + + // Set activity time + $_SESSION['last_activity'] = $now; + + // Set matching values as required + if ($config['sess_match_ip'] == TRUE && !isset($_SESSION['ip_address'])) + { + // Store user IP address + $_SESSION['ip_address'] = $CI->input->ip_address(); + } + if ($config['sess_match_useragent'] == TRUE && !isset($_SESSION['user_agent'])) + { + // Store user agent string + $_SESSION['user_agent'] = trim(substr($CI->input->user_agent(), 0, 50)); + } + + // Make session ID available + $_SESSION['session_id'] = session_id(); + } + + /** + * Save the session data + * + * @access public + * @return void + */ + public function sess_save() + { + // Nothing to do - changes to $_SESSION are automatically saved + } + + /** + * Destroy the current session + * + * @access public + * @return void + */ + public function sess_destroy() + { + // Cleanup session + $_SESSION = array(); + $name = session_name(); + if (isset($_COOKIE[$name])) + { + // Clear session cookie + $params = session_get_cookie_params(); + setcookie($name, '', time() - 42000, $params['path'], $params['domain']); + unset($_COOKIE[$name]); + } + session_destroy(); + } + + /** + * Regenerate the current session + * + * Regenerate the session id + * + * @access public + * @param boolean Destroy session data flag (default: FALSE) + * @return void + */ + public function sess_regenerate($destroy = FALSE) + { + // Just regenerate id, passing destroy flag + session_regenerate_id($destroy); + $_SESSION['session_id'] = session_id(); + } + + /** + * Get a reference to user data array + * + * @access public + * @return array Reference to userdata + */ + public function &get_userdata() + { + // Just return reference to $_SESSION + return $_SESSION; + } +} + +/* End of file Session_native.php */ +/* Location: ./system/libraries/Session/drivers/Session_native.php */ |