diff options
-rw-r--r-- | system/core/Loader.php | 11 | ||||
-rw-r--r-- | system/libraries/Driver.php | 22 | ||||
-rwxr-xr-x | system/libraries/Session/Session.php | 621 | ||||
-rwxr-xr-x[-rw-r--r--] | system/libraries/Session/drivers/Session_cookie.php (renamed from system/libraries/Session.php) | 579 | ||||
-rwxr-xr-x | system/libraries/Session/drivers/Session_native.php | 187 | ||||
-rw-r--r-- | tests/codeigniter/libraries/Session_test.php | 304 | ||||
-rw-r--r-- | tests/mocks/autoloader.php | 23 | ||||
-rw-r--r-- | tests/mocks/libraries/session.php | 43 |
8 files changed, 1402 insertions, 388 deletions
diff --git a/system/core/Loader.php b/system/core/Loader.php index 0bc6e844a..ea81c6f26 100644 --- a/system/core/Loader.php +++ b/system/core/Loader.php @@ -1193,6 +1193,15 @@ class CI_Loader { } } + // Autoload drivers + if (isset($autoload['drivers'])) + { + foreach ($autoload['drivers'] as $item) + { + $this->driver($item); + } + } + // Autoload models if (isset($autoload['model'])) { @@ -1260,4 +1269,4 @@ class CI_Loader { } /* End of file Loader.php */ -/* Location: ./system/core/Loader.php */
\ No newline at end of file +/* Location: ./system/core/Loader.php */ 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/Session.php b/system/libraries/Session/Session.php new file mode 100755 index 000000000..68819a665 --- /dev/null +++ b/system/libraries/Session/Session.php @@ -0,0 +1,621 @@ +<?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 native PHP session driver will load, but the 'sess_driver' config/param item (see above) can be + * used to specify the 'cookie' 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 + $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)) + { + $this->current = $this->$child; + $this->userdata =& $this->current->get_userdata(); + } + else + { + $this->load_driver($driver); + } + } + } + + /** + * 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) + { + // Just call regenerate on driver + $this->current->sess_regenerate($destroy); + } + + /** + * 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.php b/system/libraries/Session/drivers/Session_cookie.php index af38dc366..19ccd417d 100644..100755 --- a/system/libraries/Session.php +++ b/system/libraries/Session/drivers/Session_cookie.php @@ -9,7 +9,7 @@ * 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 + * 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 @@ -26,7 +26,11 @@ */ /** - * Session Class + * Cookie-based session management driver + * + * This is the CI_Session functionality, as written by EllisLab, abstracted out to a driver. + * I have done a little updating for PHP5, and made minor changes to extract this functionality from + * the public interface (now in the Session Library), but effectively this code is unchanged. * * @package CodeIgniter * @subpackage Libraries @@ -34,8 +38,7 @@ * @author EllisLab Dev Team * @link http://codeigniter.com/user_guide/libraries/sessions.html */ -class CI_Session { - +class CI_Session_cookie extends CI_Session_driver { /** * Whether to encrypt the session cookie * @@ -142,20 +145,12 @@ class CI_Session { 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 * @@ -178,31 +173,31 @@ class CI_Session { public $now; /** - * Session Constructor + * Initialize session driver object * - * The constructor runs the session routines automatically - * whenever the class is instantiated. - * - * @param array + * @access protected * @return void */ - public function __construct($params = array()) + protected function initialize() { - 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) + // manually via the $params array 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); + $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 Session class you are required to set an encryption key in your config file.'); + 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 @@ -220,8 +215,8 @@ class CI_Session { $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 + // 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 @@ -236,40 +231,138 @@ class CI_Session { // 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()) + if ( ! $this->_sess_read()) { - $this->sess_create(); + $this->_sess_create(); } else { - $this->sess_update(); + $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(); + } + + /** + * Write the session data + * + * @return void + */ + public function sess_save() + { + // 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->all_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]; + } - log_message('debug', 'Session routines successfully run'); + // 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); } - // -------------------------------------------------------------------- + /** + * Destroy the current session + * + * @return void + */ + public function sess_destroy() + { + // Kill the session DB row + if ($this->sess_use_database === TRUE && $this->has_userdata('session_id')) + { + $this->CI->db->where('session_id', $this->userdata['session_id']); + $this->CI->db->delete($this->sess_table_name); + } + + // 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 */ - public function sess_read() + protected function _sess_read() { // Fetch the cookie $session = $this->CI->input->cookie($this->sess_cookie_name); - // No cookie? Goodbye cruel world!... + // No cookie? Goodbye cruel world!... if ($session === NULL) { log_message('debug', 'A session cookie was not found.'); @@ -288,9 +381,10 @@ class CI_Session { $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)) + 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.'); + 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; } @@ -300,7 +394,8 @@ class CI_Session { $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'])) + if ( ! is_array($session) || ! isset($session['session_id'], $session['ip_address'], $session['user_agent'], + $session['last_activity'])) { $this->sess_destroy(); return FALSE; @@ -321,7 +416,8 @@ class CI_Session { } // Does the User Agent Match? - if ($this->sess_match_useragent === TRUE && trim($session['user_agent']) !== trim(substr($this->CI->input->user_agent(), 0, 120))) + if ($this->sess_match_useragent === TRUE && + trim($session['user_agent']) !== trim(substr($this->CI->input->user_agent(), 0, 120))) { $this->sess_destroy(); return FALSE; @@ -351,7 +447,7 @@ class CI_Session { return FALSE; } - // Is there custom data? If so, add it to the main session array + // Is there custom data? If so, add it to the main session array $row = $query->row(); if ( ! empty($row->user_data)) { @@ -374,65 +470,13 @@ class CI_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 * + * @access protected * @return void */ - public function sess_create() + protected function _sess_create() { $sessid = ''; do @@ -445,12 +489,12 @@ class CI_Session { $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' => '' - ); + '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) @@ -462,17 +506,17 @@ class CI_Session { $this->_set_cookie(); } - // -------------------------------------------------------------------- - /** * Update an existing session * + * @access protected + * @param boolean Force update flag (default: false) * @return void */ - public function sess_update() + protected function _sess_update($force = FALSE) { - // We only update the session every five minutes by default - if (($this->userdata['last_activity'] + $this->sess_time_to_update) >= $this->now) + // 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; } @@ -481,9 +525,7 @@ class CI_Session { // 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 - */ + // 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; @@ -533,254 +575,23 @@ class CI_Session { $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))); + $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 + * @access protected + * @return int Time */ protected function _get_time() { - if ($this->time_reference === 'local' OR $this->time_reference === date_default_timezone_get()) + if ($this->time_reference === 'local' || $this->time_reference === date_default_timezone_get()) { return time(); } @@ -791,15 +602,14 @@ class CI_Session { return mktime($hour, $minute, $second, $month, $day, $year); } - // -------------------------------------------------------------------- - /** * Write the session cookie * - * @param mixed + * @access protected + * @param array Cookie name/value pairs * @return void */ - protected function _set_cookie($cookie_data = NULL) + protected function _set_cookie(array $cookie_data = NULL) { if (is_null($cookie_data)) { @@ -822,18 +632,31 @@ class CI_Session { $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 - ); + $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 @@ -841,8 +664,9 @@ class CI_Session { * 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 + * @access protected + * @param mixed Data to serialize + * @return string Serialized data */ protected function _serialize($data) { @@ -862,8 +686,9 @@ class CI_Session { * * This function converts any slashes found into a temporary marker * - * @param string - * @param string + * @access protected + * @param string Value + * @param string Key * @return void */ protected function _escape_slashes(&$val, $key) @@ -874,16 +699,15 @@ class CI_Session { } } - // -------------------------------------------------------------------- - /** * Unserialize * * This function unserializes a data string, then converts any * temporary slash markers back to actual slashes * - * @param array - * @return string + * @access protected + * @param mixed Data to unserialize + * @return mixed Unserialized data */ protected function _unserialize($data) { @@ -898,15 +722,14 @@ class CI_Session { 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 + * @access protected + * @param string Value + * @param string Key * @return void */ protected function _unescape_slashes(&$val, $key) @@ -917,14 +740,13 @@ class CI_Session { } } - // -------------------------------------------------------------------- - /** * Garbage collection * * This deletes expired session rows from database * if the probability percentage is met * + * @access protected * @return void */ protected function _sess_gc() @@ -948,8 +770,7 @@ class CI_Session { log_message('debug', 'Session garbage collection performed.'); } } - } -/* End of file Session.php */ -/* Location: ./system/libraries/Session.php */
\ No newline at end of file +/* 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..27db942eb --- /dev/null +++ b/system/libraries/Session/drivers/Session_native.php @@ -0,0 +1,187 @@ +<?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(); + foreach (array('sess_cookie_name', 'sess_expire_on_close', 'sess_expiration', 'sess_match_ip', + 'sess_match_useragent', 'cookie_prefix', 'cookie_path', 'cookie_domain') 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']) + { + $name = $config['sess_cookie_name']; + 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(); + } + + // 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)); + } + } + + /** + * 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); + } + + /** + * 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 */ diff --git a/tests/codeigniter/libraries/Session_test.php b/tests/codeigniter/libraries/Session_test.php new file mode 100644 index 000000000..db7efb6b7 --- /dev/null +++ b/tests/codeigniter/libraries/Session_test.php @@ -0,0 +1,304 @@ +<?php + +/** + * Session driver library unit test + */ +class Session_test extends CI_TestCase { + protected $settings = array('use_cookies' => 0, 'use_only_cookies' => 0, 'cache_limiter' => false); + protected $setting_vals = array(); + protected $cookie_vals; + protected $session; + + /** + * Set up test framework + */ + public function set_up() + { + // Override settings + foreach ($this->settings as $name => $value) { + $this->setting_vals[$name] = ini_get('session.'.$name); + ini_set('session.'.$name, $value); + } + + // Start with clean environment + $this->cookie_vals = $_COOKIE; + $_COOKIE = array(); + + // Establish necessary support classes + $obj = new stdClass; + foreach (array('config' => 'cfg', 'load' => 'load', 'input' => 'in') as $name => $abbr) { + $class = $this->ci_core_class($abbr); + $obj->$name = new $class; + } + $this->ci_instance($obj); + + // Attach session instance locally + $config = array('sess_encrypt_cookie' => FALSE, 'sess_use_database' => FALSE, 'sess_table_name' => '', + 'sess_expiration' => 7200, 'sess_expire_on_close' => FALSE, 'sess_match_ip' => FALSE, + 'sess_match_useragent' => TRUE, 'sess_cookie_name' => 'ci_session', 'cookie_path' => '', + 'cookie_domain' => '', 'cookie_secure' => FALSE, 'cookie_httponly' => FALSE, 'sess_time_to_update' => 300, + 'time_reference' => 'local', 'cookie_prefix' => '', 'encryption_key' => 'foobar', + 'sess_valid_drivers' => array('Mock_Libraries_Session_native', 'Mock_Libraries_Session_cookie')); + $this->session = new Mock_Libraries_Session($config); + } + + /** + * Tear down test framework + */ + public function tear_down() + { + // Restore environment + if (session_id()) session_destroy(); + $_SESSION = array(); + $_COOKIE = $this->cookie_vals; + + // Restore settings + foreach ($this->settings as $name => $value) { + ini_set('session.'.$name, $this->setting_vals[$name]); + } + } + + /** + * Test set_userdata() function + */ + public function test_set_userdata() + { + // Set a userdata message for each driver + $cmsg = 'Some test data'; + $this->session->cookie->set_userdata('test1', $cmsg); + $nmsg = 'Other test data'; + $this->session->native->set_userdata('test1', $nmsg); + + // Verify independent messages + $this->assertEquals($this->session->cookie->userdata('test1'), $cmsg); + $this->assertEquals($this->session->native->userdata('test1'), $nmsg); + } + + /** + * Test the has_userdata() function + */ + public function test_has_userdata() + { + // Set a userdata message for each driver + $cmsg = 'My test data'; + $this->session->cookie->set_userdata('test2', $cmsg); + $nmsg = 'Your test data'; + $this->session->native->set_userdata('test2', $nmsg); + + // Verify independent messages + $this->assertTrue($this->session->cookie->has_userdata('test2')); + $this->assertTrue($this->session->native->has_userdata('test2')); + } + + /** + * Test the all_userdata() function + */ + public function test_all_userdata() + { + // Set a specific series of data for each driver + $cdata = array('one' => 'first', 'two' => 'second', 'three' => 'third', 'foo' => 'bar', 'bar' => 'baz'); + $this->session->cookie->set_userdata($cdata); + $ndata = array('one' => 'gold', 'two' => 'silver', 'three' => 'bronze', 'foo' => 'baz', 'bar' => 'foo'); + $this->session->native->set_userdata($ndata); + + // Make sure all values are present + $call = $this->session->cookie->all_userdata(); + foreach ($cdata as $key => $value) { + $this->assertEquals($value, $call[$key]); + } + $nall = $this->session->native->all_userdata(); + foreach ($ndata as $key => $value) { + $this->assertEquals($value, $nall[$key]); + } + } + + /** + * Test the unset_userdata() function + */ + public function test_unset_userdata() + { + // Set a userdata message for each driver + $cmsg = 'Other test data'; + $this->session->cookie->set_userdata('test3', $cmsg); + $nmsg = 'Sundry test data'; + $this->session->native->set_userdata('test3', $nmsg); + + // Verify independent messages + $this->assertEquals($this->session->cookie->userdata('test3'), $cmsg); + $this->assertEquals($this->session->native->userdata('test3'), $nmsg); + + // Unset them and verify absence + $this->session->cookie->unset_userdata('test3'); + $this->session->native->unset_userdata('test3'); + $this->assertEquals($this->session->cookie->userdata('test3'), NULL); + $this->assertEquals($this->session->native->userdata('test3'), NULL); + } + + /** + * Test the set_flashdata() function + */ + public function test_set_flashdata() + { + // Set flashdata message for each driver + $cmsg = 'Some flash data'; + $this->session->cookie->set_flashdata('test4', $cmsg); + $nmsg = 'Other flash data'; + $this->session->native->set_flashdata('test4', $nmsg); + + // Simulate page reload + $this->session->cookie->reload(); + $this->session->native->reload(); + + // Verify independent messages + $this->assertEquals($this->session->cookie->flashdata('test4'), $cmsg); + $this->assertEquals($this->session->native->flashdata('test4'), $nmsg); + + // Simulate next page reload + $this->session->cookie->reload(); + $this->session->native->reload(); + + // Verify absence of messages + $this->assertEquals($this->session->cookie->flashdata('test4'), NULL); + $this->assertEquals($this->session->native->flashdata('test4'), NULL); + } + + /** + * Test the keep_flashdata() function + */ + public function test_keep_flashdata() + { + // Set flashdata message for each driver + $cmsg = 'My flash data'; + $this->session->cookie->set_flashdata('test5', $cmsg); + $nmsg = 'Your flash data'; + $this->session->native->set_flashdata('test5', $nmsg); + + // Simulate page reload and verify independent messages + $this->session->cookie->reload(); + $this->session->native->reload(); + $this->assertEquals($this->session->cookie->flashdata('test5'), $cmsg); + $this->assertEquals($this->session->native->flashdata('test5'), $nmsg); + + // Keep messages + $this->session->cookie->keep_flashdata('test5'); + $this->session->native->keep_flashdata('test5'); + + // Simulate next page reload and verify message persistence + $this->session->cookie->reload(); + $this->session->native->reload(); + $this->assertEquals($this->session->cookie->flashdata('test5'), $cmsg); + $this->assertEquals($this->session->native->flashdata('test5'), $nmsg); + + // Simulate next page reload and verify absence of messages + $this->session->cookie->reload(); + $this->session->native->reload(); + $this->assertEquals($this->session->cookie->flashdata('test5'), NULL); + $this->assertEquals($this->session->native->flashdata('test5'), NULL); + } + + /** + * Test the all_flashdata() function + */ + public function test_all_flashdata() + { + // Set a specific series of data for each driver + $cdata = array('one' => 'first', 'two' => 'second', 'three' => 'third', 'foo' => 'bar', 'bar' => 'baz'); + $this->session->cookie->set_flashdata($cdata); + $ndata = array('one' => 'gold', 'two' => 'silver', 'three' => 'bronze', 'foo' => 'baz', 'bar' => 'foo'); + $this->session->native->set_flashdata($ndata); + + // Simulate page reload and make sure all values are present + $this->session->cookie->reload(); + $this->session->native->reload(); + $this->assertEquals($cdata, $this->session->cookie->all_flashdata()); + $this->assertEquals($ndata, $this->session->native->all_flashdata()); + } + + /** + * Test the set_tempdata() function + */ + public function test_set_tempdata() + { + // Set tempdata message for each driver - 1 second timeout + $cmsg = 'Some temp data'; + $this->session->cookie->set_tempdata('test6', $cmsg, 1); + $nmsg = 'Other temp data'; + $this->session->native->set_tempdata('test6', $nmsg, 1); + + // Simulate page reload and verify independent messages + $this->session->cookie->reload(); + $this->session->native->reload(); + $this->assertEquals($this->session->cookie->tempdata('test6'), $cmsg); + $this->assertEquals($this->session->native->tempdata('test6'), $nmsg); + + // Wait 2 seconds, simulate page reload and verify message absence + sleep(2); + $this->session->cookie->reload(); + $this->session->native->reload(); + $this->assertEquals($this->session->cookie->tempdata('test6'), NULL); + $this->assertEquals($this->session->native->tempdata('test6'), NULL); + } + + /** + * Test the unset_tempdata() function + */ + public function test_unset_tempdata() + { + // Set tempdata message for each driver - 1 second timeout + $cmsg = 'My temp data'; + $this->session->cookie->set_tempdata('test7', $cmsg, 1); + $nmsg = 'Your temp data'; + $this->session->native->set_tempdata('test7', $nmsg, 1); + + // Verify independent messages + $this->assertEquals($this->session->cookie->tempdata('test7'), $cmsg); + $this->assertEquals($this->session->native->tempdata('test7'), $nmsg); + + // Unset data and verify message absence + $this->session->cookie->unset_tempdata('test7'); + $this->session->native->unset_tempdata('test7'); + $this->assertEquals($this->session->cookie->tempdata('test7'), NULL); + $this->assertEquals($this->session->native->tempdata('test7'), NULL); + } + + /** + * Test the sess_regenerate() function + */ + public function test_sess_regenerate() + { + // Get current session id, regenerate, and compare + // Cookie driver + $oldid = $this->session->cookie->userdata('session_id'); + $this->session->cookie->sess_regenerate(); + $newid = $this->session->cookie->userdata('session_id'); + $this->assertFalse($oldid === $newid); + + // Native driver - bug #55267 (https://bugs.php.net/bug.php?id=55267) prevents testing this + /*$oldid = session_id(); + $this->session->native->sess_regenerate(); + $oldid = session_id(); + $this->assertFalse($oldid === $newid);*/ + } + + /** + * Test the sess_destroy() function + */ + public function test_sess_destroy() + { + // Set a userdata message, destroy session, and verify absence + $msg = 'More test data'; + + // Cookie driver + $this->session->cookie->set_userdata('test8', $msg); + $this->assertEquals($this->session->cookie->userdata('test8'), $msg); + $this->session->cookie->sess_destroy(); + $this->assertEquals($this->session->cookie->userdata('test8'), NULL); + + // Native driver + $this->session->native->set_userdata('test8', $msg); + $this->assertEquals($this->session->native->userdata('test8'), $msg); + $this->session->native->sess_destroy(); + $this->assertEquals($this->session->native->userdata('test8'), NULL); + } +} + diff --git a/tests/mocks/autoloader.php b/tests/mocks/autoloader.php index be1c2220c..88d016bba 100644 --- a/tests/mocks/autoloader.php +++ b/tests/mocks/autoloader.php @@ -26,10 +26,14 @@ function autoload($class) 'Email', 'Encrypt', 'Form_validation', 'Ftp', 'Image_lib', 'Javascript', 'Log', 'Migration', 'Pagination', - 'Parser', 'Profiler', 'Session', - 'Table', 'Trackback', 'Typography', - 'Unit_test', 'Upload', 'User_agent', - 'Xmlrpc', 'Zip', + 'Parser', 'Profiler', 'Table', + 'Trackback', 'Typography', 'Unit_test', + 'Upload', 'User_agent', 'Xmlrpc', + 'Zip', + ); + + $ci_drivers = array( + 'Session', ); if (strpos($class, 'Mock_') === 0) @@ -52,6 +56,15 @@ function autoload($class) $dir = BASEPATH.'libraries'.DIRECTORY_SEPARATOR; $class = ($subclass === 'Driver_Library') ? 'Driver' : $subclass; } + elseif (in_array($subclass, $ci_drivers)) + { + $dir = BASEPATH.'libraries'.DIRECTORY_SEPARATOR.$subclass.DIRECTORY_SEPARATOR; + $class = $subclass; + } + elseif (in_array(($parent = strtok($subclass, '_')), $ci_drivers)) { + $dir = BASEPATH.'libraries'.DIRECTORY_SEPARATOR.$parent.DIRECTORY_SEPARATOR.'drivers'.DIRECTORY_SEPARATOR; + $class = $subclass; + } elseif (preg_match('/^CI_DB_(.+)_(driver|forge|result|utility)$/', $class, $m) && count($m) === 3) { $driver_path = BASEPATH.'database'.DIRECTORY_SEPARATOR.'drivers'.DIRECTORY_SEPARATOR; @@ -91,4 +104,4 @@ function autoload($class) } include_once($file); -}
\ No newline at end of file +} diff --git a/tests/mocks/libraries/session.php b/tests/mocks/libraries/session.php new file mode 100644 index 000000000..9d6feee42 --- /dev/null +++ b/tests/mocks/libraries/session.php @@ -0,0 +1,43 @@ +<?php + +/** + * Mock library to add testing features to Session driver library + */ +class Mock_Libraries_Session extends CI_Session { + /** + * Simulate new page load + */ + public function reload() + { + $this->_flashdata_sweep(); + $this->_flashdata_mark(); + $this->_tempdata_sweep(); + } +} + +/** + * Mock cookie driver to overload cookie setting + */ +class Mock_Libraries_Session_cookie extends CI_Session_cookie { + /** + * Overload _setcookie to manage $_COOKIE values, since actual cookies can't be set in unit testing + */ + protected function _setcookie($name, $value = '', $expire = 0, $path = '', $domain = '', $secure = false, + $httponly = false) + { + if (empty($value) || $expire <= time()) { + // Clear cookie + unset($_COOKIE[$name]); + } + else { + // Set cookie + $_COOKIE[$name] = $value; + } + } +} + +/** + * Mock native driver (just for consistency in loading) + */ +class Mock_Libraries_Session_native extends CI_Session_native { } + |