diff options
author | Florian Pritz <bluewind@xinu.at> | 2017-09-09 16:05:22 +0200 |
---|---|---|
committer | Florian Pritz <bluewind@xinu.at> | 2017-09-09 16:05:22 +0200 |
commit | 27639d64d06b62f237bbde253c46cd28fdce8884 (patch) | |
tree | 7a2f00cfd44cfcdfe6cb1abc1cfc0675632948c4 /system/core | |
parent | 9c5bfbee5b42ea50a5611c537b8dbf01d7a64f79 (diff) | |
parent | 6c7a4266410070d30f8f6bcdf9c9e67f3d6478e3 (diff) |
Merge tag '3.1.5' into dev-ci3
Signed-off-by: Florian Pritz <bluewind@xinu.at>
Diffstat (limited to 'system/core')
-rw-r--r-- | system/core/Benchmark.php | 105 | ||||
-rw-r--r-- | system/core/CodeIgniter.php | 467 | ||||
-rw-r--r-- | system/core/Common.php | 879 | ||||
-rw-r--r-- | system/core/Config.php | 400 | ||||
-rw-r--r-- | system/core/Controller.php | 74 | ||||
-rw-r--r-- | system/core/Exceptions.php | 267 | ||||
-rw-r--r-- | system/core/Hooks.php | 212 | ||||
-rw-r--r-- | system/core/Input.php | 1036 | ||||
-rw-r--r-- | system/core/Lang.php | 171 | ||||
-rw-r--r-- | system/core/Loader.php | 1205 | ||||
-rw-r--r-- | system/core/Log.php | 296 | ||||
-rw-r--r-- | system/core/Model.php | 77 | ||||
-rw-r--r-- | system/core/Output.php | 714 | ||||
-rw-r--r-- | system/core/Router.php | 587 | ||||
-rw-r--r-- | system/core/Security.php | 915 | ||||
-rw-r--r-- | system/core/URI.php | 628 | ||||
-rw-r--r-- | system/core/Utf8.php | 149 | ||||
-rw-r--r-- | system/core/compat/hash.php | 254 | ||||
-rw-r--r-- | system/core/compat/index.html | 11 | ||||
-rw-r--r-- | system/core/compat/mbstring.php | 149 | ||||
-rw-r--r-- | system/core/compat/password.php | 251 | ||||
-rw-r--r-- | system/core/compat/standard.php | 182 | ||||
-rw-r--r-- | system/core/index.html | 3 |
23 files changed, 5739 insertions, 3293 deletions
diff --git a/system/core/Benchmark.php b/system/core/Benchmark.php index a5c3e999b..b3ac79c62 100644 --- a/system/core/Benchmark.php +++ b/system/core/Benchmark.php @@ -1,61 +1,82 @@ -<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); +<?php /** * CodeIgniter * - * An open source application development framework for PHP 5.1.6 or newer + * An open source application development framework for PHP * - * @package CodeIgniter - * @author ExpressionEngine Dev Team - * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. - * @license http://codeigniter.com/user_guide/license.html - * @link http://codeigniter.com - * @since Version 1.0 + * This content is released under the MIT License (MIT) + * + * Copyright (c) 2014 - 2017, British Columbia Institute of Technology + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @package CodeIgniter + * @author EllisLab Dev Team + * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/) + * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) + * @license http://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com + * @since Version 1.0.0 * @filesource */ - -// ------------------------------------------------------------------------ +defined('BASEPATH') OR exit('No direct script access allowed'); /** - * CodeIgniter Benchmark Class + * Benchmark Class * * This class enables you to mark points and calculate the time difference - * between them. Memory consumption can also be displayed. + * between them. Memory consumption can also be displayed. * * @package CodeIgniter * @subpackage Libraries * @category Libraries - * @author ExpressionEngine Dev Team - * @link http://codeigniter.com/user_guide/libraries/benchmark.html + * @author EllisLab Dev Team + * @link https://codeigniter.com/user_guide/libraries/benchmark.html */ class CI_Benchmark { /** - * List of all benchmark markers and when they were added + * List of all benchmark markers * - * @var array + * @var array */ - var $marker = array(); - - // -------------------------------------------------------------------- + public $marker = array(); /** * Set a benchmark marker * * Multiple calls to this function can be made so that several - * execution points can be timed + * execution points can be timed. * - * @access public - * @param string $name name of the marker + * @param string $name Marker name * @return void */ - function mark($name) + public function mark($name) { - $this->marker[$name] = microtime(); + $this->marker[$name] = microtime(TRUE); } // -------------------------------------------------------------------- /** + * Elapsed time + * * Calculates the time difference between two marked points. * * If the first parameter is empty this function instead returns the @@ -63,15 +84,17 @@ class CI_Benchmark { * execution time to be shown in a template. The output class will * swap the real value for this variable. * - * @access public - * @param string a particular marked point - * @param string a particular marked point - * @param integer the number of decimal places - * @return mixed + * @param string $point1 A particular marked point + * @param string $point2 A particular marked point + * @param int $decimals Number of decimal places + * + * @return string Calculated elapsed time on success, + * an '{elapsed_string}' if $point1 is empty + * or an empty string if $point1 is not found. */ - function elapsed_time($point1 = '', $point2 = '', $decimals = 4) + public function elapsed_time($point1 = '', $point2 = '', $decimals = 4) { - if ($point1 == '') + if ($point1 === '') { return '{elapsed_time}'; } @@ -83,13 +106,10 @@ class CI_Benchmark { if ( ! isset($this->marker[$point2])) { - $this->marker[$point2] = microtime(); + $this->marker[$point2] = microtime(TRUE); } - list($sm, $ss) = explode(' ', $this->marker[$point1]); - list($em, $es) = explode(' ', $this->marker[$point2]); - - return number_format(($em + $es) - ($sm + $ss), $decimals); + return number_format($this->marker[$point2] - $this->marker[$point1], $decimals); } // -------------------------------------------------------------------- @@ -97,22 +117,17 @@ class CI_Benchmark { /** * Memory Usage * - * This function returns the {memory_usage} pseudo-variable. + * Simply returns the {memory_usage} marker. + * * This permits it to be put it anywhere in a template * without the memory being calculated until the end. * The output class will swap the real value for this variable. * - * @access public - * @return string + * @return string '{memory_usage}' */ - function memory_usage() + public function memory_usage() { return '{memory_usage}'; } } - -// END CI_Benchmark class - -/* End of file Benchmark.php */ -/* Location: ./system/core/Benchmark.php */
\ No newline at end of file diff --git a/system/core/CodeIgniter.php b/system/core/CodeIgniter.php index 34078174a..823e034d7 100644 --- a/system/core/CodeIgniter.php +++ b/system/core/CodeIgniter.php @@ -1,19 +1,41 @@ -<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); +<?php /** * CodeIgniter * - * An open source application development framework for PHP 5.1.6 or newer + * An open source application development framework for PHP * - * @package CodeIgniter - * @author ExpressionEngine Dev Team - * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. - * @license http://codeigniter.com/user_guide/license.html - * @link http://codeigniter.com - * @since Version 1.0 + * This content is released under the MIT License (MIT) + * + * Copyright (c) 2014 - 2017, British Columbia Institute of Technology + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @package CodeIgniter + * @author EllisLab Dev Team + * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/) + * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) + * @license http://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com + * @since Version 1.0.0 * @filesource */ - -// ------------------------------------------------------------------------ +defined('BASEPATH') OR exit('No direct script access allowed'); /** * System Initialization File @@ -21,60 +43,101 @@ * Loads the base classes and executes the request. * * @package CodeIgniter - * @subpackage codeigniter + * @subpackage CodeIgniter * @category Front-controller - * @author ExpressionEngine Dev Team - * @link http://codeigniter.com/user_guide/ + * @author EllisLab Dev Team + * @link https://codeigniter.com/user_guide/ */ /** * CodeIgniter Version * - * @var string + * @var string * */ - define('CI_VERSION', '2.2.0'); + const CI_VERSION = '3.1.5'; -/** - * CodeIgniter Branch (Core = TRUE, Reactor = FALSE) - * - * @var boolean - * +/* + * ------------------------------------------------------ + * Load the framework constants + * ------------------------------------------------------ */ - define('CI_CORE', FALSE); + if (file_exists(APPPATH.'config/'.ENVIRONMENT.'/constants.php')) + { + require_once(APPPATH.'config/'.ENVIRONMENT.'/constants.php'); + } + + if (file_exists(APPPATH.'config/constants.php')) + { + require_once(APPPATH.'config/constants.php'); + } /* * ------------------------------------------------------ * Load the global functions * ------------------------------------------------------ */ - require(BASEPATH.'core/Common.php'); + require_once(BASEPATH.'core/Common.php'); + /* * ------------------------------------------------------ - * Load the framework constants + * Security procedures * ------------------------------------------------------ */ - if (defined('ENVIRONMENT') AND file_exists(APPPATH.'config/'.ENVIRONMENT.'/constants.php')) - { - require(APPPATH.'config/'.ENVIRONMENT.'/constants.php'); - } - else + +if ( ! is_php('5.4')) +{ + ini_set('magic_quotes_runtime', 0); + + if ((bool) ini_get('register_globals')) { - require(APPPATH.'config/constants.php'); + $_protected = array( + '_SERVER', + '_GET', + '_POST', + '_FILES', + '_REQUEST', + '_SESSION', + '_ENV', + '_COOKIE', + 'GLOBALS', + 'HTTP_RAW_POST_DATA', + 'system_path', + 'application_folder', + 'view_folder', + '_protected', + '_registered' + ); + + $_registered = ini_get('variables_order'); + foreach (array('E' => '_ENV', 'G' => '_GET', 'P' => '_POST', 'C' => '_COOKIE', 'S' => '_SERVER') as $key => $superglobal) + { + if (strpos($_registered, $key) === FALSE) + { + continue; + } + + foreach (array_keys($$superglobal) as $var) + { + if (isset($GLOBALS[$var]) && ! in_array($var, $_protected, TRUE)) + { + $GLOBALS[$var] = NULL; + } + } + } } +} + /* * ------------------------------------------------------ * Define a custom error handler so we can log PHP errors * ------------------------------------------------------ */ - set_error_handler('_exception_handler'); - - if ( ! is_php('5.3')) - { - @set_magic_quotes_runtime(0); // Kill magic quotes - } + set_error_handler('_error_handler'); + set_exception_handler('_exception_handler'); + register_shutdown_function('_shutdown_handler'); /* * ------------------------------------------------------ @@ -85,26 +148,39 @@ * The subclass prefix allows CI to know if a core class is * being extended via a library in the local application * "libraries" folder. Since CI allows config items to be - * overriden via data set in the main index. php file, + * overridden via data set in the main index.php file, * before proceeding we need to know if a subclass_prefix - * override exists. If so, we will set this value now, + * override exists. If so, we will set this value now, * before any classes are loaded * Note: Since the config file data is cached it doesn't * hurt to load it here. */ - if (isset($assign_to_config['subclass_prefix']) AND $assign_to_config['subclass_prefix'] != '') + if ( ! empty($assign_to_config['subclass_prefix'])) { get_config(array('subclass_prefix' => $assign_to_config['subclass_prefix'])); } /* * ------------------------------------------------------ - * Set a liberal script execution time limit + * Should we use a Composer autoloader? * ------------------------------------------------------ */ - if (function_exists("set_time_limit") == TRUE AND @ini_get("safe_mode") == 0) + if ($composer_autoload = config_item('composer_autoload')) { - @set_time_limit(300); + if ($composer_autoload === TRUE) + { + file_exists(APPPATH.'vendor/autoload.php') + ? require_once(APPPATH.'vendor/autoload.php') + : log_message('error', '$config[\'composer_autoload\'] is set to TRUE but '.APPPATH.'vendor/autoload.php was not found.'); + } + elseif (file_exists($composer_autoload)) + { + require_once($composer_autoload); + } + else + { + log_message('error', 'Could not find the specified $config[\'composer_autoload\'] path: '.$composer_autoload); + } } /* @@ -128,33 +204,96 @@ * Is there a "pre_system" hook? * ------------------------------------------------------ */ - $EXT->_call_hook('pre_system'); + $EXT->call_hook('pre_system'); /* * ------------------------------------------------------ * Instantiate the config class * ------------------------------------------------------ + * + * Note: It is important that Config is loaded first as + * most other classes depend on it either directly or by + * depending on another class that uses it. + * */ $CFG =& load_class('Config', 'core'); // Do we have any manually set config items in the index.php file? - if (isset($assign_to_config)) + if (isset($assign_to_config) && is_array($assign_to_config)) { - $CFG->_assign_to_config($assign_to_config); + foreach ($assign_to_config as $key => $value) + { + $CFG->set_item($key, $value); + } } /* * ------------------------------------------------------ - * Instantiate the UTF-8 class + * Important charset-related stuff * ------------------------------------------------------ * - * Note: Order here is rather important as the UTF-8 - * class needs to be used very early on, but it cannot - * properly determine if UTf-8 can be supported until - * after the Config class is instantiated. + * Configure mbstring and/or iconv if they are enabled + * and set MB_ENABLED and ICONV_ENABLED constants, so + * that we don't repeatedly do extension_loaded() or + * function_exists() calls. + * + * Note: UTF-8 class depends on this. It used to be done + * in it's constructor, but it's _not_ class-specific. * */ + $charset = strtoupper(config_item('charset')); + ini_set('default_charset', $charset); + + if (extension_loaded('mbstring')) + { + define('MB_ENABLED', TRUE); + // mbstring.internal_encoding is deprecated starting with PHP 5.6 + // and it's usage triggers E_DEPRECATED messages. + @ini_set('mbstring.internal_encoding', $charset); + // This is required for mb_convert_encoding() to strip invalid characters. + // That's utilized by CI_Utf8, but it's also done for consistency with iconv. + mb_substitute_character('none'); + } + else + { + define('MB_ENABLED', FALSE); + } + // There's an ICONV_IMPL constant, but the PHP manual says that using + // iconv's predefined constants is "strongly discouraged". + if (extension_loaded('iconv')) + { + define('ICONV_ENABLED', TRUE); + // iconv.internal_encoding is deprecated starting with PHP 5.6 + // and it's usage triggers E_DEPRECATED messages. + @ini_set('iconv.internal_encoding', $charset); + } + else + { + define('ICONV_ENABLED', FALSE); + } + + if (is_php('5.6')) + { + ini_set('php.internal_encoding', $charset); + } + +/* + * ------------------------------------------------------ + * Load compatibility features + * ------------------------------------------------------ + */ + + require_once(BASEPATH.'core/compat/mbstring.php'); + require_once(BASEPATH.'core/compat/hash.php'); + require_once(BASEPATH.'core/compat/password.php'); + require_once(BASEPATH.'core/compat/standard.php'); + +/* + * ------------------------------------------------------ + * Instantiate the UTF-8 class + * ------------------------------------------------------ + */ $UNI =& load_class('Utf8', 'core'); /* @@ -169,14 +308,7 @@ * Instantiate the routing class and set the routing * ------------------------------------------------------ */ - $RTR =& load_class('Router', 'core'); - $RTR->_set_routing(); - - // Set any routing overrides that may exist in the main index file - if (isset($routing)) - { - $RTR->_set_overrides($routing); - } + $RTR =& load_class('Router', 'core', isset($routing) ? $routing : NULL); /* * ------------------------------------------------------ @@ -187,15 +319,12 @@ /* * ------------------------------------------------------ - * Is there a valid cache file? If so, we're done... + * Is there a valid cache file? If so, we're done... * ------------------------------------------------------ */ - if ($EXT->_call_hook('cache_override') === FALSE) + if ($EXT->call_hook('cache_override') === FALSE && $OUT->_display_cache($CFG, $URI) === TRUE) { - if ($OUT->_display_cache($CFG, $URI) == TRUE) - { - exit; - } + exit; } /* @@ -226,76 +355,157 @@ * */ // Load the base controller class - require BASEPATH.'core/Controller.php'; - + require_once BASEPATH.'core/Controller.php'; + + /** + * Reference to the CI_Controller method. + * + * Returns current CI instance object + * + * @return CI_Controller + */ function &get_instance() { return CI_Controller::get_instance(); } - if (file_exists(APPPATH.'core/'.$CFG->config['subclass_prefix'].'Controller.php')) { - require APPPATH.'core/'.$CFG->config['subclass_prefix'].'Controller.php'; + require_once APPPATH.'core/'.$CFG->config['subclass_prefix'].'Controller.php'; } - // Load the local application controller - // Note: The Router class automatically validates the controller path using the router->_validate_request(). - // If this include fails it means that the default controller in the Routes.php file is not resolving to something valid. - if ( ! file_exists(APPPATH.'controllers/'.$RTR->fetch_directory().$RTR->fetch_class().'.php')) - { - show_error('Unable to load your default controller. Please make sure the controller specified in your Routes.php file is valid.'); - } - - include(APPPATH.'controllers/'.$RTR->fetch_directory().$RTR->fetch_class().'.php'); - // Set a mark point for benchmarking $BM->mark('loading_time:_base_classes_end'); /* * ------------------------------------------------------ - * Security check + * Sanity checks * ------------------------------------------------------ * - * None of the functions in the app controller or the - * loader class can be called via the URI, nor can - * controller functions that begin with an underscore + * The Router class has already validated the request, + * leaving us with 3 options here: + * + * 1) an empty class name, if we reached the default + * controller, but it didn't exist; + * 2) a query string which doesn't go through a + * file_exists() check + * 3) a regular request for a non-existing page + * + * We handle all of these as a 404 error. + * + * Furthermore, none of the methods in the app controller + * or the loader class can be called via the URI, nor can + * controller methods that begin with an underscore. */ - $class = $RTR->fetch_class(); - $method = $RTR->fetch_method(); - if ( ! class_exists($class) - OR strncmp($method, '_', 1) == 0 - OR in_array(strtolower($method), array_map('strtolower', get_class_methods('CI_Controller'))) - ) + $e404 = FALSE; + $class = ucfirst($RTR->class); + $method = $RTR->method; + + if (empty($class) OR ! file_exists(APPPATH.'controllers/'.$RTR->directory.$class.'.php')) + { + $e404 = TRUE; + } + else + { + require_once(APPPATH.'controllers/'.$RTR->directory.$class.'.php'); + + if ( ! class_exists($class, FALSE) OR $method[0] === '_' OR method_exists('CI_Controller', $method)) + { + $e404 = TRUE; + } + elseif (method_exists($class, '_remap')) + { + $params = array($method, array_slice($URI->rsegments, 2)); + $method = '_remap'; + } + elseif ( ! method_exists($class, $method)) + { + $e404 = TRUE; + } + /** + * DO NOT CHANGE THIS, NOTHING ELSE WORKS! + * + * - method_exists() returns true for non-public methods, which passes the previous elseif + * - is_callable() returns false for PHP 4-style constructors, even if there's a __construct() + * - method_exists($class, '__construct') won't work because CI_Controller::__construct() is inherited + * - People will only complain if this doesn't work, even though it is documented that it shouldn't. + * + * ReflectionMethod::isConstructor() is the ONLY reliable check, + * knowing which method will be executed as a constructor. + */ + elseif ( ! is_callable(array($class, $method))) + { + $reflection = new ReflectionMethod($class, $method); + if ( ! $reflection->isPublic() OR $reflection->isConstructor()) + { + $e404 = TRUE; + } + } + } + + if ($e404) { if ( ! empty($RTR->routes['404_override'])) { - $x = explode('/', $RTR->routes['404_override']); - $class = $x[0]; - $method = (isset($x[1]) ? $x[1] : 'index'); - if ( ! class_exists($class)) + if (sscanf($RTR->routes['404_override'], '%[^/]/%s', $error_class, $error_method) !== 2) + { + $error_method = 'index'; + } + + $error_class = ucfirst($error_class); + + if ( ! class_exists($error_class, FALSE)) { - if ( ! file_exists(APPPATH.'controllers/'.$class.'.php')) + if (file_exists(APPPATH.'controllers/'.$RTR->directory.$error_class.'.php')) { - show_404("{$class}/{$method}"); + require_once(APPPATH.'controllers/'.$RTR->directory.$error_class.'.php'); + $e404 = ! class_exists($error_class, FALSE); + } + // Were we in a directory? If so, check for a global override + elseif ( ! empty($RTR->directory) && file_exists(APPPATH.'controllers/'.$error_class.'.php')) + { + require_once(APPPATH.'controllers/'.$error_class.'.php'); + if (($e404 = ! class_exists($error_class, FALSE)) === FALSE) + { + $RTR->directory = ''; + } } - - include_once(APPPATH.'controllers/'.$class.'.php'); } + else + { + $e404 = FALSE; + } + } + + // Did we reset the $e404 flag? If so, set the rsegments, starting from index 1 + if ( ! $e404) + { + $class = $error_class; + $method = $error_method; + + $URI->rsegments = array( + 1 => $class, + 2 => $method + ); } else { - show_404("{$class}/{$method}"); + show_404($RTR->directory.$class.'/'.$method); } } + if ($method !== '_remap') + { + $params = array_slice($URI->rsegments, 2); + } + /* * ------------------------------------------------------ * Is there a "pre_controller" hook? * ------------------------------------------------------ */ - $EXT->_call_hook('pre_controller'); + $EXT->call_hook('pre_controller'); /* * ------------------------------------------------------ @@ -312,53 +522,14 @@ * Is there a "post_controller_constructor" hook? * ------------------------------------------------------ */ - $EXT->_call_hook('post_controller_constructor'); + $EXT->call_hook('post_controller_constructor'); /* * ------------------------------------------------------ * Call the requested method * ------------------------------------------------------ */ - // Is there a "remap" function? If so, we call it instead - if (method_exists($CI, '_remap')) - { - $CI->_remap($method, array_slice($URI->rsegments, 2)); - } - else - { - // is_callable() returns TRUE on some versions of PHP 5 for private and protected - // methods, so we'll use this workaround for consistent behavior - if ( ! in_array(strtolower($method), array_map('strtolower', get_class_methods($CI)))) - { - // Check and see if we are using a 404 override and use it. - if ( ! empty($RTR->routes['404_override'])) - { - $x = explode('/', $RTR->routes['404_override']); - $class = $x[0]; - $method = (isset($x[1]) ? $x[1] : 'index'); - if ( ! class_exists($class)) - { - if ( ! file_exists(APPPATH.'controllers/'.$class.'.php')) - { - show_404("{$class}/{$method}"); - } - - include_once(APPPATH.'controllers/'.$class.'.php'); - unset($CI); - $CI = new $class(); - } - } - else - { - show_404("{$class}/{$method}"); - } - } - - // Call the requested method. - // Any URI segments present (besides the class/function) will be passed to the method for convenience - call_user_func_array(array(&$CI, $method), array_slice($URI->rsegments, 2)); - } - + call_user_func_array(array(&$CI, $method), $params); // Mark a benchmark end point $BM->mark('controller_execution_time_( '.$class.' / '.$method.' )_end'); @@ -368,14 +539,14 @@ * Is there a "post_controller" hook? * ------------------------------------------------------ */ - $EXT->_call_hook('post_controller'); + $EXT->call_hook('post_controller'); /* * ------------------------------------------------------ * Send the final rendered output to the browser * ------------------------------------------------------ */ - if ($EXT->_call_hook('display_override') === FALSE) + if ($EXT->call_hook('display_override') === FALSE) { $OUT->_display(); } @@ -385,18 +556,4 @@ * Is there a "post_system" hook? * ------------------------------------------------------ */ - $EXT->_call_hook('post_system'); - -/* - * ------------------------------------------------------ - * Close the DB connection if one exists - * ------------------------------------------------------ - */ - if (class_exists('CI_DB') AND isset($CI->db)) - { - $CI->db->close(); - } - - -/* End of file CodeIgniter.php */ -/* Location: ./system/core/CodeIgniter.php */
\ No newline at end of file + $EXT->call_hook('post_system'); diff --git a/system/core/Common.php b/system/core/Common.php index 4bf8a9ef5..d6a1fdb4e 100644 --- a/system/core/Common.php +++ b/system/core/Common.php @@ -1,24 +1,41 @@ -<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); -/** - * MODIFIED - * config_item(): option to override returned values - */ - +<?php /** * CodeIgniter * - * An open source application development framework for PHP 5.1.6 or newer + * An open source application development framework for PHP * - * @package CodeIgniter - * @author ExpressionEngine Dev Team - * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. - * @license http://codeigniter.com/user_guide/license.html - * @link http://codeigniter.com - * @since Version 1.0 + * This content is released under the MIT License (MIT) + * + * Copyright (c) 2014 - 2017, British Columbia Institute of Technology + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @package CodeIgniter + * @author EllisLab Dev Team + * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/) + * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) + * @license http://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com + * @since Version 1.0.0 * @filesource */ - -// ------------------------------------------------------------------------ +defined('BASEPATH') OR exit('No direct script access allowed'); /** * Common Functions @@ -26,34 +43,30 @@ * Loads the base classes and executes the request. * * @package CodeIgniter - * @subpackage codeigniter + * @subpackage CodeIgniter * @category Common Functions - * @author ExpressionEngine Dev Team - * @link http://codeigniter.com/user_guide/ + * @author EllisLab Dev Team + * @link https://codeigniter.com/user_guide/ */ // ------------------------------------------------------------------------ -/** -* Determines if the current version of PHP is greater then the supplied value -* -* Since there are a few places where we conditionally test for PHP > 5 -* we'll set a static variable. -* -* @access public -* @param string -* @return bool TRUE if the current version is $version or higher -*/ if ( ! function_exists('is_php')) { - function is_php($version = '5.0.0') + /** + * Determines if the current version of PHP is equal to or greater than the supplied value + * + * @param string + * @return bool TRUE if the current version is $version or higher + */ + function is_php($version) { static $_is_php; - $version = (string)$version; + $version = (string) $version; if ( ! isset($_is_php[$version])) { - $_is_php[$version] = (version_compare(PHP_VERSION, $version) < 0) ? FALSE : TRUE; + $_is_php[$version] = version_compare(PHP_VERSION, $version, '>='); } return $_is_php[$version]; @@ -62,43 +75,44 @@ if ( ! function_exists('is_php')) // ------------------------------------------------------------------------ -/** - * Tests for file writability - * - * is_writable() returns TRUE on Windows servers when you really can't write to - * the file, based on the read-only attribute. is_writable() is also unreliable - * on Unix servers if safe_mode is on. - * - * @access private - * @return void - */ if ( ! function_exists('is_really_writable')) { + /** + * Tests for file writability + * + * is_writable() returns TRUE on Windows servers when you really can't write to + * the file, based on the read-only attribute. is_writable() is also unreliable + * on Unix servers if safe_mode is on. + * + * @link https://bugs.php.net/bug.php?id=54709 + * @param string + * @return bool + */ function is_really_writable($file) { // If we're on a Unix server with safe_mode off we call is_writable - if (DIRECTORY_SEPARATOR == '/' AND @ini_get("safe_mode") == FALSE) + if (DIRECTORY_SEPARATOR === '/' && (is_php('5.4') OR ! ini_get('safe_mode'))) { return is_writable($file); } - // For windows servers and safe_mode "on" installations we'll actually - // write a file then read it. Bah... + /* For Windows servers and safe_mode "on" installations we'll actually + * write a file then read it. Bah... + */ if (is_dir($file)) { - $file = rtrim($file, '/').'/'.md5(mt_rand(1,100).mt_rand(1,100)); - - if (($fp = @fopen($file, FOPEN_WRITE_CREATE)) === FALSE) + $file = rtrim($file, '/').'/'.md5(mt_rand()); + if (($fp = @fopen($file, 'ab')) === FALSE) { return FALSE; } fclose($fp); - @chmod($file, DIR_WRITE_MODE); + @chmod($file, 0777); @unlink($file); return TRUE; } - elseif ( ! is_file($file) OR ($fp = @fopen($file, FOPEN_WRITE_CREATE)) === FALSE) + elseif ( ! is_file($file) OR ($fp = @fopen($file, 'ab')) === FALSE) { return FALSE; } @@ -110,26 +124,25 @@ if ( ! function_exists('is_really_writable')) // ------------------------------------------------------------------------ -/** -* Class registry -* -* This function acts as a singleton. If the requested class does not -* exist it is instantiated and set to a static variable. If it has -* previously been instantiated the variable is returned. -* -* @access public -* @param string the class name being requested -* @param string the directory where the class should be found -* @param string the class name prefix -* @return object -*/ if ( ! function_exists('load_class')) { - function &load_class($class, $directory = 'libraries', $prefix = 'CI_') + /** + * Class registry + * + * This function acts as a singleton. If the requested class does not + * exist it is instantiated and set to a static variable. If it has + * previously been instantiated the variable is returned. + * + * @param string the class name being requested + * @param string the directory where the class should be found + * @param mixed an optional argument to pass to the class constructor + * @return object + */ + function &load_class($class, $directory = 'libraries', $param = NULL) { static $_classes = array(); - // Does the class exist? If so, we're done... + // Does the class exist? If so, we're done... if (isset($_classes[$class])) { return $_classes[$class]; @@ -143,60 +156,64 @@ if ( ! function_exists('load_class')) { if (file_exists($path.$directory.'/'.$class.'.php')) { - $name = $prefix.$class; + $name = 'CI_'.$class; - if (class_exists($name) === FALSE) + if (class_exists($name, FALSE) === FALSE) { - require($path.$directory.'/'.$class.'.php'); + require_once($path.$directory.'/'.$class.'.php'); } break; } } - // Is the request a class extension? If so we load it too + // Is the request a class extension? If so we load it too if (file_exists(APPPATH.$directory.'/'.config_item('subclass_prefix').$class.'.php')) { $name = config_item('subclass_prefix').$class; - if (class_exists($name) === FALSE) + if (class_exists($name, FALSE) === FALSE) { - require(APPPATH.$directory.'/'.config_item('subclass_prefix').$class.'.php'); + require_once(APPPATH.$directory.'/'.$name.'.php'); } } // Did we find the class? if ($name === FALSE) { - // Note: We use exit() rather then show_error() in order to avoid a - // self-referencing loop with the Excptions class - exit('Unable to locate the specified class: '.$class.'.php'); + // Note: We use exit() rather than show_error() in order to avoid a + // self-referencing loop with the Exceptions class + set_status_header(503); + echo 'Unable to locate the specified class: '.$class.'.php'; + exit(5); // EXIT_UNK_CLASS } // Keep track of what we just loaded is_loaded($class); - $_classes[$class] = new $name(); + $_classes[$class] = isset($param) + ? new $name($param) + : new $name(); return $_classes[$class]; } } // -------------------------------------------------------------------- -/** -* Keeps track of which libraries have been loaded. This function is -* called by the load_class() function above -* -* @access public -* @return array -*/ if ( ! function_exists('is_loaded')) { + /** + * Keeps track of which libraries have been loaded. This function is + * called by the load_class() function above + * + * @param string + * @return array + */ function &is_loaded($class = '') { static $_is_loaded = array(); - if ($class != '') + if ($class !== '') { $_is_loaded[strtolower($class)] = $class; } @@ -207,333 +224,500 @@ if ( ! function_exists('is_loaded')) // ------------------------------------------------------------------------ -/** -* Loads the main config.php file -* -* This function lets us grab the config file even if the Config class -* hasn't been instantiated yet -* -* @access private -* @return array -*/ if ( ! function_exists('get_config')) { - function &get_config($replace = array()) + /** + * Loads the main config.php file + * + * This function lets us grab the config file even if the Config class + * hasn't been instantiated yet + * + * @param array + * @return array + */ + function &get_config(Array $replace = array()) { - static $_config; + static $config; - if (isset($_config)) - { - return $_config[0]; - } - - // Is the config file in the environment folder? - if ( ! defined('ENVIRONMENT') OR ! file_exists($file_path = APPPATH.'config/'.ENVIRONMENT.'/config.php')) + if (empty($config)) { $file_path = APPPATH.'config/config.php'; + $found = FALSE; + if (file_exists($file_path)) + { + $found = TRUE; + require($file_path); + } + + // Is the config file in the environment folder? + if (file_exists($file_path = APPPATH.'config/'.ENVIRONMENT.'/config.php')) + { + require($file_path); + } + elseif ( ! $found) + { + set_status_header(503); + echo 'The configuration file does not exist.'; + exit(3); // EXIT_CONFIG + } + + // Does the $config array exist in the file? + if ( ! isset($config) OR ! is_array($config)) + { + set_status_header(503); + echo 'Your config file does not appear to be formatted correctly.'; + exit(3); // EXIT_CONFIG + } } - // Fetch the config file - if ( ! file_exists($file_path)) + // Are any values being dynamically added or replaced? + foreach ($replace as $key => $val) { - exit('The configuration file does not exist.'); + $config[$key] = $val; } - require($file_path); + return $config; + } +} - // Does the $config array exist in the file? - if ( ! isset($config) OR ! is_array($config)) - { - exit('Your config file does not appear to be formatted correctly.'); - } +// ------------------------------------------------------------------------ - // Are any values being dynamically replaced? - if (count($replace) > 0) +if ( ! function_exists('config_item')) +{ + /** + * Returns the specified config item + * + * @param string + * @return mixed + */ + function config_item($item) + { + static $_config; + + if (empty($_config)) { - foreach ($replace as $key => $val) - { - if (isset($config[$key])) - { - $config[$key] = $val; - } - } + // references cannot be directly assigned to static variables, so we use an array + $_config[0] =& get_config(); } - $_config[0] =& $config; - return $_config[0]; + return isset($_config[0][$item]) ? $_config[0][$item] : NULL; } } // ------------------------------------------------------------------------ -/** -* Returns the specified config item -* -* @access public -* @return mixed -*/ -if ( ! function_exists('config_item')) +if ( ! function_exists('get_mimes')) { - function config_item($item, $value = null) + /** + * Returns the MIME types array from config/mimes.php + * + * @return array + */ + function &get_mimes() { - static $_config_item = array(); + static $_mimes; - if ( ! isset($_config_item[$item])) + if (empty($_mimes)) { - $config =& get_config(); + $_mimes = file_exists(APPPATH.'config/mimes.php') + ? include(APPPATH.'config/mimes.php') + : array(); - if ( ! isset($config[$item])) + if (file_exists(APPPATH.'config/'.ENVIRONMENT.'/mimes.php')) { - return FALSE; + $_mimes = array_merge($_mimes, include(APPPATH.'config/'.ENVIRONMENT.'/mimes.php')); } - $_config_item[$item] = $config[$item]; } - if ($value !== null) { - $_config_item[$item] = $value; + return $_mimes; + } +} + +// ------------------------------------------------------------------------ + +if ( ! function_exists('is_https')) +{ + /** + * Is HTTPS? + * + * Determines if the application is accessed via an encrypted + * (HTTPS) connection. + * + * @return bool + */ + function is_https() + { + if ( ! empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) !== 'off') + { + return TRUE; + } + elseif (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) === 'https') + { + return TRUE; + } + elseif ( ! empty($_SERVER['HTTP_FRONT_END_HTTPS']) && strtolower($_SERVER['HTTP_FRONT_END_HTTPS']) !== 'off') + { + return TRUE; } - return $_config_item[$item]; + return FALSE; + } +} + +// ------------------------------------------------------------------------ + +if ( ! function_exists('is_cli')) +{ + + /** + * Is CLI? + * + * Test to see if a request was made from the command line. + * + * @return bool + */ + function is_cli() + { + return (PHP_SAPI === 'cli' OR defined('STDIN')); } } // ------------------------------------------------------------------------ -/** -* Error Handler -* -* This function lets us invoke the exception class and -* display errors using the standard error template located -* in application/errors/errors.php -* This function will send the error page directly to the -* browser and exit. -* -* @access public -* @return void -*/ if ( ! function_exists('show_error')) { + /** + * Error Handler + * + * This function lets us invoke the exception class and + * display errors using the standard error template located + * in application/views/errors/error_general.php + * This function will send the error page directly to the + * browser and exit. + * + * @param string + * @param int + * @param string + * @return void + */ function show_error($message, $status_code = 500, $heading = 'An Error Was Encountered') { + $status_code = abs($status_code); + if ($status_code < 100) + { + $exit_status = $status_code + 9; // 9 is EXIT__AUTO_MIN + $status_code = 500; + } + else + { + $exit_status = 1; // EXIT_ERROR + } + $_error =& load_class('Exceptions', 'core'); echo $_error->show_error($heading, $message, 'error_general', $status_code); - exit; + exit($exit_status); } } // ------------------------------------------------------------------------ -/** -* 404 Page Handler -* -* This function is similar to the show_error() function above -* However, instead of the standard error template it displays -* 404 errors. -* -* @access public -* @return void -*/ if ( ! function_exists('show_404')) { + /** + * 404 Page Handler + * + * This function is similar to the show_error() function above + * However, instead of the standard error template it displays + * 404 errors. + * + * @param string + * @param bool + * @return void + */ function show_404($page = '', $log_error = TRUE) { $_error =& load_class('Exceptions', 'core'); $_error->show_404($page, $log_error); - exit; + exit(4); // EXIT_UNKNOWN_FILE } } // ------------------------------------------------------------------------ -/** -* Error Logging Interface -* -* We use this as a simple mechanism to access the logging -* class and send messages to be logged. -* -* @access public -* @return void -*/ if ( ! function_exists('log_message')) { - function log_message($level = 'error', $message, $php_error = FALSE) + /** + * Error Logging Interface + * + * We use this as a simple mechanism to access the logging + * class and send messages to be logged. + * + * @param string the error level: 'error', 'debug' or 'info' + * @param string the error message + * @return void + */ + function log_message($level, $message) { static $_log; - if (config_item('log_threshold') == 0) + if ($_log === NULL) { - return; + // references cannot be directly assigned to static variables, so we use an array + $_log[0] =& load_class('Log', 'core'); } - $_log =& load_class('Log'); - $_log->write_log($level, $message, $php_error); + $_log[0]->write_log($level, $message); } } // ------------------------------------------------------------------------ -/** - * Set HTTP Status Header - * - * @access public - * @param int the status code - * @param string - * @return void - */ if ( ! function_exists('set_status_header')) { + /** + * Set HTTP Status Header + * + * @param int the status code + * @param string + * @return void + */ function set_status_header($code = 200, $text = '') { - $stati = array( - 200 => 'OK', - 201 => 'Created', - 202 => 'Accepted', - 203 => 'Non-Authoritative Information', - 204 => 'No Content', - 205 => 'Reset Content', - 206 => 'Partial Content', - - 300 => 'Multiple Choices', - 301 => 'Moved Permanently', - 302 => 'Found', - 304 => 'Not Modified', - 305 => 'Use Proxy', - 307 => 'Temporary Redirect', - - 400 => 'Bad Request', - 401 => 'Unauthorized', - 403 => 'Forbidden', - 404 => 'Not Found', - 405 => 'Method Not Allowed', - 406 => 'Not Acceptable', - 407 => 'Proxy Authentication Required', - 408 => 'Request Timeout', - 409 => 'Conflict', - 410 => 'Gone', - 411 => 'Length Required', - 412 => 'Precondition Failed', - 413 => 'Request Entity Too Large', - 414 => 'Request-URI Too Long', - 415 => 'Unsupported Media Type', - 416 => 'Requested Range Not Satisfiable', - 417 => 'Expectation Failed', - - 500 => 'Internal Server Error', - 501 => 'Not Implemented', - 502 => 'Bad Gateway', - 503 => 'Service Unavailable', - 504 => 'Gateway Timeout', - 505 => 'HTTP Version Not Supported' - ); - - if ($code == '' OR ! is_numeric($code)) + if (is_cli()) { - show_error('Status codes must be numeric', 500); + return; } - if (isset($stati[$code]) AND $text == '') + if (empty($code) OR ! is_numeric($code)) { - $text = $stati[$code]; + show_error('Status codes must be numeric', 500); } - if ($text == '') + if (empty($text)) { - show_error('No status text available. Please check your status code number or supply your own message text.', 500); + is_int($code) OR $code = (int) $code; + $stati = array( + 100 => 'Continue', + 101 => 'Switching Protocols', + + 200 => 'OK', + 201 => 'Created', + 202 => 'Accepted', + 203 => 'Non-Authoritative Information', + 204 => 'No Content', + 205 => 'Reset Content', + 206 => 'Partial Content', + + 300 => 'Multiple Choices', + 301 => 'Moved Permanently', + 302 => 'Found', + 303 => 'See Other', + 304 => 'Not Modified', + 305 => 'Use Proxy', + 307 => 'Temporary Redirect', + + 400 => 'Bad Request', + 401 => 'Unauthorized', + 402 => 'Payment Required', + 403 => 'Forbidden', + 404 => 'Not Found', + 405 => 'Method Not Allowed', + 406 => 'Not Acceptable', + 407 => 'Proxy Authentication Required', + 408 => 'Request Timeout', + 409 => 'Conflict', + 410 => 'Gone', + 411 => 'Length Required', + 412 => 'Precondition Failed', + 413 => 'Request Entity Too Large', + 414 => 'Request-URI Too Long', + 415 => 'Unsupported Media Type', + 416 => 'Requested Range Not Satisfiable', + 417 => 'Expectation Failed', + 422 => 'Unprocessable Entity', + 426 => 'Upgrade Required', + 428 => 'Precondition Required', + 429 => 'Too Many Requests', + 431 => 'Request Header Fields Too Large', + + 500 => 'Internal Server Error', + 501 => 'Not Implemented', + 502 => 'Bad Gateway', + 503 => 'Service Unavailable', + 504 => 'Gateway Timeout', + 505 => 'HTTP Version Not Supported', + 511 => 'Network Authentication Required', + ); + + if (isset($stati[$code])) + { + $text = $stati[$code]; + } + else + { + show_error('No status text available. Please check your status code number or supply your own message text.', 500); + } } - $server_protocol = (isset($_SERVER['SERVER_PROTOCOL'])) ? $_SERVER['SERVER_PROTOCOL'] : FALSE; - - if (substr(php_sapi_name(), 0, 3) == 'cgi') - { - header("Status: {$code} {$text}", TRUE); - } - elseif ($server_protocol == 'HTTP/1.1' OR $server_protocol == 'HTTP/1.0') - { - header($server_protocol." {$code} {$text}", TRUE, $code); - } - else + if (strpos(PHP_SAPI, 'cgi') === 0) { - header("HTTP/1.1 {$code} {$text}", TRUE, $code); + header('Status: '.$code.' '.$text, TRUE); + return; } + + $server_protocol = (isset($_SERVER['SERVER_PROTOCOL']) && in_array($_SERVER['SERVER_PROTOCOL'], array('HTTP/1.0', 'HTTP/1.1', 'HTTP/2'), TRUE)) + ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.1'; + header($server_protocol.' '.$code.' '.$text, TRUE, $code); } } // -------------------------------------------------------------------- -/** -* Exception Handler -* -* This is the custom exception handler that is declaired at the top -* of Codeigniter.php. The main reason we use this is to permit -* PHP errors to be logged in our own log files since the user may -* not have access to server logs. Since this function -* effectively intercepts PHP errors, however, we also need -* to display errors based on the current error_reporting level. -* We do that with the use of a PHP error template. -* -* @access private -* @return void -*/ -if ( ! function_exists('_exception_handler')) +if ( ! function_exists('_error_handler')) { - function _exception_handler($severity, $message, $filepath, $line) + /** + * Error Handler + * + * This is the custom error handler that is declared at the (relative) + * top of CodeIgniter.php. The main reason we use this is to permit + * PHP errors to be logged in our own log files since the user may + * not have access to server logs. Since this function effectively + * intercepts PHP errors, however, we also need to display errors + * based on the current error_reporting level. + * We do that with the use of a PHP error template. + * + * @param int $severity + * @param string $message + * @param string $filepath + * @param int $line + * @return void + */ + function _error_handler($severity, $message, $filepath, $line) { - // We don't bother with "strict" notices since they tend to fill up - // the log file with excess information that isn't normally very helpful. - // For example, if you are running PHP 5 and you use version 4 style - // class functions (without prefixes like "public", "private", etc.) - // you'll get notices telling you that these have been deprecated. - if ($severity == E_STRICT) + $is_error = (((E_ERROR | E_PARSE | E_COMPILE_ERROR | E_CORE_ERROR | E_USER_ERROR) & $severity) === $severity); + + // When an error occurred, set the status header to '500 Internal Server Error' + // to indicate to the client something went wrong. + // This can't be done within the $_error->show_php_error method because + // it is only called when the display_errors flag is set (which isn't usually + // the case in a production environment) or when errors are ignored because + // they are above the error_reporting threshold. + if ($is_error) + { + set_status_header(500); + } + + // Should we ignore the error? We'll get the current error_reporting + // level and add its bits with the severity bits to find out. + if (($severity & error_reporting()) !== $severity) { return; } $_error =& load_class('Exceptions', 'core'); + $_error->log_exception($severity, $message, $filepath, $line); - // Should we display the error? We'll get the current error_reporting - // level and add its bits with the severity bits to find out. - if (($severity & error_reporting()) == $severity) + // Should we display the error? + if (str_ireplace(array('off', 'none', 'no', 'false', 'null'), '', ini_get('display_errors'))) { $_error->show_php_error($severity, $message, $filepath, $line); } - // Should we log the error? No? We're done... - if (config_item('log_threshold') == 0) + // If the error is fatal, the execution of the script should be stopped because + // errors can't be recovered from. Halting the script conforms with PHP's + // default error handling. See http://www.php.net/manual/en/errorfunc.constants.php + if ($is_error) { - return; + exit(1); // EXIT_ERROR } + } +} - $_error->log_exception($severity, $message, $filepath, $line); +// ------------------------------------------------------------------------ + +if ( ! function_exists('_exception_handler')) +{ + /** + * Exception Handler + * + * Sends uncaught exceptions to the logger and displays them + * only if display_errors is On so that they don't show up in + * production environments. + * + * @param Exception $exception + * @return void + */ + function _exception_handler($exception) + { + $_error =& load_class('Exceptions', 'core'); + $_error->log_exception('error', 'Exception: '.$exception->getMessage(), $exception->getFile(), $exception->getLine()); + + is_cli() OR set_status_header(500); + // Should we display the error? + if (str_ireplace(array('off', 'none', 'no', 'false', 'null'), '', ini_get('display_errors'))) + { + $_error->show_exception($exception); + } + + exit(1); // EXIT_ERROR + } +} + +// ------------------------------------------------------------------------ + +if ( ! function_exists('_shutdown_handler')) +{ + /** + * Shutdown Handler + * + * This is the shutdown handler that is declared at the top + * of CodeIgniter.php. The main reason we use this is to simulate + * a complete custom exception handler. + * + * E_STRICT is purposively neglected because such events may have + * been caught. Duplication or none? None is preferred for now. + * + * @link http://insomanic.me.uk/post/229851073/php-trick-catching-fatal-errors-e-error-with-a + * @return void + */ + function _shutdown_handler() + { + $last_error = error_get_last(); + if (isset($last_error) && + ($last_error['type'] & (E_ERROR | E_PARSE | E_CORE_ERROR | E_CORE_WARNING | E_COMPILE_ERROR | E_COMPILE_WARNING))) + { + _error_handler($last_error['type'], $last_error['message'], $last_error['file'], $last_error['line']); + } } } // -------------------------------------------------------------------- -/** - * Remove Invisible Characters - * - * This prevents sandwiching null characters - * between ascii characters, like Java\0script. - * - * @access public - * @param string - * @return string - */ if ( ! function_exists('remove_invisible_characters')) { + /** + * Remove Invisible Characters + * + * This prevents sandwiching null characters + * between ascii characters, like Java\0script. + * + * @param string + * @param bool + * @return string + */ function remove_invisible_characters($str, $url_encoded = TRUE) { $non_displayables = array(); - - // every control character except newline (dec 10) - // carriage return (dec 13), and horizontal tab (dec 09) - + + // every control character except newline (dec 10), + // carriage return (dec 13) and horizontal tab (dec 09) if ($url_encoded) { - $non_displayables[] = '/%0[0-8bcef]/'; // url encoded 00-08, 11, 12, 14, 15 - $non_displayables[] = '/%1[0-9a-f]/'; // url encoded 16-31 + $non_displayables[] = '/%0[0-8bcef]/i'; // url encoded 00-08, 11, 12, 14, 15 + $non_displayables[] = '/%1[0-9a-f]/i'; // url encoded 16-31 + $non_displayables[] = '/%7f/i'; // url encoded 127 } - + $non_displayables[] = '/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]+/S'; // 00-08, 11, 12, 14-31, 127 do @@ -548,27 +732,118 @@ if ( ! function_exists('remove_invisible_characters')) // ------------------------------------------------------------------------ -/** -* Returns HTML escaped variable -* -* @access public -* @param mixed -* @return mixed -*/ if ( ! function_exists('html_escape')) { - function html_escape($var) + /** + * Returns HTML escaped variable. + * + * @param mixed $var The input string or array of strings to be escaped. + * @param bool $double_encode $double_encode set to FALSE prevents escaping twice. + * @return mixed The escaped string or array of strings as a result. + */ + function html_escape($var, $double_encode = TRUE) { + if (empty($var)) + { + return $var; + } + if (is_array($var)) { - return array_map('html_escape', $var); + foreach (array_keys($var) as $key) + { + $var[$key] = html_escape($var[$key], $double_encode); + } + + return $var; } - else + + return htmlspecialchars($var, ENT_QUOTES, config_item('charset'), $double_encode); + } +} + +// ------------------------------------------------------------------------ + +if ( ! function_exists('_stringify_attributes')) +{ + /** + * Stringify attributes for use in HTML tags. + * + * Helper function used to convert a string, array, or object + * of attributes to a string. + * + * @param mixed string, array, object + * @param bool + * @return string + */ + function _stringify_attributes($attributes, $js = FALSE) + { + $atts = NULL; + + if (empty($attributes)) + { + return $atts; + } + + if (is_string($attributes)) { - return htmlspecialchars($var, ENT_QUOTES, config_item('charset')); + return ' '.$attributes; } + + $attributes = (array) $attributes; + + foreach ($attributes as $key => $val) + { + $atts .= ($js) ? $key.'='.$val.',' : ' '.$key.'="'.$val.'"'; + } + + return rtrim($atts, ','); } } -/* End of file Common.php */ -/* Location: ./system/core/Common.php */
\ No newline at end of file +// ------------------------------------------------------------------------ + +if ( ! function_exists('function_usable')) +{ + /** + * Function usable + * + * Executes a function_exists() check, and if the Suhosin PHP + * extension is loaded - checks whether the function that is + * checked might be disabled in there as well. + * + * This is useful as function_exists() will return FALSE for + * functions disabled via the *disable_functions* php.ini + * setting, but not for *suhosin.executor.func.blacklist* and + * *suhosin.executor.disable_eval*. These settings will just + * terminate script execution if a disabled function is executed. + * + * The above described behavior turned out to be a bug in Suhosin, + * but even though a fix was committed for 0.9.34 on 2012-02-12, + * that version is yet to be released. This function will therefore + * be just temporary, but would probably be kept for a few years. + * + * @link http://www.hardened-php.net/suhosin/ + * @param string $function_name Function to check for + * @return bool TRUE if the function exists and is safe to call, + * FALSE otherwise. + */ + function function_usable($function_name) + { + static $_suhosin_func_blacklist; + + if (function_exists($function_name)) + { + if ( ! isset($_suhosin_func_blacklist)) + { + $_suhosin_func_blacklist = extension_loaded('suhosin') + ? explode(',', trim(ini_get('suhosin.executor.func.blacklist'))) + : array(); + } + + return ! in_array($function_name, $_suhosin_func_blacklist, TRUE); + } + + return FALSE; + } +} diff --git a/system/core/Config.php b/system/core/Config.php index caa8b945a..cda62241b 100644 --- a/system/core/Config.php +++ b/system/core/Config.php @@ -1,78 +1,107 @@ -<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); +<?php /** * CodeIgniter * - * An open source application development framework for PHP 5.1.6 or newer + * An open source application development framework for PHP * - * @package CodeIgniter - * @author ExpressionEngine Dev Team - * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. - * @license http://codeigniter.com/user_guide/license.html - * @link http://codeigniter.com - * @since Version 1.0 + * This content is released under the MIT License (MIT) + * + * Copyright (c) 2014 - 2017, British Columbia Institute of Technology + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @package CodeIgniter + * @author EllisLab Dev Team + * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/) + * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) + * @license http://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com + * @since Version 1.0.0 * @filesource */ - -// ------------------------------------------------------------------------ +defined('BASEPATH') OR exit('No direct script access allowed'); /** - * CodeIgniter Config Class + * Config Class * * This class contains functions that enable config files to be managed * * @package CodeIgniter * @subpackage Libraries * @category Libraries - * @author ExpressionEngine Dev Team - * @link http://codeigniter.com/user_guide/libraries/config.html + * @author EllisLab Dev Team + * @link https://codeigniter.com/user_guide/libraries/config.html */ class CI_Config { /** * List of all loaded config values * - * @var array + * @var array */ - var $config = array(); + public $config = array(); + /** * List of all loaded config files * - * @var array + * @var array */ - var $is_loaded = array(); + public $is_loaded = array(); + /** - * List of paths to search when trying to load a config file + * List of paths to search when trying to load a config file. * - * @var array + * @used-by CI_Loader + * @var array */ - var $_config_paths = array(APPPATH); + public $_config_paths = array(APPPATH); + + // -------------------------------------------------------------------- /** - * Constructor + * Class constructor * - * Sets the $config data from the primary config.php file as a class variable + * Sets the $config data from the primary config.php file as a class variable. * - * @access public - * @param string the config file name - * @param boolean if configuration values should be loaded into their own section - * @param boolean true if errors should just return false, false if an error message should be displayed - * @return boolean if the file was successfully loaded or not + * @return void */ - function __construct() + public function __construct() { $this->config =& get_config(); - log_message('debug', "Config Class Initialized"); // Set the base_url automatically if none was provided - if ($this->config['base_url'] == '') + if (empty($this->config['base_url'])) { - if (isset($_SERVER['HTTP_HOST'])) + if (isset($_SERVER['SERVER_ADDR'])) { - $base_url = isset($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) !== 'off' ? 'https' : 'http'; - $base_url .= '://'. $_SERVER['HTTP_HOST']; - $base_url .= str_replace(basename($_SERVER['SCRIPT_NAME']), '', $_SERVER['SCRIPT_NAME']); - } + if (strpos($_SERVER['SERVER_ADDR'], ':') !== FALSE) + { + $server_addr = '['.$_SERVER['SERVER_ADDR'].']'; + } + else + { + $server_addr = $_SERVER['SERVER_ADDR']; + } + $base_url = (is_https() ? 'https' : 'http').'://'.$server_addr + .substr($_SERVER['SCRIPT_NAME'], 0, strpos($_SERVER['SCRIPT_NAME'], basename($_SERVER['SCRIPT_FILENAME']))); + } else { $base_url = 'http://localhost/'; @@ -80,6 +109,8 @@ class CI_Config { $this->set_item('base_url', $base_url); } + + log_message('info', 'Config Class Initialized'); } // -------------------------------------------------------------------- @@ -87,91 +118,71 @@ class CI_Config { /** * Load Config File * - * @access public - * @param string the config file name - * @param boolean if configuration values should be loaded into their own section - * @param boolean true if errors should just return false, false if an error message should be displayed - * @return boolean if the file was loaded correctly + * @param string $file Configuration file name + * @param bool $use_sections Whether configuration values should be loaded into their own section + * @param bool $fail_gracefully Whether to just return FALSE or display an error message + * @return bool TRUE if the file was loaded correctly or FALSE on failure */ - function load($file = '', $use_sections = FALSE, $fail_gracefully = FALSE) + public function load($file = '', $use_sections = FALSE, $fail_gracefully = FALSE) { - $file = ($file == '') ? 'config' : str_replace('.php', '', $file); - $found = FALSE; + $file = ($file === '') ? 'config' : str_replace('.php', '', $file); $loaded = FALSE; - $check_locations = defined('ENVIRONMENT') - ? array(ENVIRONMENT.'/'.$file, $file) - : array($file); - foreach ($this->_config_paths as $path) { - foreach ($check_locations as $location) + foreach (array($file, ENVIRONMENT.DIRECTORY_SEPARATOR.$file) as $location) { $file_path = $path.'config/'.$location.'.php'; - if (in_array($file_path, $this->is_loaded, TRUE)) { - $loaded = TRUE; - continue 2; + return TRUE; } - if (file_exists($file_path)) + if ( ! file_exists($file_path)) { - $found = TRUE; - break; + continue; } - } - - if ($found === FALSE) - { - continue; - } - include($file_path); + include($file_path); - if ( ! isset($config) OR ! is_array($config)) - { - if ($fail_gracefully === TRUE) + if ( ! isset($config) OR ! is_array($config)) { - return FALSE; + if ($fail_gracefully === TRUE) + { + return FALSE; + } + + show_error('Your '.$file_path.' file does not appear to contain a valid configuration array.'); } - show_error('Your '.$file_path.' file does not appear to contain a valid configuration array.'); - } - if ($use_sections === TRUE) - { - if (isset($this->config[$file])) + if ($use_sections === TRUE) { - $this->config[$file] = array_merge($this->config[$file], $config); + $this->config[$file] = isset($this->config[$file]) + ? array_merge($this->config[$file], $config) + : $config; } else { - $this->config[$file] = $config; + $this->config = array_merge($this->config, $config); } - } - else - { - $this->config = array_merge($this->config, $config); - } - - $this->is_loaded[] = $file_path; - unset($config); - $loaded = TRUE; - log_message('debug', 'Config file loaded: '.$file_path); - break; + $this->is_loaded[] = $file_path; + $config = NULL; + $loaded = TRUE; + log_message('debug', 'Config file loaded: '.$file_path); + } } - if ($loaded === FALSE) + if ($loaded === TRUE) { - if ($fail_gracefully === TRUE) - { - return FALSE; - } - show_error('The configuration file '.$file.'.php does not exist.'); + return TRUE; + } + elseif ($fail_gracefully === TRUE) + { + return FALSE; } - return TRUE; + show_error('The configuration file '.$file.'.php does not exist.'); } // -------------------------------------------------------------------- @@ -179,59 +190,35 @@ class CI_Config { /** * Fetch a config file item * - * - * @access public - * @param string the config item name - * @param string the index name - * @param bool - * @return string + * @param string $item Config item name + * @param string $index Index name + * @return string|null The configuration item or NULL if the item doesn't exist */ - function item($item, $index = '') + public function item($item, $index = '') { if ($index == '') { - if ( ! isset($this->config[$item])) - { - return FALSE; - } - - $pref = $this->config[$item]; - } - else - { - if ( ! isset($this->config[$index])) - { - return FALSE; - } - - if ( ! isset($this->config[$index][$item])) - { - return FALSE; - } - - $pref = $this->config[$index][$item]; + return isset($this->config[$item]) ? $this->config[$item] : NULL; } - return $pref; + return isset($this->config[$index], $this->config[$index][$item]) ? $this->config[$index][$item] : NULL; } // -------------------------------------------------------------------- /** - * Fetch a config file item - adds slash after item (if item is not empty) + * Fetch a config file item with slash appended (if not empty) * - * @access public - * @param string the config item name - * @param bool - * @return string + * @param string $item Config item name + * @return string|null The configuration item or NULL if the item doesn't exist */ - function slash_item($item) + public function slash_item($item) { if ( ! isset($this->config[$item])) { - return FALSE; + return NULL; } - if( trim($this->config[$item]) == '') + elseif (trim($this->config[$item]) === '') { return ''; } @@ -243,80 +230,122 @@ class CI_Config { /** * Site URL + * * Returns base_url . index_page [. uri_string] * - * @access public - * @param string the URI string + * @uses CI_Config::_uri_string() + * + * @param string|string[] $uri URI string or an array of segments + * @param string $protocol * @return string */ - function site_url($uri = '') + public function site_url($uri = '', $protocol = NULL) { - if ($uri == '') + $base_url = $this->slash_item('base_url'); + + if (isset($protocol)) { - return $this->slash_item('base_url').$this->item('index_page'); + // For protocol-relative links + if ($protocol === '') + { + $base_url = substr($base_url, strpos($base_url, '//')); + } + else + { + $base_url = $protocol.substr($base_url, strpos($base_url, '://')); + } } - if ($this->item('enable_query_strings') == FALSE) + if (empty($uri)) { - $suffix = ($this->item('url_suffix') == FALSE) ? '' : $this->item('url_suffix'); - return $this->slash_item('base_url').$this->slash_item('index_page').$this->_uri_string($uri).$suffix; + return $base_url.$this->item('index_page'); } - else + + $uri = $this->_uri_string($uri); + + if ($this->item('enable_query_strings') === FALSE) { - return $this->slash_item('base_url').$this->item('index_page').'?'.$this->_uri_string($uri); + $suffix = isset($this->config['url_suffix']) ? $this->config['url_suffix'] : ''; + + if ($suffix !== '') + { + if (($offset = strpos($uri, '?')) !== FALSE) + { + $uri = substr($uri, 0, $offset).$suffix.substr($uri, $offset); + } + else + { + $uri .= $suffix; + } + } + + return $base_url.$this->slash_item('index_page').$uri; } + elseif (strpos($uri, '?') === FALSE) + { + $uri = '?'.$uri; + } + + return $base_url.$this->item('index_page').$uri; } // ------------------------------------------------------------- /** * Base URL + * * Returns base_url [. uri_string] * - * @access public - * @param string $uri - * @return string + * @uses CI_Config::_uri_string() + * + * @param string|string[] $uri URI string or an array of segments + * @param string $protocol + * @return string */ - function base_url($uri = '') + public function base_url($uri = '', $protocol = NULL) { - return $this->slash_item('base_url').ltrim($this->_uri_string($uri), '/'); + $base_url = $this->slash_item('base_url'); + + if (isset($protocol)) + { + // For protocol-relative links + if ($protocol === '') + { + $base_url = substr($base_url, strpos($base_url, '//')); + } + else + { + $base_url = $protocol.substr($base_url, strpos($base_url, '://')); + } + } + + return $base_url.$this->_uri_string($uri); } // ------------------------------------------------------------- /** - * Build URI string for use in Config::site_url() and Config::base_url() + * Build URI string * - * @access protected - * @param $uri - * @return string + * @used-by CI_Config::site_url() + * @used-by CI_Config::base_url() + * + * @param string|string[] $uri URI string or an array of segments + * @return string */ protected function _uri_string($uri) { - if ($this->item('enable_query_strings') == FALSE) + if ($this->item('enable_query_strings') === FALSE) { - if (is_array($uri)) - { - $uri = implode('/', $uri); - } - $uri = ltrim($uri, '/'); + is_array($uri) && $uri = implode('/', $uri); + return ltrim($uri, '/'); } - else + elseif (is_array($uri)) { - if (is_array($uri)) - { - $i = 0; - $str = ''; - foreach ($uri as $key => $val) - { - $prefix = ($i == 0) ? '' : '&'; - $str .= $prefix.$key.'='.$val; - $i++; - } - $uri = $str; - } + return http_build_query($uri); } - return $uri; + + return $uri; } // -------------------------------------------------------------------- @@ -324,12 +353,12 @@ class CI_Config { /** * System URL * - * @access public + * @deprecated 3.0.0 Encourages insecure practices * @return string */ - function system_url() + public function system_url() { - $x = explode("/", preg_replace("|/*(.+?)/*$|", "\\1", BASEPATH)); + $x = explode('/', preg_replace('|/*(.+?)/*$|', '\\1', BASEPATH)); return $this->slash_item('base_url').end($x).'/'; } @@ -338,42 +367,13 @@ class CI_Config { /** * Set a config file item * - * @access public - * @param string the config item key - * @param string the config item value + * @param string $item Config item key + * @param string $value Config item value * @return void */ - function set_item($item, $value) + public function set_item($item, $value) { $this->config[$item] = $value; } - // -------------------------------------------------------------------- - - /** - * Assign to Config - * - * This function is called by the front controller (CodeIgniter.php) - * after the Config class is instantiated. It permits config items - * to be assigned or overriden by variables contained in the index.php file - * - * @access private - * @param array - * @return void - */ - function _assign_to_config($items = array()) - { - if (is_array($items)) - { - foreach ($items as $key => $val) - { - $this->set_item($key, $val); - } - } - } } - -// END CI_Config class - -/* End of file Config.php */ -/* Location: ./system/core/Config.php */ diff --git a/system/core/Controller.php b/system/core/Controller.php index 6ccaf9755..59a916734 100644 --- a/system/core/Controller.php +++ b/system/core/Controller.php @@ -1,22 +1,44 @@ -<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); +<?php /** * CodeIgniter * - * An open source application development framework for PHP 5.1.6 or newer + * An open source application development framework for PHP * - * @package CodeIgniter - * @author ExpressionEngine Dev Team - * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. - * @license http://codeigniter.com/user_guide/license.html - * @link http://codeigniter.com - * @since Version 1.0 + * This content is released under the MIT License (MIT) + * + * Copyright (c) 2014 - 2017, British Columbia Institute of Technology + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @package CodeIgniter + * @author EllisLab Dev Team + * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/) + * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) + * @license http://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com + * @since Version 1.0.0 * @filesource */ - -// ------------------------------------------------------------------------ +defined('BASEPATH') OR exit('No direct script access allowed'); /** - * CodeIgniter Application Controller Class + * Application Controller Class * * This class object is the super class that every library in * CodeIgniter will be assigned to. @@ -24,15 +46,22 @@ * @package CodeIgniter * @subpackage Libraries * @category Libraries - * @author ExpressionEngine Dev Team - * @link http://codeigniter.com/user_guide/general/controllers.html + * @author EllisLab Dev Team + * @link https://codeigniter.com/user_guide/general/controllers.html */ class CI_Controller { + /** + * Reference to the CI singleton + * + * @var object + */ private static $instance; /** - * Constructor + * Class constructor + * + * @return void */ public function __construct() { @@ -47,18 +76,21 @@ class CI_Controller { } $this->load =& load_class('Loader', 'core'); - $this->load->initialize(); - - log_message('debug', "Controller Class Initialized"); + log_message('info', 'Controller Class Initialized'); } + // -------------------------------------------------------------------- + + /** + * Get the CI singleton + * + * @static + * @return object + */ public static function &get_instance() { return self::$instance; } -} -// END Controller class -/* End of file Controller.php */ -/* Location: ./system/core/Controller.php */
\ No newline at end of file +} diff --git a/system/core/Exceptions.php b/system/core/Exceptions.php index 451209689..526909602 100644 --- a/system/core/Exceptions.php +++ b/system/core/Exceptions.php @@ -1,19 +1,41 @@ -<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); +<?php /** * CodeIgniter * - * An open source application development framework for PHP 5.1.6 or newer + * An open source application development framework for PHP * - * @package CodeIgniter - * @author ExpressionEngine Dev Team - * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. - * @license http://codeigniter.com/user_guide/license.html - * @link http://codeigniter.com - * @since Version 1.0 + * This content is released under the MIT License (MIT) + * + * Copyright (c) 2014 - 2017, British Columbia Institute of Technology + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @package CodeIgniter + * @author EllisLab Dev Team + * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/) + * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) + * @license http://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com + * @since Version 1.0.0 * @filesource */ - -// ------------------------------------------------------------------------ +defined('BASEPATH') OR exit('No direct script access allowed'); /** * Exceptions Class @@ -21,53 +43,47 @@ * @package CodeIgniter * @subpackage Libraries * @category Exceptions - * @author ExpressionEngine Dev Team - * @link http://codeigniter.com/user_guide/libraries/exceptions.html + * @author EllisLab Dev Team + * @link https://codeigniter.com/user_guide/libraries/exceptions.html */ class CI_Exceptions { - var $action; - var $severity; - var $message; - var $filename; - var $line; /** * Nesting level of the output buffering mechanism * - * @var int - * @access public + * @var int */ - var $ob_level; + public $ob_level; /** - * List if available error levels + * List of available error levels * - * @var array - * @access public + * @var array */ - var $levels = array( - E_ERROR => 'Error', - E_WARNING => 'Warning', - E_PARSE => 'Parsing Error', - E_NOTICE => 'Notice', - E_CORE_ERROR => 'Core Error', - E_CORE_WARNING => 'Core Warning', - E_COMPILE_ERROR => 'Compile Error', - E_COMPILE_WARNING => 'Compile Warning', - E_USER_ERROR => 'User Error', - E_USER_WARNING => 'User Warning', - E_USER_NOTICE => 'User Notice', - E_STRICT => 'Runtime Notice' - ); - + public $levels = array( + E_ERROR => 'Error', + E_WARNING => 'Warning', + E_PARSE => 'Parsing Error', + E_NOTICE => 'Notice', + E_CORE_ERROR => 'Core Error', + E_CORE_WARNING => 'Core Warning', + E_COMPILE_ERROR => 'Compile Error', + E_COMPILE_WARNING => 'Compile Warning', + E_USER_ERROR => 'User Error', + E_USER_WARNING => 'User Warning', + E_USER_NOTICE => 'User Notice', + E_STRICT => 'Runtime Notice' + ); /** - * Constructor + * Class constructor + * + * @return void */ public function __construct() { $this->ob_level = ob_get_level(); - // Note: Do not log messages from this constructor. + // Note: Do not log messages from this constructor. } // -------------------------------------------------------------------- @@ -75,45 +91,52 @@ class CI_Exceptions { /** * Exception Logger * - * This function logs PHP generated error messages + * Logs PHP generated error messages * - * @access private - * @param string the error severity - * @param string the error string - * @param string the error filepath - * @param string the error line number - * @return string + * @param int $severity Log level + * @param string $message Error message + * @param string $filepath File path + * @param int $line Line number + * @return void */ - function log_exception($severity, $message, $filepath, $line) + public function log_exception($severity, $message, $filepath, $line) { - $severity = ( ! isset($this->levels[$severity])) ? $severity : $this->levels[$severity]; - - log_message('error', 'Severity: '.$severity.' --> '.$message. ' '.$filepath.' '.$line, TRUE); + $severity = isset($this->levels[$severity]) ? $this->levels[$severity] : $severity; + log_message('error', 'Severity: '.$severity.' --> '.$message.' '.$filepath.' '.$line); } // -------------------------------------------------------------------- /** - * 404 Page Not Found Handler + * 404 Error Handler + * + * @uses CI_Exceptions::show_error() * - * @access private - * @param string the page - * @param bool log error yes/no - * @return string + * @param string $page Page URI + * @param bool $log_error Whether to log the error + * @return void */ - function show_404($page = '', $log_error = TRUE) + public function show_404($page = '', $log_error = TRUE) { - $heading = "404 Page Not Found"; - $message = "The page you requested was not found."; + if (is_cli()) + { + $heading = 'Not Found'; + $message = 'The controller/method pair you requested was not found.'; + } + else + { + $heading = '404 Page Not Found'; + $message = 'The page you requested was not found.'; + } // By default we log this, but allow a dev to skip it if ($log_error) { - log_message('error', '404 Page Not Found --> '.$page); + log_message('error', $heading.': '.$page); } echo $this->show_error($heading, $message, 'error_404', 404); - exit; + exit(4); // EXIT_UNKNOWN_FILE } // -------------------------------------------------------------------- @@ -121,29 +144,42 @@ class CI_Exceptions { /** * General Error Page * - * This function takes an error message as input - * (either as a string or an array) and displays - * it using the specified template. + * Takes an error message as input (either as a string or an array) + * and displays it using the specified template. + * + * @param string $heading Page heading + * @param string|string[] $message Error message + * @param string $template Template name + * @param int $status_code (default: 500) * - * @access private - * @param string the heading - * @param string the message - * @param string the template name - * @param int the status code - * @return string + * @return string Error page output */ - function show_error($heading, $message, $template = 'error_general', $status_code = 500) + public function show_error($heading, $message, $template = 'error_general', $status_code = 500) { - set_status_header($status_code); + $templates_path = config_item('error_views_path'); + if (empty($templates_path)) + { + $templates_path = VIEWPATH.'errors'.DIRECTORY_SEPARATOR; + } - $message = '<p>'.implode('</p><p>', ( ! is_array($message)) ? array($message) : $message).'</p>'; + if (is_cli()) + { + $message = "\t".(is_array($message) ? implode("\n\t", $message) : $message); + $template = 'cli'.DIRECTORY_SEPARATOR.$template; + } + else + { + set_status_header($status_code); + $message = '<p>'.(is_array($message) ? implode('</p><p>', $message) : $message).'</p>'; + $template = 'html'.DIRECTORY_SEPARATOR.$template; + } if (ob_get_level() > $this->ob_level + 1) { ob_end_flush(); } ob_start(); - include(APPPATH.'errors/'.$template.'.php'); + include($templates_path.$template.'.php'); $buffer = ob_get_contents(); ob_end_clean(); return $buffer; @@ -151,27 +187,77 @@ class CI_Exceptions { // -------------------------------------------------------------------- + public function show_exception($exception) + { + $templates_path = config_item('error_views_path'); + if (empty($templates_path)) + { + $templates_path = VIEWPATH.'errors'.DIRECTORY_SEPARATOR; + } + + $message = $exception->getMessage(); + if (empty($message)) + { + $message = '(null)'; + } + + if (is_cli()) + { + $templates_path .= 'cli'.DIRECTORY_SEPARATOR; + } + else + { + $templates_path .= 'html'.DIRECTORY_SEPARATOR; + } + + if (ob_get_level() > $this->ob_level + 1) + { + ob_end_flush(); + } + + ob_start(); + include($templates_path.'error_exception.php'); + $buffer = ob_get_contents(); + ob_end_clean(); + echo $buffer; + } + + // -------------------------------------------------------------------- + /** * Native PHP error handler * - * @access private - * @param string the error severity - * @param string the error string - * @param string the error filepath - * @param string the error line number - * @return string + * @param int $severity Error level + * @param string $message Error message + * @param string $filepath File path + * @param int $line Line number + * @return void */ - function show_php_error($severity, $message, $filepath, $line) + public function show_php_error($severity, $message, $filepath, $line) { - $severity = ( ! isset($this->levels[$severity])) ? $severity : $this->levels[$severity]; + $templates_path = config_item('error_views_path'); + if (empty($templates_path)) + { + $templates_path = VIEWPATH.'errors'.DIRECTORY_SEPARATOR; + } + + $severity = isset($this->levels[$severity]) ? $this->levels[$severity] : $severity; - $filepath = str_replace("\\", "/", $filepath); + // For safety reasons we don't show the full file path in non-CLI requests + if ( ! is_cli()) + { + $filepath = str_replace('\\', '/', $filepath); + if (FALSE !== strpos($filepath, '/')) + { + $x = explode('/', $filepath); + $filepath = $x[count($x)-2].'/'.end($x); + } - // For safety reasons we do not show the full file path - if (FALSE !== strpos($filepath, '/')) + $template = 'html'.DIRECTORY_SEPARATOR.'error_php'; + } + else { - $x = explode('/', $filepath); - $filepath = $x[count($x)-2].'/'.end($x); + $template = 'cli'.DIRECTORY_SEPARATOR.'error_php'; } if (ob_get_level() > $this->ob_level + 1) @@ -179,15 +265,10 @@ class CI_Exceptions { ob_end_flush(); } ob_start(); - include(APPPATH.'errors/error_php.php'); + include($templates_path.$template.'.php'); $buffer = ob_get_contents(); ob_end_clean(); echo $buffer; } - } -// END Exceptions Class - -/* End of file Exceptions.php */ -/* Location: ./system/core/Exceptions.php */
\ No newline at end of file diff --git a/system/core/Hooks.php b/system/core/Hooks.php index ee5c23076..f2d6f21ca 100644 --- a/system/core/Hooks.php +++ b/system/core/Hooks.php @@ -1,95 +1,114 @@ -<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); +<?php /** * CodeIgniter * - * An open source application development framework for PHP 5.1.6 or newer + * An open source application development framework for PHP * - * @package CodeIgniter - * @author ExpressionEngine Dev Team - * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. - * @license http://codeigniter.com/user_guide/license.html - * @link http://codeigniter.com - * @since Version 1.0 + * This content is released under the MIT License (MIT) + * + * Copyright (c) 2014 - 2017, British Columbia Institute of Technology + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @package CodeIgniter + * @author EllisLab Dev Team + * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/) + * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) + * @license http://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com + * @since Version 1.0.0 * @filesource */ - -// ------------------------------------------------------------------------ +defined('BASEPATH') OR exit('No direct script access allowed'); /** - * CodeIgniter Hooks Class + * Hooks Class * * Provides a mechanism to extend the base system without hacking. * * @package CodeIgniter * @subpackage Libraries * @category Libraries - * @author ExpressionEngine Dev Team - * @link http://codeigniter.com/user_guide/libraries/encryption.html + * @author EllisLab Dev Team + * @link https://codeigniter.com/user_guide/general/hooks.html */ class CI_Hooks { /** - * Determines wether hooks are enabled + * Determines whether hooks are enabled * - * @var bool + * @var bool */ - var $enabled = FALSE; + public $enabled = FALSE; + /** * List of all hooks set in config/hooks.php * - * @var array + * @var array */ - var $hooks = array(); + public $hooks = array(); + /** - * Determines wether hook is in progress, used to prevent infinte loops + * Array with class objects to use hooks methods * - * @var bool + * @var array */ - var $in_progress = FALSE; + protected $_objects = array(); /** - * Constructor + * In progress flag * + * Determines whether hook is in progress, used to prevent infinte loops + * + * @var bool */ - function __construct() - { - $this->_initialize(); - log_message('debug', "Hooks Class Initialized"); - } - - // -------------------------------------------------------------------- + protected $_in_progress = FALSE; /** - * Initialize the Hooks Preferences + * Class constructor * - * @access private * @return void */ - function _initialize() + public function __construct() { $CFG =& load_class('Config', 'core'); + log_message('info', 'Hooks Class Initialized'); // If hooks are not enabled in the config file // there is nothing else to do - - if ($CFG->item('enable_hooks') == FALSE) + if ($CFG->item('enable_hooks') === FALSE) { return; } // Grab the "hooks" definition file. - // If there are no hooks, we're done. - - if (defined('ENVIRONMENT') AND is_file(APPPATH.'config/'.ENVIRONMENT.'/hooks.php')) - { - include(APPPATH.'config/'.ENVIRONMENT.'/hooks.php'); - } - elseif (is_file(APPPATH.'config/hooks.php')) + if (file_exists(APPPATH.'config/hooks.php')) { include(APPPATH.'config/hooks.php'); } + if (file_exists(APPPATH.'config/'.ENVIRONMENT.'/hooks.php')) + { + include(APPPATH.'config/'.ENVIRONMENT.'/hooks.php'); + } + // If there are no hooks, we're done. if ( ! isset($hook) OR ! is_array($hook)) { return; @@ -104,20 +123,21 @@ class CI_Hooks { /** * Call Hook * - * Calls a particular hook + * Calls a particular hook. Called by CodeIgniter.php. * - * @access private - * @param string the hook name - * @return mixed + * @uses CI_Hooks::_run_hook() + * + * @param string $which Hook name + * @return bool TRUE on success or FALSE on failure */ - function _call_hook($which = '') + public function call_hook($which = '') { if ( ! $this->enabled OR ! isset($this->hooks[$which])) { return FALSE; } - if (isset($this->hooks[$which][0]) AND is_array($this->hooks[$which][0])) + if (is_array($this->hooks[$which]) && ! isset($this->hooks[$which]['function'])) { foreach ($this->hooks[$which] as $val) { @@ -139,13 +159,21 @@ class CI_Hooks { * * Runs a particular hook * - * @access private - * @param array the hook details - * @return bool + * @param array $data Hook details + * @return bool TRUE on success or FALSE on failure */ - function _run_hook($data) + protected function _run_hook($data) { - if ( ! is_array($data)) + // Closures/lambda functions and array($object, 'method') callables + if (is_callable($data)) + { + is_array($data) + ? $data[0]->{$data[1]}() + : $data(); + + return TRUE; + } + elseif ( ! is_array($data)) { return FALSE; } @@ -156,8 +184,7 @@ class CI_Hooks { // If the script being called happens to have the same // hook call within it a loop can happen - - if ($this->in_progress == TRUE) + if ($this->_in_progress === TRUE) { return; } @@ -166,7 +193,7 @@ class CI_Hooks { // Set file path // ----------------------------------- - if ( ! isset($data['filepath']) OR ! isset($data['filename'])) + if ( ! isset($data['filepath'], $data['filename'])) { return FALSE; } @@ -178,71 +205,62 @@ class CI_Hooks { return FALSE; } - // ----------------------------------- - // Set class/function name - // ----------------------------------- - - $class = FALSE; - $function = FALSE; - $params = ''; - - if (isset($data['class']) AND $data['class'] != '') - { - $class = $data['class']; - } - - if (isset($data['function'])) - { - $function = $data['function']; - } - - if (isset($data['params'])) - { - $params = $data['params']; - } + // Determine and class and/or function names + $class = empty($data['class']) ? FALSE : $data['class']; + $function = empty($data['function']) ? FALSE : $data['function']; + $params = isset($data['params']) ? $data['params'] : ''; - if ($class === FALSE AND $function === FALSE) + if (empty($function)) { return FALSE; } - // ----------------------------------- - // Set the in_progress flag - // ----------------------------------- - - $this->in_progress = TRUE; + // Set the _in_progress flag + $this->_in_progress = TRUE; - // ----------------------------------- // Call the requested class and/or function - // ----------------------------------- - if ($class !== FALSE) { - if ( ! class_exists($class)) + // The object is stored? + if (isset($this->_objects[$class])) { - require($filepath); + if (method_exists($this->_objects[$class], $function)) + { + $this->_objects[$class]->$function($params); + } + else + { + return $this->_in_progress = FALSE; + } } + else + { + class_exists($class, FALSE) OR require_once($filepath); + + if ( ! class_exists($class, FALSE) OR ! method_exists($class, $function)) + { + return $this->_in_progress = FALSE; + } - $HOOK = new $class; - $HOOK->$function($params); + // Store the object and execute the method + $this->_objects[$class] = new $class(); + $this->_objects[$class]->$function($params); + } } else { + function_exists($function) OR require_once($filepath); + if ( ! function_exists($function)) { - require($filepath); + return $this->_in_progress = FALSE; } $function($params); } - $this->in_progress = FALSE; + $this->_in_progress = FALSE; return TRUE; } } - -// END CI_Hooks class - -/* End of file Hooks.php */ -/* Location: ./system/core/Hooks.php */
\ No newline at end of file diff --git a/system/core/Input.php b/system/core/Input.php index 7bc34231d..af4f87c1f 100644 --- a/system/core/Input.php +++ b/system/core/Input.php @@ -1,19 +1,41 @@ -<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); +<?php /** * CodeIgniter * - * An open source application development framework for PHP 5.1.6 or newer + * An open source application development framework for PHP * - * @package CodeIgniter - * @author ExpressionEngine Dev Team - * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. - * @license http://codeigniter.com/user_guide/license.html - * @link http://codeigniter.com - * @since Version 1.0 + * This content is released under the MIT License (MIT) + * + * Copyright (c) 2014 - 2017, British Columbia Institute of Technology + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @package CodeIgniter + * @author EllisLab Dev Team + * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/) + * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) + * @license http://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com + * @since Version 1.0.0 * @filesource */ - -// ------------------------------------------------------------------------ +defined('BASEPATH') OR exit('No direct script access allowed'); /** * Input Class @@ -23,84 +45,121 @@ * @package CodeIgniter * @subpackage Libraries * @category Input - * @author ExpressionEngine Dev Team - * @link http://codeigniter.com/user_guide/libraries/input.html + * @author EllisLab Dev Team + * @link https://codeigniter.com/user_guide/libraries/input.html */ class CI_Input { /** * IP address of the current user * - * @var string + * @var string */ - var $ip_address = FALSE; + protected $ip_address = FALSE; + /** - * user agent (web browser) being used by the current user + * Allow GET array flag * - * @var string - */ - var $user_agent = FALSE; - /** - * If FALSE, then $_GET will be set to an empty array + * If set to FALSE, then $_GET will be set to an empty array. * - * @var bool + * @var bool */ - var $_allow_get_array = TRUE; + protected $_allow_get_array = TRUE; + /** - * If TRUE, then newlines are standardized + * Standardize new lines flag * - * @var bool + * If set to TRUE, then newlines are standardized. + * + * @var bool */ - var $_standardize_newlines = TRUE; + protected $_standardize_newlines; + /** - * Determines whether the XSS filter is always active when GET, POST or COOKIE data is encountered - * Set automatically based on config setting + * Enable XSS flag + * + * Determines whether the XSS filter is always active when + * GET, POST or COOKIE data is encountered. + * Set automatically based on config setting. * - * @var bool + * @var bool */ - var $_enable_xss = FALSE; + protected $_enable_xss = FALSE; + /** + * Enable CSRF flag + * * Enables a CSRF cookie token to be set. - * Set automatically based on config setting + * Set automatically based on config setting. * - * @var bool + * @var bool */ - var $_enable_csrf = FALSE; + protected $_enable_csrf = FALSE; + /** * List of all HTTP request headers * * @var array */ - protected $headers = array(); + protected $headers = array(); /** - * Constructor + * Raw input stream data + * + * Holds a cache of php://input contents * - * Sets whether to globally enable the XSS processing - * and whether to allow the $_GET array + * @var string + */ + protected $_raw_input_stream; + + /** + * Parsed input stream data + * + * Parsed from php://input at runtime + * + * @see CI_Input::input_stream() + * @var array + */ + protected $_input_stream; + + protected $security; + protected $uni; + + // -------------------------------------------------------------------- + + /** + * Class constructor + * + * Determines whether to globally enable the XSS processing + * and whether to allow the $_GET array. * * @return void */ public function __construct() { - log_message('debug', "Input Class Initialized"); - - $this->_allow_get_array = (config_item('allow_get_array') === TRUE); + $this->_allow_get_array = (config_item('allow_get_array') === TRUE); $this->_enable_xss = (config_item('global_xss_filtering') === TRUE); $this->_enable_csrf = (config_item('csrf_protection') === TRUE); + $this->_standardize_newlines = (bool) config_item('standardize_newlines'); - global $SEC; - $this->security =& $SEC; + $this->security =& load_class('Security', 'core'); // Do we need the UTF-8 class? if (UTF8_ENABLED === TRUE) { - global $UNI; - $this->uni =& $UNI; + $this->uni =& load_class('Utf8', 'core'); } // Sanitize global arrays $this->_sanitize_globals(); + + // CSRF Protection check + if ($this->_enable_csrf === TRUE && ! is_cli()) + { + $this->security->csrf_verify(); + } + + log_message('info', 'Input Class Initialized'); } // -------------------------------------------------------------------- @@ -108,147 +167,204 @@ class CI_Input { /** * Fetch from array * - * This is a helper function to retrieve values from global arrays + * Internal method used to retrieve values from global arrays. * - * @access private - * @param array - * @param string - * @param bool - * @return string + * @param array &$array $_GET, $_POST, $_COOKIE, $_SERVER, etc. + * @param mixed $index Index for item to be fetched from $array + * @param bool $xss_clean Whether to apply XSS filtering + * @return mixed */ - function _fetch_from_array(&$array, $index = '', $xss_clean = FALSE) + protected function _fetch_from_array(&$array, $index = NULL, $xss_clean = NULL) { - if ( ! isset($array[$index])) + is_bool($xss_clean) OR $xss_clean = $this->_enable_xss; + + // If $index is NULL, it means that the whole $array is requested + isset($index) OR $index = array_keys($array); + + // allow fetching multiple keys at once + if (is_array($index)) + { + $output = array(); + foreach ($index as $key) + { + $output[$key] = $this->_fetch_from_array($array, $key, $xss_clean); + } + + return $output; + } + + if (isset($array[$index])) { - return FALSE; + $value = $array[$index]; } + elseif (($count = preg_match_all('/(?:^[^\[]+)|\[[^]]*\]/', $index, $matches)) > 1) // Does the index contain array notation + { + $value = $array; + for ($i = 0; $i < $count; $i++) + { + $key = trim($matches[0][$i], '[]'); + if ($key === '') // Empty notation will return the value as array + { + break; + } - if ($xss_clean === TRUE) + if (isset($value[$key])) + { + $value = $value[$key]; + } + else + { + return NULL; + } + } + } + else { - return $this->security->xss_clean($array[$index]); + return NULL; } - return $array[$index]; + return ($xss_clean === TRUE) + ? $this->security->xss_clean($value) + : $value; } // -------------------------------------------------------------------- /** - * Fetch an item from the GET array - * - * @access public - * @param string - * @param bool - * @return string - */ - function get($index = NULL, $xss_clean = FALSE) + * Fetch an item from the GET array + * + * @param mixed $index Index for item to be fetched from $_GET + * @param bool $xss_clean Whether to apply XSS filtering + * @return mixed + */ + public function get($index = NULL, $xss_clean = NULL) { - // Check if a field has been provided - if ($index === NULL AND ! empty($_GET)) - { - $get = array(); - - // loop through the full _GET array - foreach (array_keys($_GET) as $key) - { - $get[$key] = $this->_fetch_from_array($_GET, $key, $xss_clean); - } - return $get; - } - return $this->_fetch_from_array($_GET, $index, $xss_clean); } // -------------------------------------------------------------------- /** - * Fetch an item from the POST array - * - * @access public - * @param string - * @param bool - * @return string - */ - function post($index = NULL, $xss_clean = FALSE) + * Fetch an item from the POST array + * + * @param mixed $index Index for item to be fetched from $_POST + * @param bool $xss_clean Whether to apply XSS filtering + * @return mixed + */ + public function post($index = NULL, $xss_clean = NULL) { - // Check if a field has been provided - if ($index === NULL AND ! empty($_POST)) - { - $post = array(); - - // Loop through the full _POST array and return it - foreach (array_keys($_POST) as $key) - { - $post[$key] = $this->_fetch_from_array($_POST, $key, $xss_clean); - } - return $post; - } - return $this->_fetch_from_array($_POST, $index, $xss_clean); } + // -------------------------------------------------------------------- + + /** + * Fetch an item from POST data with fallback to GET + * + * @param string $index Index for item to be fetched from $_POST or $_GET + * @param bool $xss_clean Whether to apply XSS filtering + * @return mixed + */ + public function post_get($index, $xss_clean = NULL) + { + return isset($_POST[$index]) + ? $this->post($index, $xss_clean) + : $this->get($index, $xss_clean); + } // -------------------------------------------------------------------- /** - * Fetch an item from either the GET array or the POST - * - * @access public - * @param string The index key - * @param bool XSS cleaning - * @return string - */ - function get_post($index = '', $xss_clean = FALSE) + * Fetch an item from GET data with fallback to POST + * + * @param string $index Index for item to be fetched from $_GET or $_POST + * @param bool $xss_clean Whether to apply XSS filtering + * @return mixed + */ + public function get_post($index, $xss_clean = NULL) { - if ( ! isset($_POST[$index]) ) - { - return $this->get($index, $xss_clean); - } - else - { - return $this->post($index, $xss_clean); - } + return isset($_GET[$index]) + ? $this->get($index, $xss_clean) + : $this->post($index, $xss_clean); } // -------------------------------------------------------------------- /** - * Fetch an item from the COOKIE array - * - * @access public - * @param string - * @param bool - * @return string - */ - function cookie($index = '', $xss_clean = FALSE) + * Fetch an item from the COOKIE array + * + * @param mixed $index Index for item to be fetched from $_COOKIE + * @param bool $xss_clean Whether to apply XSS filtering + * @return mixed + */ + public function cookie($index = NULL, $xss_clean = NULL) { return $this->_fetch_from_array($_COOKIE, $index, $xss_clean); } + // -------------------------------------------------------------------- + + /** + * Fetch an item from the SERVER array + * + * @param mixed $index Index for item to be fetched from $_SERVER + * @param bool $xss_clean Whether to apply XSS filtering + * @return mixed + */ + public function server($index, $xss_clean = NULL) + { + return $this->_fetch_from_array($_SERVER, $index, $xss_clean); + } + // ------------------------------------------------------------------------ /** - * Set cookie - * - * Accepts six parameter, or you can submit an associative - * array in the first parameter containing all the values. - * - * @access public - * @param mixed - * @param string the value of the cookie - * @param string the number of seconds until expiration - * @param string the cookie domain. Usually: .yourdomain.com - * @param string the cookie path - * @param string the cookie prefix - * @param bool true makes the cookie secure - * @return void - */ - function set_cookie($name = '', $value = '', $expire = '', $domain = '', $path = '/', $prefix = '', $secure = FALSE) + * Fetch an item from the php://input stream + * + * Useful when you need to access PUT, DELETE or PATCH request data. + * + * @param string $index Index for item to be fetched + * @param bool $xss_clean Whether to apply XSS filtering + * @return mixed + */ + public function input_stream($index = NULL, $xss_clean = NULL) + { + // Prior to PHP 5.6, the input stream can only be read once, + // so we'll need to check if we have already done that first. + if ( ! is_array($this->_input_stream)) + { + // $this->raw_input_stream will trigger __get(). + parse_str($this->raw_input_stream, $this->_input_stream); + is_array($this->_input_stream) OR $this->_input_stream = array(); + } + + return $this->_fetch_from_array($this->_input_stream, $index, $xss_clean); + } + + // ------------------------------------------------------------------------ + + /** + * Set cookie + * + * Accepts an arbitrary number of parameters (up to 7) or an associative + * array in the first parameter containing all the values. + * + * @param string|mixed[] $name Cookie name or an array containing parameters + * @param string $value Cookie value + * @param int $expire Cookie expiration time in seconds + * @param string $domain Cookie domain (e.g.: '.yourdomain.com') + * @param string $path Cookie path (default: '/') + * @param string $prefix Cookie name prefix + * @param bool $secure Whether to only transfer cookies via SSL + * @param bool $httponly Whether to only makes the cookie accessible via HTTP (no javascript) + * @return void + */ + public function set_cookie($name, $value = '', $expire = '', $domain = '', $path = '/', $prefix = '', $secure = NULL, $httponly = NULL) { if (is_array($name)) { // always leave 'name' in last place, as the loop will break otherwise, due to $$item - foreach (array('value', 'expire', 'domain', 'path', 'prefix', 'secure', 'name') as $item) + foreach (array('value', 'expire', 'domain', 'path', 'prefix', 'secure', 'httponly', 'name') as $item) { if (isset($name[$item])) { @@ -257,22 +373,28 @@ class CI_Input { } } - if ($prefix == '' AND config_item('cookie_prefix') != '') + if ($prefix === '' && config_item('cookie_prefix') !== '') { $prefix = config_item('cookie_prefix'); } - if ($domain == '' AND config_item('cookie_domain') != '') + + if ($domain == '' && config_item('cookie_domain') != '') { $domain = config_item('cookie_domain'); } - if ($path == '/' AND config_item('cookie_path') != '/') + + if ($path === '/' && config_item('cookie_path') !== '/') { $path = config_item('cookie_path'); } - if ($secure == FALSE AND config_item('cookie_secure') != FALSE) - { - $secure = config_item('cookie_secure'); - } + + $secure = ($secure === NULL && config_item('cookie_secure') !== NULL) + ? (bool) config_item('cookie_secure') + : (bool) $secure; + + $httponly = ($httponly === NULL && config_item('cookie_httponly') !== NULL) + ? (bool) config_item('cookie_httponly') + : (bool) $httponly; if ( ! is_numeric($expire)) { @@ -283,31 +405,18 @@ class CI_Input { $expire = ($expire > 0) ? time() + $expire : 0; } - setcookie($prefix.$name, $value, $expire, $path, $domain, $secure); - } - - // -------------------------------------------------------------------- - - /** - * Fetch an item from the SERVER array - * - * @access public - * @param string - * @param bool - * @return string - */ - function server($index = '', $xss_clean = FALSE) - { - return $this->_fetch_from_array($_SERVER, $index, $xss_clean); + setcookie($prefix.$name, $value, $expire, $path, $domain, $secure, $httponly); } // -------------------------------------------------------------------- /** - * Fetch the IP Address - * - * @return string - */ + * Fetch the IP Address + * + * Determines and validates the visitor's IP address. + * + * @return string IP address + */ public function ip_address() { if ($this->ip_address !== FALSE) @@ -316,25 +425,27 @@ class CI_Input { } $proxy_ips = config_item('proxy_ips'); - if ( ! empty($proxy_ips)) + if ( ! empty($proxy_ips) && ! is_array($proxy_ips)) { $proxy_ips = explode(',', str_replace(' ', '', $proxy_ips)); + } + + $this->ip_address = $this->server('REMOTE_ADDR'); + + if ($proxy_ips) + { foreach (array('HTTP_X_FORWARDED_FOR', 'HTTP_CLIENT_IP', 'HTTP_X_CLIENT_IP', 'HTTP_X_CLUSTER_CLIENT_IP') as $header) { - if (($spoof = $this->server($header)) !== FALSE) + if (($spoof = $this->server($header)) !== NULL) { // Some proxies typically list the whole chain of IP // addresses through which the client has reached us. // e.g. client_ip, proxy_ip1, proxy_ip2, etc. - if (strpos($spoof, ',') !== FALSE) - { - $spoof = explode(',', $spoof, 2); - $spoof = $spoof[0]; - } + sscanf($spoof, '%[^,]', $spoof); if ( ! $this->valid_ip($spoof)) { - $spoof = FALSE; + $spoof = NULL; } else { @@ -343,275 +454,166 @@ class CI_Input { } } - $this->ip_address = ($spoof !== FALSE && in_array($_SERVER['REMOTE_ADDR'], $proxy_ips, TRUE)) - ? $spoof : $_SERVER['REMOTE_ADDR']; - } - else - { - $this->ip_address = $_SERVER['REMOTE_ADDR']; - } - - if ( ! $this->valid_ip($this->ip_address)) - { - $this->ip_address = '0.0.0.0'; - } + if ($spoof) + { + for ($i = 0, $c = count($proxy_ips); $i < $c; $i++) + { + // Check if we have an IP address or a subnet + if (strpos($proxy_ips[$i], '/') === FALSE) + { + // An IP address (and not a subnet) is specified. + // We can compare right away. + if ($proxy_ips[$i] === $this->ip_address) + { + $this->ip_address = $spoof; + break; + } + + continue; + } - return $this->ip_address; - } + // We have a subnet ... now the heavy lifting begins + isset($separator) OR $separator = $this->valid_ip($this->ip_address, 'ipv6') ? ':' : '.'; - // -------------------------------------------------------------------- + // If the proxy entry doesn't match the IP protocol - skip it + if (strpos($proxy_ips[$i], $separator) === FALSE) + { + continue; + } - /** - * Validate IP Address - * - * @access public - * @param string - * @param string ipv4 or ipv6 - * @return bool - */ - public function valid_ip($ip, $which = '') - { - $which = strtolower($which); + // Convert the REMOTE_ADDR IP address to binary, if needed + if ( ! isset($ip, $sprintf)) + { + if ($separator === ':') + { + // Make sure we're have the "full" IPv6 format + $ip = explode(':', + str_replace('::', + str_repeat(':', 9 - substr_count($this->ip_address, ':')), + $this->ip_address + ) + ); + + for ($j = 0; $j < 8; $j++) + { + $ip[$j] = intval($ip[$j], 16); + } + + $sprintf = '%016b%016b%016b%016b%016b%016b%016b%016b'; + } + else + { + $ip = explode('.', $this->ip_address); + $sprintf = '%08b%08b%08b%08b'; + } + + $ip = vsprintf($sprintf, $ip); + } - // First check if filter_var is available - if (is_callable('filter_var')) - { - switch ($which) { - case 'ipv4': - $flag = FILTER_FLAG_IPV4; - break; - case 'ipv6': - $flag = FILTER_FLAG_IPV6; - break; - default: - $flag = ''; - break; - } + // Split the netmask length off the network address + sscanf($proxy_ips[$i], '%[^/]/%d', $netaddr, $masklen); - return (bool) filter_var($ip, FILTER_VALIDATE_IP, $flag); - } + // Again, an IPv6 address is most likely in a compressed form + if ($separator === ':') + { + $netaddr = explode(':', str_replace('::', str_repeat(':', 9 - substr_count($netaddr, ':')), $netaddr)); + for ($j = 0; $j < 8; $j++) + { + $netaddr[$j] = intval($netaddr[$j], 16); + } + } + else + { + $netaddr = explode('.', $netaddr); + } - if ($which !== 'ipv6' && $which !== 'ipv4') - { - if (strpos($ip, ':') !== FALSE) - { - $which = 'ipv6'; - } - elseif (strpos($ip, '.') !== FALSE) - { - $which = 'ipv4'; - } - else - { - return FALSE; + // Convert to binary and finally compare + if (strncmp($ip, vsprintf($sprintf, $netaddr), $masklen) === 0) + { + $this->ip_address = $spoof; + break; + } + } } } - $func = '_valid_'.$which; - return $this->$func($ip); - } - - // -------------------------------------------------------------------- - - /** - * Validate IPv4 Address - * - * Updated version suggested by Geert De Deckere - * - * @access protected - * @param string - * @return bool - */ - protected function _valid_ipv4($ip) - { - $ip_segments = explode('.', $ip); - - // Always 4 segments needed - if (count($ip_segments) !== 4) - { - return FALSE; - } - // IP can not start with 0 - if ($ip_segments[0][0] == '0') - { - return FALSE; - } - - // Check each segment - foreach ($ip_segments as $segment) + if ( ! $this->valid_ip($this->ip_address)) { - // IP segments must be digits and can not be - // longer than 3 digits or greater then 255 - if ($segment == '' OR preg_match("/[^0-9]/", $segment) OR $segment > 255 OR strlen($segment) > 3) - { - return FALSE; - } + return $this->ip_address = '0.0.0.0'; } - return TRUE; + return $this->ip_address; } // -------------------------------------------------------------------- /** - * Validate IPv6 Address - * - * @access protected - * @param string - * @return bool - */ - protected function _valid_ipv6($str) + * Validate IP Address + * + * @param string $ip IP address + * @param string $which IP protocol: 'ipv4' or 'ipv6' + * @return bool + */ + public function valid_ip($ip, $which = '') { - // 8 groups, separated by : - // 0-ffff per group - // one set of consecutive 0 groups can be collapsed to :: - - $groups = 8; - $collapsed = FALSE; - - $chunks = array_filter( - preg_split('/(:{1,2})/', $str, NULL, PREG_SPLIT_DELIM_CAPTURE) - ); - - // Rule out easy nonsense - if (current($chunks) == ':' OR end($chunks) == ':') - { - return FALSE; - } - - // PHP supports IPv4-mapped IPv6 addresses, so we'll expect those as well - if (strpos(end($chunks), '.') !== FALSE) + switch (strtolower($which)) { - $ipv4 = array_pop($chunks); - - if ( ! $this->_valid_ipv4($ipv4)) - { - return FALSE; - } - - $groups--; + case 'ipv4': + $which = FILTER_FLAG_IPV4; + break; + case 'ipv6': + $which = FILTER_FLAG_IPV6; + break; + default: + $which = NULL; + break; } - while ($seg = array_pop($chunks)) - { - if ($seg[0] == ':') - { - if (--$groups == 0) - { - return FALSE; // too many groups - } - - if (strlen($seg) > 2) - { - return FALSE; // long separator - } - - if ($seg == '::') - { - if ($collapsed) - { - return FALSE; // multiple collapsed - } - - $collapsed = TRUE; - } - } - elseif (preg_match("/[^0-9a-f]/i", $seg) OR strlen($seg) > 4) - { - return FALSE; // invalid segment - } - } - - return $collapsed OR $groups == 1; + return (bool) filter_var($ip, FILTER_VALIDATE_IP, $which); } // -------------------------------------------------------------------- /** - * User Agent - * - * @access public - * @return string - */ - function user_agent() + * Fetch User Agent string + * + * @return string|null User Agent string or NULL if it doesn't exist + */ + public function user_agent($xss_clean = NULL) { - if ($this->user_agent !== FALSE) - { - return $this->user_agent; - } - - $this->user_agent = ( ! isset($_SERVER['HTTP_USER_AGENT'])) ? FALSE : $_SERVER['HTTP_USER_AGENT']; - - return $this->user_agent; + return $this->_fetch_from_array($_SERVER, 'HTTP_USER_AGENT', $xss_clean); } // -------------------------------------------------------------------- /** - * Sanitize Globals - * - * This function does the following: - * - * Unsets $_GET data (if query strings are not enabled) - * - * Unsets all globals if register_globals is enabled - * - * Standardizes newline characters to \n - * - * @access private - * @return void - */ - function _sanitize_globals() + * Sanitize Globals + * + * Internal method serving for the following purposes: + * + * - Unsets $_GET data, if query strings are not enabled + * - Cleans POST, COOKIE and SERVER data + * - Standardizes newline characters to PHP_EOL + * + * @return void + */ + protected function _sanitize_globals() { - // It would be "wrong" to unset any of these GLOBALS. - $protected = array('_SERVER', '_GET', '_POST', '_FILES', '_REQUEST', - '_SESSION', '_ENV', 'GLOBALS', 'HTTP_RAW_POST_DATA', - 'system_folder', 'application_folder', 'BM', 'EXT', - 'CFG', 'URI', 'RTR', 'OUT', 'IN'); - - // Unset globals for securiy. - // This is effectively the same as register_globals = off - foreach (array($_GET, $_POST, $_COOKIE) as $global) - { - if ( ! is_array($global)) - { - if ( ! in_array($global, $protected)) - { - global $$global; - $$global = NULL; - } - } - else - { - foreach ($global as $key => $val) - { - if ( ! in_array($key, $protected)) - { - global $$key; - $$key = NULL; - } - } - } - } - // Is $_GET data allowed? If not we'll set the $_GET to an empty array - if ($this->_allow_get_array == FALSE) + if ($this->_allow_get_array === FALSE) { $_GET = array(); } - else + elseif (is_array($_GET)) { - if (is_array($_GET) AND count($_GET) > 0) + foreach ($_GET as $key => $val) { - foreach ($_GET as $key => $val) - { - $_GET[$this->_clean_input_keys($key)] = $this->_clean_input_data($val); - } + $_GET[$this->_clean_input_keys($key)] = $this->_clean_input_data($val); } } // Clean $_POST Data - if (is_array($_POST) AND count($_POST) > 0) + if (is_array($_POST)) { foreach ($_POST as $key => $val) { @@ -620,56 +622,57 @@ class CI_Input { } // Clean $_COOKIE Data - if (is_array($_COOKIE) AND count($_COOKIE) > 0) + if (is_array($_COOKIE)) { // Also get rid of specially treated cookies that might be set by a server // or silly application, that are of no use to a CI application anyway // but that when present will trip our 'Disallowed Key Characters' alarm // http://www.ietf.org/rfc/rfc2109.txt // note that the key names below are single quoted strings, and are not PHP variables - unset($_COOKIE['$Version']); - unset($_COOKIE['$Path']); - unset($_COOKIE['$Domain']); + unset( + $_COOKIE['$Version'], + $_COOKIE['$Path'], + $_COOKIE['$Domain'] + ); foreach ($_COOKIE as $key => $val) { - $_COOKIE[$this->_clean_input_keys($key)] = $this->_clean_input_data($val); + if (($cookie_key = $this->_clean_input_keys($key)) !== FALSE) + { + $_COOKIE[$cookie_key] = $this->_clean_input_data($val); + } + else + { + unset($_COOKIE[$key]); + } } } // Sanitize PHP_SELF $_SERVER['PHP_SELF'] = strip_tags($_SERVER['PHP_SELF']); - - // CSRF Protection check on HTTP requests - if ($this->_enable_csrf == TRUE && ! $this->is_cli_request()) - { - $this->security->csrf_verify(); - } - - log_message('debug', "Global POST and COOKIE data sanitized"); + log_message('debug', 'Global POST, GET and COOKIE data sanitized'); } // -------------------------------------------------------------------- /** - * Clean Input Data - * - * This is a helper function. It escapes data and - * standardizes newline characters to \n - * - * @access private - * @param string - * @return string - */ - function _clean_input_data($str) + * Clean Input Data + * + * Internal method that aids in escaping data and + * standardizing newline characters to PHP_EOL. + * + * @param string|string[] $str Input string(s) + * @return string + */ + protected function _clean_input_data($str) { if (is_array($str)) { $new_array = array(); - foreach ($str as $key => $val) + foreach (array_keys($str) as $key) { - $new_array[$this->_clean_input_keys($key)] = $this->_clean_input_data($val); + $new_array[$this->_clean_input_keys($key)] = $this->_clean_input_data($str[$key]); } return $new_array; } @@ -677,7 +680,7 @@ class CI_Input { /* We strip slashes if magic quotes is on to keep things consistent NOTE: In PHP 5.4 get_magic_quotes_gpc() will always return 0 and - it will probably not exist in future versions at all. + it will probably not exist in future versions at all. */ if ( ! is_php('5.4') && get_magic_quotes_gpc()) { @@ -691,21 +694,12 @@ class CI_Input { } // Remove control characters - $str = remove_invisible_characters($str, false); - - // Should we filter the input data? - if ($this->_enable_xss === TRUE) - { - $str = $this->security->xss_clean($str); - } + $str = remove_invisible_characters($str, FALSE); // Standardize newlines if needed - if ($this->_standardize_newlines == TRUE) + if ($this->_standardize_newlines === TRUE) { - if (strpos($str, "\r") !== FALSE) - { - $str = str_replace(array("\r\n", "\r", "\r\n\n"), PHP_EOL, $str); - } + return preg_replace('/(?:\r\n|[\r\n])/', PHP_EOL, $str); } return $str; @@ -714,27 +708,38 @@ class CI_Input { // -------------------------------------------------------------------- /** - * Clean Keys - * - * This is a helper function. To prevent malicious users - * from trying to exploit keys we make sure that keys are - * only named with alpha-numeric text and a few other items. - * - * @access private - * @param string - * @return string - */ - function _clean_input_keys($str) + * Clean Keys + * + * Internal method that helps to prevent malicious users + * from trying to exploit keys we make sure that keys are + * only named with alpha-numeric text and a few other items. + * + * @param string $str Input string + * @param bool $fatal Whether to terminate script exection + * or to return FALSE if an invalid + * key is encountered + * @return string|bool + */ + protected function _clean_input_keys($str, $fatal = TRUE) { - if ( ! preg_match("/^[a-z0-9:_\/-]+$/i", $str)) + if ( ! preg_match('/^[a-z0-9:_\/|-]+$/i', $str)) { - exit('Disallowed Key Characters.'); + if ($fatal === TRUE) + { + return FALSE; + } + else + { + set_status_header(503); + echo 'Disallowed Key Characters.'; + exit(7); // EXIT_USER_INPUT + } } // Clean UTF-8 if supported if (UTF8_ENABLED === TRUE) { - $str = $this->uni->clean_string($str); + return $this->uni->clean_string($str); } return $str; @@ -745,43 +750,40 @@ class CI_Input { /** * Request Headers * - * In Apache, you can simply call apache_request_headers(), however for - * people running other webservers the function is undefined. - * - * @param bool XSS cleaning - * - * @return array + * @param bool $xss_clean Whether to apply XSS filtering + * @return array */ public function request_headers($xss_clean = FALSE) { - // Look at Apache go! + // If header is already defined, return it immediately + if ( ! empty($this->headers)) + { + return $this->_fetch_from_array($this->headers, NULL, $xss_clean); + } + + // In Apache, you can simply call apache_request_headers() if (function_exists('apache_request_headers')) { - $headers = apache_request_headers(); + $this->headers = apache_request_headers(); } else { - $headers['Content-Type'] = (isset($_SERVER['CONTENT_TYPE'])) ? $_SERVER['CONTENT_TYPE'] : @getenv('CONTENT_TYPE'); + isset($_SERVER['CONTENT_TYPE']) && $this->headers['Content-Type'] = $_SERVER['CONTENT_TYPE']; foreach ($_SERVER as $key => $val) { - if (strncmp($key, 'HTTP_', 5) === 0) + if (sscanf($key, 'HTTP_%s', $header) === 1) { - $headers[substr($key, 5)] = $this->_fetch_from_array($_SERVER, $key, $xss_clean); + // take SOME_HEADER and turn it into Some-Header + $header = str_replace('_', ' ', strtolower($header)); + $header = str_replace(' ', '-', ucwords($header)); + + $this->headers[$header] = $_SERVER[$key]; } } } - // take SOME_HEADER and turn it into Some-Header - foreach ($headers as $key => $val) - { - $key = str_replace('_', ' ', strtolower($key)); - $key = str_replace(' ', '-', ucwords($key)); - - $this->headers[$key] = $val; - } - - return $this->headers; + return $this->_fetch_from_array($this->headers, NULL, $xss_clean); } // -------------------------------------------------------------------- @@ -791,59 +793,103 @@ class CI_Input { * * Returns the value of a single member of the headers class member * - * @param string array key for $this->headers - * @param boolean XSS Clean or not - * @return mixed FALSE on failure, string on success + * @param string $index Header name + * @param bool $xss_clean Whether to apply XSS filtering + * @return string|null The requested header on success or NULL on failure */ public function get_request_header($index, $xss_clean = FALSE) { - if (empty($this->headers)) - { - $this->request_headers(); - } + static $headers; - if ( ! isset($this->headers[$index])) + if ( ! isset($headers)) { - return FALSE; + empty($this->headers) && $this->request_headers(); + foreach ($this->headers as $key => $value) + { + $headers[strtolower($key)] = $value; + } } - if ($xss_clean === TRUE) + $index = strtolower($index); + + if ( ! isset($headers[$index])) { - return $this->security->xss_clean($this->headers[$index]); + return NULL; } - return $this->headers[$index]; + return ($xss_clean === TRUE) + ? $this->security->xss_clean($headers[$index]) + : $headers[$index]; } // -------------------------------------------------------------------- /** - * Is ajax Request? + * Is AJAX request? * - * Test to see if a request contains the HTTP_X_REQUESTED_WITH header + * Test to see if a request contains the HTTP_X_REQUESTED_WITH header. * - * @return boolean + * @return bool */ public function is_ajax_request() { - return ($this->server('HTTP_X_REQUESTED_WITH') === 'XMLHttpRequest'); + return ( ! empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest'); } // -------------------------------------------------------------------- /** - * Is cli Request? + * Is CLI request? * - * Test to see if a request was made from the command line + * Test to see if a request was made from the command line. * - * @return bool + * @deprecated 3.0.0 Use is_cli() instead + * @return bool */ public function is_cli_request() { - return (php_sapi_name() === 'cli' OR defined('STDIN')); + return is_cli(); } -} + // -------------------------------------------------------------------- -/* End of file Input.php */ -/* Location: ./system/core/Input.php */
\ No newline at end of file + /** + * Get Request Method + * + * Return the request method + * + * @param bool $upper Whether to return in upper or lower case + * (default: FALSE) + * @return string + */ + public function method($upper = FALSE) + { + return ($upper) + ? strtoupper($this->server('REQUEST_METHOD')) + : strtolower($this->server('REQUEST_METHOD')); + } + + // ------------------------------------------------------------------------ + + /** + * Magic __get() + * + * Allows read access to protected properties + * + * @param string $name + * @return mixed + */ + public function __get($name) + { + if ($name === 'raw_input_stream') + { + isset($this->_raw_input_stream) OR $this->_raw_input_stream = file_get_contents('php://input'); + return $this->_raw_input_stream; + } + elseif ($name === 'ip_address') + { + return $this->ip_address; + } + } + +} diff --git a/system/core/Lang.php b/system/core/Lang.php index ef5d1080c..569b02368 100644 --- a/system/core/Lang.php +++ b/system/core/Lang.php @@ -1,19 +1,41 @@ -<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); +<?php /** * CodeIgniter * - * An open source application development framework for PHP 5.1.6 or newer + * An open source application development framework for PHP * - * @package CodeIgniter - * @author ExpressionEngine Dev Team - * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. - * @license http://codeigniter.com/user_guide/license.html - * @link http://codeigniter.com - * @since Version 1.0 + * This content is released under the MIT License (MIT) + * + * Copyright (c) 2014 - 2017, British Columbia Institute of Technology + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @package CodeIgniter + * @author EllisLab Dev Team + * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/) + * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) + * @license http://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com + * @since Version 1.0.0 * @filesource */ - -// ------------------------------------------------------------------------ +defined('BASEPATH') OR exit('No direct script access allowed'); /** * Language Class @@ -21,32 +43,33 @@ * @package CodeIgniter * @subpackage Libraries * @category Language - * @author ExpressionEngine Dev Team - * @link http://codeigniter.com/user_guide/libraries/language.html + * @author EllisLab Dev Team + * @link https://codeigniter.com/user_guide/libraries/language.html */ class CI_Lang { /** * List of translations * - * @var array + * @var array */ - var $language = array(); + public $language = array(); + /** * List of loaded language files * - * @var array + * @var array */ - var $is_loaded = array(); + public $is_loaded = array(); /** - * Constructor + * Class constructor * - * @access public + * @return void */ - function __construct() + public function __construct() { - log_message('debug', "Language Class Initialized"); + log_message('info', 'Language Class Initialized'); } // -------------------------------------------------------------------- @@ -54,98 +77,122 @@ class CI_Lang { /** * Load a language file * - * @access public - * @param mixed the name of the language file to be loaded. Can be an array - * @param string the language (english, etc.) - * @param bool return loaded array of translations - * @param bool add suffix to $langfile - * @param string alternative path to look for language file - * @return mixed + * @param mixed $langfile Language file name + * @param string $idiom Language name (english, etc.) + * @param bool $return Whether to return the loaded array of translations + * @param bool $add_suffix Whether to add suffix to $langfile + * @param string $alt_path Alternative path to look for the language file + * + * @return void|string[] Array containing translations, if $return is set to TRUE */ - function load($langfile = '', $idiom = '', $return = FALSE, $add_suffix = TRUE, $alt_path = '') + public function load($langfile, $idiom = '', $return = FALSE, $add_suffix = TRUE, $alt_path = '') { + if (is_array($langfile)) + { + foreach ($langfile as $value) + { + $this->load($value, $idiom, $return, $add_suffix, $alt_path); + } + + return; + } + $langfile = str_replace('.php', '', $langfile); - if ($add_suffix == TRUE) + if ($add_suffix === TRUE) { - $langfile = str_replace('_lang.', '', $langfile).'_lang'; + $langfile = preg_replace('/_lang$/', '', $langfile).'_lang'; } $langfile .= '.php'; - if (in_array($langfile, $this->is_loaded, TRUE)) + if (empty($idiom) OR ! preg_match('/^[a-z_-]+$/i', $idiom)) { - return; + $config =& get_config(); + $idiom = empty($config['language']) ? 'english' : $config['language']; } - $config =& get_config(); + if ($return === FALSE && isset($this->is_loaded[$langfile]) && $this->is_loaded[$langfile] === $idiom) + { + return; + } - if ($idiom == '') + // Load the base file, so any others found can override it + $basepath = BASEPATH.'language/'.$idiom.'/'.$langfile; + if (($found = file_exists($basepath)) === TRUE) { - $deft_lang = ( ! isset($config['language'])) ? 'english' : $config['language']; - $idiom = ($deft_lang == '') ? 'english' : $deft_lang; + include($basepath); } - // Determine where the language file is and load it - if ($alt_path != '' && file_exists($alt_path.'language/'.$idiom.'/'.$langfile)) + // Do we have an alternative path to look in? + if ($alt_path !== '') { - include($alt_path.'language/'.$idiom.'/'.$langfile); + $alt_path .= 'language/'.$idiom.'/'.$langfile; + if (file_exists($alt_path)) + { + include($alt_path); + $found = TRUE; + } } else { - $found = FALSE; - foreach (get_instance()->load->get_package_paths(TRUE) as $package_path) { - if (file_exists($package_path.'language/'.$idiom.'/'.$langfile)) + $package_path .= 'language/'.$idiom.'/'.$langfile; + if ($basepath !== $package_path && file_exists($package_path)) { - include($package_path.'language/'.$idiom.'/'.$langfile); + include($package_path); $found = TRUE; break; } } - - if ($found !== TRUE) - { - show_error('Unable to load the requested language file: language/'.$idiom.'/'.$langfile); - } } + if ($found !== TRUE) + { + show_error('Unable to load the requested language file: language/'.$idiom.'/'.$langfile); + } - if ( ! isset($lang)) + if ( ! isset($lang) OR ! is_array($lang)) { log_message('error', 'Language file contains no data: language/'.$idiom.'/'.$langfile); + + if ($return === TRUE) + { + return array(); + } return; } - if ($return == TRUE) + if ($return === TRUE) { return $lang; } - $this->is_loaded[] = $langfile; + $this->is_loaded[$langfile] = $idiom; $this->language = array_merge($this->language, $lang); - unset($lang); - log_message('debug', 'Language file loaded: language/'.$idiom.'/'.$langfile); + log_message('info', 'Language file loaded: language/'.$idiom.'/'.$langfile); return TRUE; } // -------------------------------------------------------------------- /** - * Fetch a single line of text from the language array + * Language line * - * @access public - * @param string $line the language line - * @return string + * Fetches a single line of text from the language array + * + * @param string $line Language line key + * @param bool $log_errors Whether to log an error message if the line is not found + * @return string Translation */ - function line($line = '') + public function line($line, $log_errors = TRUE) { - $value = ($line == '' OR ! isset($this->language[$line])) ? FALSE : $this->language[$line]; + $value = isset($this->language[$line]) ? $this->language[$line] : FALSE; // Because killer robots like unicorns! - if ($value === FALSE) + if ($value === FALSE && $log_errors === TRUE) { log_message('error', 'Could not find the language line "'.$line.'"'); } @@ -154,7 +201,3 @@ class CI_Lang { } } -// END Language Class - -/* End of file Lang.php */ -/* Location: ./system/core/Lang.php */ diff --git a/system/core/Loader.php b/system/core/Loader.php index b5b0634e6..5ed6adb48 100644 --- a/system/core/Loader.php +++ b/system/core/Loader.php @@ -1,30 +1,52 @@ -<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); +<?php /** * CodeIgniter * - * An open source application development framework for PHP 5.1.6 or newer + * An open source application development framework for PHP * - * @package CodeIgniter - * @author ExpressionEngine Dev Team - * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. - * @license http://codeigniter.com/user_guide/license.html - * @link http://codeigniter.com - * @since Version 1.0 + * This content is released under the MIT License (MIT) + * + * Copyright (c) 2014 - 2017, British Columbia Institute of Technology + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @package CodeIgniter + * @author EllisLab Dev Team + * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/) + * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) + * @license http://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com + * @since Version 1.0.0 * @filesource */ - -// ------------------------------------------------------------------------ +defined('BASEPATH') OR exit('No direct script access allowed'); /** * Loader Class * - * Loads views and files + * Loads framework components. * * @package CodeIgniter * @subpackage Libraries - * @author ExpressionEngine Dev Team * @category Loader - * @link http://codeigniter.com/user_guide/libraries/loader.html + * @author EllisLab Dev Team + * @link https://codeigniter.com/user_guide/libraries/loader.html */ class CI_Loader { @@ -32,126 +54,107 @@ class CI_Loader { /** * Nesting level of the output buffering mechanism * - * @var int - * @access protected + * @var int */ protected $_ci_ob_level; + /** * List of paths to load views from * - * @var array - * @access protected + * @var array */ - protected $_ci_view_paths = array(); + protected $_ci_view_paths = array(VIEWPATH => TRUE); + /** * List of paths to load libraries from * - * @var array - * @access protected + * @var array */ - protected $_ci_library_paths = array(); + protected $_ci_library_paths = array(APPPATH, BASEPATH); + /** * List of paths to load models from * - * @var array - * @access protected + * @var array */ - protected $_ci_model_paths = array(); + protected $_ci_model_paths = array(APPPATH); + /** * List of paths to load helpers from * - * @var array - * @access protected + * @var array */ - protected $_ci_helper_paths = array(); - /** - * List of loaded base classes - * Set by the controller class - * - * @var array - * @access protected - */ - protected $_base_classes = array(); // Set by the controller class + protected $_ci_helper_paths = array(APPPATH, BASEPATH); + /** * List of cached variables * - * @var array - * @access protected + * @var array */ - protected $_ci_cached_vars = array(); + protected $_ci_cached_vars = array(); + /** * List of loaded classes * - * @var array - * @access protected + * @var array */ - protected $_ci_classes = array(); - /** - * List of loaded files - * - * @var array - * @access protected - */ - protected $_ci_loaded_files = array(); + protected $_ci_classes = array(); + /** * List of loaded models * - * @var array - * @access protected + * @var array */ - protected $_ci_models = array(); + protected $_ci_models = array(); + /** * List of loaded helpers * - * @var array - * @access protected + * @var array */ - protected $_ci_helpers = array(); + protected $_ci_helpers = array(); + /** * List of class name mappings * - * @var array - * @access protected + * @var array */ - protected $_ci_varmap = array('unit_test' => 'unit', - 'user_agent' => 'agent'); + protected $_ci_varmap = array( + 'unit_test' => 'unit', + 'user_agent' => 'agent' + ); + + // -------------------------------------------------------------------- /** - * Constructor + * Class constructor + * + * Sets component load paths, gets the initial output buffering level. * - * Sets the path to the view files and gets the initial output buffering level + * @return void */ public function __construct() { - $this->_ci_ob_level = ob_get_level(); - $this->_ci_library_paths = array(APPPATH, BASEPATH); - $this->_ci_helper_paths = array(APPPATH, BASEPATH); - $this->_ci_model_paths = array(APPPATH); - $this->_ci_view_paths = array(APPPATH.'views/' => TRUE); + $this->_ci_ob_level = ob_get_level(); + $this->_ci_classes =& is_loaded(); - log_message('debug', "Loader Class Initialized"); + log_message('info', 'Loader Class Initialized'); } // -------------------------------------------------------------------- /** - * Initialize the Loader - * - * This method is called once in CI_Controller. + * Initializer * - * @param array - * @return object + * @todo Figure out a way to move this to the constructor + * without breaking *package_path*() methods. + * @uses CI_Loader::_ci_autoloader() + * @used-by CI_Controller::__construct() + * @return void */ public function initialize() { - $this->_ci_classes = array(); - $this->_ci_loaded_files = array(); - $this->_ci_models = array(); - $this->_base_classes =& is_loaded(); - $this->_ci_autoloader(); - - return $this; } // -------------------------------------------------------------------- @@ -159,61 +162,61 @@ class CI_Loader { /** * Is Loaded * - * A utility function to test if a class is in the self::$_ci_classes array. - * This function returns the object name if the class tested for is loaded, - * and returns FALSE if it isn't. + * A utility method to test if a class is in the self::$_ci_classes array. * - * It is mainly used in the form_helper -> _get_validation_object() + * @used-by Mainly used by Form Helper function _get_validation_object(). * - * @param string class being checked for - * @return mixed class object name on the CI SuperObject or FALSE + * @param string $class Class name to check for + * @return string|bool Class object name if loaded or FALSE */ public function is_loaded($class) { - if (isset($this->_ci_classes[$class])) - { - return $this->_ci_classes[$class]; - } - - return FALSE; + return array_search(ucfirst($class), $this->_ci_classes, TRUE); } // -------------------------------------------------------------------- /** - * Class Loader + * Library Loader * - * This function lets users load and instantiate classes. - * It is designed to be called from a user's app controllers. + * Loads and instantiates libraries. + * Designed to be called from application controllers. * - * @param string the name of the class - * @param mixed the optional parameters - * @param string an optional object name - * @return void + * @param string $library Library name + * @param array $params Optional parameters to pass to the library class constructor + * @param string $object_name An optional object name to assign to + * @return object */ - public function library($library = '', $params = NULL, $object_name = NULL) + public function library($library, $params = NULL, $object_name = NULL) { - if (is_array($library)) + if (empty($library)) + { + return $this; + } + elseif (is_array($library)) { - foreach ($library as $class) + foreach ($library as $key => $value) { - $this->library($class, $params); + if (is_int($key)) + { + $this->library($value, $params); + } + else + { + $this->library($key, $params, $value); + } } - return; - } - - if ($library == '' OR isset($this->_base_classes[$library])) - { - return FALSE; + return $this; } - if ( ! is_null($params) && ! is_array($params)) + if ($params !== NULL && ! is_array($params)) { $params = NULL; } - $this->_ci_load_class($library, $params, $object_name); + $this->_ci_load_library($library, $params, $object_name); + return $this; } // -------------------------------------------------------------------- @@ -221,27 +224,27 @@ class CI_Loader { /** * Model Loader * - * This function lets users load and instantiate models. + * Loads and instantiates models. * - * @param string the name of the class - * @param string name for the model - * @param bool database connection - * @return void + * @param string $model Model name + * @param string $name An optional object name to assign to + * @param bool $db_conn An optional database connection configuration to initialize + * @return object */ public function model($model, $name = '', $db_conn = FALSE) { - if (is_array($model)) + if (empty($model)) { - foreach ($model as $babe) + return $this; + } + elseif (is_array($model)) + { + foreach ($model as $key => $value) { - $this->model($babe); + is_int($key) ? $this->model($value, '', $db_conn) : $this->model($key, $value, $db_conn); } - return; - } - if ($model == '') - { - return; + return $this; } $path = ''; @@ -250,64 +253,105 @@ class CI_Loader { if (($last_slash = strrpos($model, '/')) !== FALSE) { // The path is in front of the last slash - $path = substr($model, 0, $last_slash + 1); + $path = substr($model, 0, ++$last_slash); // And the model name behind it - $model = substr($model, $last_slash + 1); + $model = substr($model, $last_slash); } - if ($name == '') + if (empty($name)) { $name = $model; } if (in_array($name, $this->_ci_models, TRUE)) { - return; + return $this; } $CI =& get_instance(); if (isset($CI->$name)) { - show_error('The model name you are loading is the name of a resource that is already being used: '.$name); + throw new RuntimeException('The model name you are loading is the name of a resource that is already being used: '.$name); } - $model = strtolower($model); - - foreach ($this->_ci_model_paths as $mod_path) + if ($db_conn !== FALSE && ! class_exists('CI_DB', FALSE)) { - if ( ! file_exists($mod_path.'models/'.$path.$model.'.php')) + if ($db_conn === TRUE) { - continue; + $db_conn = ''; } - if ($db_conn !== FALSE AND ! class_exists('CI_DB')) + $this->database($db_conn, FALSE, TRUE); + } + + // Note: All of the code under this condition used to be just: + // + // load_class('Model', 'core'); + // + // However, load_class() instantiates classes + // to cache them for later use and that prevents + // MY_Model from being an abstract class and is + // sub-optimal otherwise anyway. + if ( ! class_exists('CI_Model', FALSE)) + { + $app_path = APPPATH.'core'.DIRECTORY_SEPARATOR; + if (file_exists($app_path.'Model.php')) { - if ($db_conn === TRUE) + require_once($app_path.'Model.php'); + if ( ! class_exists('CI_Model', FALSE)) { - $db_conn = ''; + throw new RuntimeException($app_path."Model.php exists, but doesn't declare class CI_Model"); } - - $CI->load->database($db_conn, FALSE, TRUE); + } + elseif ( ! class_exists('CI_Model', FALSE)) + { + require_once(BASEPATH.'core'.DIRECTORY_SEPARATOR.'Model.php'); } - if ( ! class_exists('CI_Model')) + $class = config_item('subclass_prefix').'Model'; + if (file_exists($app_path.$class.'.php')) { - load_class('Model', 'core'); + require_once($app_path.$class.'.php'); + if ( ! class_exists($class, FALSE)) + { + throw new RuntimeException($app_path.$class.".php exists, but doesn't declare class ".$class); + } } + } - require_once($mod_path.'models/'.$path.$model.'.php'); + $model = ucfirst($model); + if ( ! class_exists($model, FALSE)) + { + foreach ($this->_ci_model_paths as $mod_path) + { + if ( ! file_exists($mod_path.'models/'.$path.$model.'.php')) + { + continue; + } - $model = ucfirst($model); + require_once($mod_path.'models/'.$path.$model.'.php'); + if ( ! class_exists($model, FALSE)) + { + throw new RuntimeException($mod_path."models/".$path.$model.".php exists, but doesn't declare class ".$model); + } - $CI->$name = new $model(); + break; + } - $this->_ci_models[] = $name; - return; + if ( ! class_exists($model, FALSE)) + { + throw new RuntimeException('Unable to locate the model you have specified: '.$model); + } + } + elseif ( ! is_subclass_of($model, 'CI_Model')) + { + throw new RuntimeException("Class ".$model." already exists and doesn't extend CI_Model"); } - // couldn't find the model - show_error('Unable to locate the model you have specified: '.$model); + $this->_ci_models[] = $name; + $CI->$name = new $model(); + return $this; } // -------------------------------------------------------------------- @@ -315,18 +359,21 @@ class CI_Loader { /** * Database Loader * - * @param string the DB credentials - * @param bool whether to return the DB object - * @param bool whether to enable active record (this allows us to override the config setting) - * @return object + * @param mixed $params Database configuration options + * @param bool $return Whether to return the database object + * @param bool $query_builder Whether to enable Query Builder + * (overrides the configuration setting) + * + * @return object|bool Database object if $return is set to TRUE, + * FALSE on failure, CI_Loader instance in any other case */ - public function database($params = '', $return = FALSE, $active_record = NULL) + public function database($params = '', $return = FALSE, $query_builder = NULL) { // Grab the super object $CI =& get_instance(); // Do we even need to load the database class? - if (class_exists('CI_DB') AND $return == FALSE AND $active_record == NULL AND isset($CI->db) AND is_object($CI->db)) + if ($return === FALSE && $query_builder === NULL && isset($CI->db) && is_object($CI->db) && ! empty($CI->db->conn_id)) { return FALSE; } @@ -335,42 +382,48 @@ class CI_Loader { if ($return === TRUE) { - return DB($params, $active_record); + return DB($params, $query_builder); } - // Initialize the db variable. Needed to prevent + // Initialize the db variable. Needed to prevent // reference errors with some configurations $CI->db = ''; // Load the DB class - $CI->db =& DB($params, $active_record); + $CI->db =& DB($params, $query_builder); + return $this; } // -------------------------------------------------------------------- /** - * Load the Utilities Class + * Load the Database Utilities Class * - * @return string + * @param object $db Database object + * @param bool $return Whether to return the DB Utilities class object or not + * @return object */ - public function dbutil() + public function dbutil($db = NULL, $return = FALSE) { - if ( ! class_exists('CI_DB')) - { - $this->database(); - } - $CI =& get_instance(); - // for backwards compatibility, load dbforge so we can extend dbutils off it - // this use is deprecated and strongly discouraged - $CI->load->dbforge(); + if ( ! is_object($db) OR ! ($db instanceof CI_DB)) + { + class_exists('CI_DB', FALSE) OR $this->database(); + $db =& $CI->db; + } require_once(BASEPATH.'database/DB_utility.php'); - require_once(BASEPATH.'database/drivers/'.$CI->db->dbdriver.'/'.$CI->db->dbdriver.'_utility.php'); - $class = 'CI_DB_'.$CI->db->dbdriver.'_utility'; + require_once(BASEPATH.'database/drivers/'.$db->dbdriver.'/'.$db->dbdriver.'_utility.php'); + $class = 'CI_DB_'.$db->dbdriver.'_utility'; - $CI->dbutil = new $class(); + if ($return === TRUE) + { + return new $class($db); + } + + $CI->dbutil = new $class($db); + return $this; } // -------------------------------------------------------------------- @@ -378,57 +431,72 @@ class CI_Loader { /** * Load the Database Forge Class * - * @return string + * @param object $db Database object + * @param bool $return Whether to return the DB Forge class object or not + * @return object */ - public function dbforge() + public function dbforge($db = NULL, $return = FALSE) { - if ( ! class_exists('CI_DB')) + $CI =& get_instance(); + if ( ! is_object($db) OR ! ($db instanceof CI_DB)) { - $this->database(); + class_exists('CI_DB', FALSE) OR $this->database(); + $db =& $CI->db; } - $CI =& get_instance(); - require_once(BASEPATH.'database/DB_forge.php'); - require_once(BASEPATH.'database/drivers/'.$CI->db->dbdriver.'/'.$CI->db->dbdriver.'_forge.php'); - $class = 'CI_DB_'.$CI->db->dbdriver.'_forge'; + require_once(BASEPATH.'database/drivers/'.$db->dbdriver.'/'.$db->dbdriver.'_forge.php'); - $CI->dbforge = new $class(); + if ( ! empty($db->subdriver)) + { + $driver_path = BASEPATH.'database/drivers/'.$db->dbdriver.'/subdrivers/'.$db->dbdriver.'_'.$db->subdriver.'_forge.php'; + if (file_exists($driver_path)) + { + require_once($driver_path); + $class = 'CI_DB_'.$db->dbdriver.'_'.$db->subdriver.'_forge'; + } + } + else + { + $class = 'CI_DB_'.$db->dbdriver.'_forge'; + } + + if ($return === TRUE) + { + return new $class($db); + } + + $CI->dbforge = new $class($db); + return $this; } // -------------------------------------------------------------------- /** - * Load View + * View Loader * - * This function is used to load a "view" file. It has three parameters: + * Loads "view" files. * - * 1. The name of the "view" file to be included. - * 2. An associative array of data to be extracted for use in the view. - * 3. TRUE/FALSE - whether to return the data or load it. In - * some cases it's advantageous to be able to return data so that - * a developer can process it in some way. - * - * @param string - * @param array - * @param bool - * @return void + * @param string $view View name + * @param array $vars An associative array of data + * to be extracted for use in the view + * @param bool $return Whether to return the view output + * or leave it to the Output class + * @return object|string */ public function view($view, $vars = array(), $return = FALSE) { - return $this->_ci_load(array('_ci_view' => $view, '_ci_vars' => $this->_ci_object_to_array($vars), '_ci_return' => $return)); + return $this->_ci_load(array('_ci_view' => $view, '_ci_vars' => $this->_ci_prepare_view_vars($vars), '_ci_return' => $return)); } // -------------------------------------------------------------------- /** - * Load File - * - * This is a generic file loader + * Generic File Loader * - * @param string - * @param bool - * @return string + * @param string $path File path + * @param bool $return Whether to return the file output + * @return object|string */ public function file($path, $return = FALSE) { @@ -443,26 +511,39 @@ class CI_Loader { * Once variables are set they become available within * the controller class and its "view" files. * - * @param array - * @param string - * @return void + * @param array|object|string $vars + * An associative array or object containing values + * to be set, or a value's name if string + * @param string $val Value to set, only used if $vars is a string + * @return object */ - public function vars($vars = array(), $val = '') + public function vars($vars, $val = '') { - if ($val != '' AND is_string($vars)) + $vars = is_string($vars) + ? array($vars => $val) + : $this->_ci_prepare_view_vars($vars); + + foreach ($vars as $key => $val) { - $vars = array($vars => $val); + $this->_ci_cached_vars[$key] = $val; } - $vars = $this->_ci_object_to_array($vars); + return $this; + } - if (is_array($vars) AND count($vars) > 0) - { - foreach ($vars as $key => $val) - { - $this->_ci_cached_vars[$key] = $val; - } - } + // -------------------------------------------------------------------- + + /** + * Clear Cached Variables + * + * Clears the cached variables. + * + * @return CI_Loader + */ + public function clear_vars() + { + $this->_ci_cached_vars = array(); + return $this; } // -------------------------------------------------------------------- @@ -472,8 +553,8 @@ class CI_Loader { * * Check if a variable is set and retrieve it. * - * @param array - * @return void + * @param string $key Variable name + * @return mixed The variable or NULL if not found */ public function get_var($key) { @@ -483,43 +564,68 @@ class CI_Loader { // -------------------------------------------------------------------- /** - * Load Helper + * Get Variables * - * This function loads the specified helper file. + * Retrieves all loaded variables. * - * @param mixed - * @return void + * @return array + */ + public function get_vars() + { + return $this->_ci_cached_vars; + } + + // -------------------------------------------------------------------- + + /** + * Helper Loader + * + * @param string|string[] $helpers Helper name(s) + * @return object */ public function helper($helpers = array()) { - foreach ($this->_ci_prep_filename($helpers, '_helper') as $helper) + is_array($helpers) OR $helpers = array($helpers); + foreach ($helpers as &$helper) { + $filename = basename($helper); + $filepath = ($filename === $helper) ? '' : substr($helper, 0, strlen($helper) - strlen($filename)); + $filename = strtolower(preg_replace('#(_helper)?(\.php)?$#i', '', $filename)).'_helper'; + $helper = $filepath.$filename; + if (isset($this->_ci_helpers[$helper])) { continue; } - $ext_helper = APPPATH.'helpers/'.config_item('subclass_prefix').$helper.'.php'; - // Is this a helper extension request? - if (file_exists($ext_helper)) + $ext_helper = config_item('subclass_prefix').$filename; + $ext_loaded = FALSE; + foreach ($this->_ci_helper_paths as $path) { - $base_helper = BASEPATH.'helpers/'.$helper.'.php'; + if (file_exists($path.'helpers/'.$ext_helper.'.php')) + { + include_once($path.'helpers/'.$ext_helper.'.php'); + $ext_loaded = TRUE; + } + } + // If we have loaded extensions - check if the base one is here + if ($ext_loaded === TRUE) + { + $base_helper = BASEPATH.'helpers/'.$helper.'.php'; if ( ! file_exists($base_helper)) { show_error('Unable to load the requested file: helpers/'.$helper.'.php'); } - include_once($ext_helper); include_once($base_helper); - $this->_ci_helpers[$helper] = TRUE; - log_message('debug', 'Helper loaded: '.$helper); + log_message('info', 'Helper loaded: '.$helper); continue; } - // Try to load the helper + // No extensions found ... try loading regular helpers and/or overrides foreach ($this->_ci_helper_paths as $path) { if (file_exists($path.'helpers/'.$helper.'.php')) @@ -527,7 +633,7 @@ class CI_Loader { include_once($path.'helpers/'.$helper.'.php'); $this->_ci_helpers[$helper] = TRUE; - log_message('debug', 'Helper loaded: '.$helper); + log_message('info', 'Helper loaded: '.$helper); break; } } @@ -538,6 +644,8 @@ class CI_Loader { show_error('Unable to load the requested file: helpers/'.$helper.'.php'); } } + + return $this; } // -------------------------------------------------------------------- @@ -545,82 +653,96 @@ class CI_Loader { /** * Load Helpers * - * This is simply an alias to the above function in case the - * user has written the plural form of this function. + * An alias for the helper() method in case the developer has + * written the plural form of it. * - * @param array - * @return void + * @uses CI_Loader::helper() + * @param string|string[] $helpers Helper name(s) + * @return object */ public function helpers($helpers = array()) { - $this->helper($helpers); + return $this->helper($helpers); } // -------------------------------------------------------------------- /** - * Loads a language file + * Language Loader * - * @param array - * @param string - * @return void + * Loads language files. + * + * @param string|string[] $files List of language file names to load + * @param string Language name + * @return object */ - public function language($file = array(), $lang = '') + public function language($files, $lang = '') { - $CI =& get_instance(); - - if ( ! is_array($file)) - { - $file = array($file); - } - - foreach ($file as $langfile) - { - $CI->lang->load($langfile, $lang); - } + get_instance()->lang->load($files, $lang); + return $this; } // -------------------------------------------------------------------- /** - * Loads a config file + * Config Loader * - * @param string - * @param bool - * @param bool - * @return void + * Loads a config file (an alias for CI_Config::load()). + * + * @uses CI_Config::load() + * @param string $file Configuration file name + * @param bool $use_sections Whether configuration values should be loaded into their own section + * @param bool $fail_gracefully Whether to just return FALSE or display an error message + * @return bool TRUE if the file was loaded correctly or FALSE on failure */ - public function config($file = '', $use_sections = FALSE, $fail_gracefully = FALSE) + public function config($file, $use_sections = FALSE, $fail_gracefully = FALSE) { - $CI =& get_instance(); - $CI->config->load($file, $use_sections, $fail_gracefully); + return get_instance()->config->load($file, $use_sections, $fail_gracefully); } // -------------------------------------------------------------------- /** - * Driver + * Driver Loader * - * Loads a driver library + * Loads a driver library. * - * @param string the name of the class - * @param mixed the optional parameters - * @param string an optional object name - * @return void + * @param string|string[] $library Driver name(s) + * @param array $params Optional parameters to pass to the driver + * @param string $object_name An optional object name to assign to + * + * @return object|bool Object or FALSE on failure if $library is a string + * and $object_name is set. CI_Loader instance otherwise. */ - public function driver($library = '', $params = NULL, $object_name = NULL) + public function driver($library, $params = NULL, $object_name = NULL) { - if ( ! class_exists('CI_Driver_Library')) + if (is_array($library)) { - // we aren't instantiating an object here, that'll be done by the Library itself - require BASEPATH.'libraries/Driver.php'; - } + foreach ($library as $key => $value) + { + if (is_int($key)) + { + $this->driver($value, $params); + } + else + { + $this->driver($key, $params, $value); + } + } - if ($library == '') + return $this; + } + elseif (empty($library)) { return FALSE; } + if ( ! class_exists('CI_Driver_Library', FALSE)) + { + // We aren't instantiating an object here, just making the base class available + require BASEPATH.'libraries/Driver.php'; + } + // We can save the loader some time since Drivers will *always* be in a subfolder, // and typically identically named to the library if ( ! strpos($library, '/')) @@ -636,13 +758,19 @@ class CI_Loader { /** * Add Package Path * - * Prepends a parent path to the library, model, helper, and config path arrays + * Prepends a parent path to the library, model, helper and config + * path arrays. * - * @param string - * @param boolean - * @return void + * @see CI_Loader::$_ci_library_paths + * @see CI_Loader::$_ci_model_paths + * @see CI_Loader::$_ci_helper_paths + * @see CI_Config::$_config_paths + * + * @param string $path Path to add + * @param bool $view_cascade (default: TRUE) + * @return object */ - public function add_package_path($path, $view_cascade=TRUE) + public function add_package_path($path, $view_cascade = TRUE) { $path = rtrim($path, '/').'/'; @@ -654,7 +782,9 @@ class CI_Loader { // Add config file path $config =& $this->_ci_get_component('config'); - array_unshift($config->_config_paths, $path); + $config->_config_paths[] = $path; + + return $this; } // -------------------------------------------------------------------- @@ -662,14 +792,14 @@ class CI_Loader { /** * Get Package Paths * - * Return a list of all package paths, by default it will ignore BASEPATH. + * Return a list of all package paths. * - * @param string - * @return void + * @param bool $include_base Whether to include BASEPATH (default: FALSE) + * @return array */ public function get_package_paths($include_base = FALSE) { - return $include_base === TRUE ? $this->_ci_library_paths : $this->_ci_model_paths; + return ($include_base === TRUE) ? $this->_ci_library_paths : $this->_ci_model_paths; } // -------------------------------------------------------------------- @@ -677,24 +807,24 @@ class CI_Loader { /** * Remove Package Path * - * Remove a path from the library, model, and helper path arrays if it exists - * If no path is provided, the most recently added path is removed. + * Remove a path from the library, model, helper and/or config + * path arrays if it exists. If no path is provided, the most recently + * added path will be removed removed. * - * @param type - * @param bool - * @return type + * @param string $path Path to remove + * @return object */ - public function remove_package_path($path = '', $remove_config_path = TRUE) + public function remove_package_path($path = '') { $config =& $this->_ci_get_component('config'); - if ($path == '') + if ($path === '') { - $void = array_shift($this->_ci_library_paths); - $void = array_shift($this->_ci_model_paths); - $void = array_shift($this->_ci_helper_paths); - $void = array_shift($this->_ci_view_paths); - $void = array_shift($config->_config_paths); + array_shift($this->_ci_library_paths); + array_shift($this->_ci_model_paths); + array_shift($this->_ci_helper_paths); + array_shift($this->_ci_view_paths); + array_pop($config->_config_paths); } else { @@ -724,32 +854,37 @@ class CI_Loader { $this->_ci_model_paths = array_unique(array_merge($this->_ci_model_paths, array(APPPATH))); $this->_ci_view_paths = array_merge($this->_ci_view_paths, array(APPPATH.'views/' => TRUE)); $config->_config_paths = array_unique(array_merge($config->_config_paths, array(APPPATH))); + + return $this; } // -------------------------------------------------------------------- /** - * Loader + * Internal CI Data Loader + * + * Used to load views and files. * - * This function is used to load views and files. * Variables are prefixed with _ci_ to avoid symbol collision with - * variables made available to view files + * variables made available to view files. * - * @param array - * @return void + * @used-by CI_Loader::view() + * @used-by CI_Loader::file() + * @param array $_ci_data Data to load + * @return object */ protected function _ci_load($_ci_data) { // Set the default data variables foreach (array('_ci_view', '_ci_vars', '_ci_path', '_ci_return') as $_ci_val) { - $$_ci_val = ( ! isset($_ci_data[$_ci_val])) ? FALSE : $_ci_data[$_ci_val]; + $$_ci_val = isset($_ci_data[$_ci_val]) ? $_ci_data[$_ci_val] : FALSE; } $file_exists = FALSE; // Set the path to the requested file - if ($_ci_path != '') + if (is_string($_ci_path) && $_ci_path !== '') { $_ci_x = explode('/', $_ci_path); $_ci_file = end($_ci_x); @@ -757,13 +892,13 @@ class CI_Loader { else { $_ci_ext = pathinfo($_ci_view, PATHINFO_EXTENSION); - $_ci_file = ($_ci_ext == '') ? $_ci_view.'.php' : $_ci_view; + $_ci_file = ($_ci_ext === '') ? $_ci_view.'.php' : $_ci_view; - foreach ($this->_ci_view_paths as $view_file => $cascade) + foreach ($this->_ci_view_paths as $_ci_view_file => $cascade) { - if (file_exists($view_file.$_ci_file)) + if (file_exists($_ci_view_file.$_ci_file)) { - $_ci_path = $view_file.$_ci_file; + $_ci_path = $_ci_view_file.$_ci_file; $file_exists = TRUE; break; } @@ -782,7 +917,6 @@ class CI_Loader { // This allows anything loaded using $this->load (views, files, etc.) // to become accessible from within the Controller and Model functions. - $_ci_CI =& get_instance(); foreach (get_object_vars($_ci_CI) as $_ci_key => $_ci_var) { @@ -795,15 +929,12 @@ class CI_Loader { /* * Extract and cache variables * - * You can either set variables using the dedicated $this->load_vars() + * You can either set variables using the dedicated $this->load->vars() * function or via the second parameter of this function. We'll merge * the two types and cache them so that views that are embedded within * other views can have access to these variables. */ - if (is_array($_ci_vars)) - { - $this->_ci_cached_vars = array_merge($this->_ci_cached_vars, $_ci_vars); - } + empty($_ci_vars) OR $this->_ci_cached_vars = array_merge($this->_ci_cached_vars, $_ci_vars); extract($this->_ci_cached_vars); /* @@ -811,29 +942,27 @@ class CI_Loader { * * We buffer the output for two reasons: * 1. Speed. You get a significant speed boost. - * 2. So that the final rendered template can be - * post-processed by the output class. Why do we - * need post processing? For one thing, in order to - * show the elapsed page load time. Unless we - * can intercept the content right before it's sent to - * the browser and then stop the timer it won't be accurate. + * 2. So that the final rendered template can be post-processed by + * the output class. Why do we need post processing? For one thing, + * in order to show the elapsed page load time. Unless we can + * intercept the content right before it's sent to the browser and + * then stop the timer it won't be accurate. */ ob_start(); // If the PHP installation does not support short tags we'll // do a little string replacement, changing the short tags // to standard PHP echo statements. - - if ((bool) @ini_get('short_open_tag') === FALSE AND config_item('rewrite_short_tags') == TRUE) + if ( ! is_php('5.4') && ! ini_get('short_open_tag') && config_item('rewrite_short_tags') === TRUE) { - echo eval('?>'.preg_replace("/;*\s*\?>/", "; ?>", str_replace('<?=', '<?php echo ', file_get_contents($_ci_path)))); + echo eval('?>'.preg_replace('/;*\s*\?>/', '; ?>', str_replace('<?=', '<?php echo ', file_get_contents($_ci_path)))); } else { include($_ci_path); // include() vs include_once() allows for multiple views with the same name } - log_message('debug', 'File loaded: '.$_ci_path); + log_message('info', 'File loaded: '.$_ci_path); // Return the file data if requested if ($_ci_return === TRUE) @@ -851,7 +980,6 @@ class CI_Loader { * we are beyond the first level of output buffering so that * it can be seen and included properly by the first included * template and any subsequent ones. Oy! - * */ if (ob_get_level() > $this->_ci_ob_level + 1) { @@ -862,21 +990,24 @@ class CI_Loader { $_ci_CI->output->append_output(ob_get_contents()); @ob_end_clean(); } + + return $this; } // -------------------------------------------------------------------- /** - * Load class + * Internal CI Library Loader * - * This function loads the requested class. + * @used-by CI_Loader::library() + * @uses CI_Loader::_ci_init_library() * - * @param string the item that is being loaded - * @param mixed any additional parameters - * @param string an optional object name + * @param string $class Class name to load + * @param mixed $params Optional parameters to pass to the class constructor + * @param string $object_name Optional object name to assign to * @return void */ - protected function _ci_load_class($class, $params = NULL, $object_name = NULL) + protected function _ci_load_library($class, $params = NULL, $object_name = NULL) { // Get the class name, and while we're at it trim any slashes. // The directory path can be included as part of the class name, @@ -885,128 +1016,184 @@ class CI_Loader { // Was the path included with the class name? // We look for a slash to determine this - $subdir = ''; if (($last_slash = strrpos($class, '/')) !== FALSE) { // Extract the path - $subdir = substr($class, 0, $last_slash + 1); + $subdir = substr($class, 0, ++$last_slash); // Get the filename from the path - $class = substr($class, $last_slash + 1); + $class = substr($class, $last_slash); + } + else + { + $subdir = ''; } - // We'll test for both lowercase and capitalized versions of the file name - foreach (array(ucfirst($class), strtolower($class)) as $class) + $class = ucfirst($class); + + // Is this a stock library? There are a few special conditions if so ... + if (file_exists(BASEPATH.'libraries/'.$subdir.$class.'.php')) { - $subclass = APPPATH.'libraries/'.$subdir.config_item('subclass_prefix').$class.'.php'; + return $this->_ci_load_stock_library($class, $subdir, $params, $object_name); + } - // Is this a class extension request? - if (file_exists($subclass)) + // Let's search for the requested library file and load it. + foreach ($this->_ci_library_paths as $path) + { + // BASEPATH has already been checked for + if ($path === BASEPATH) { - $baseclass = BASEPATH.'libraries/'.ucfirst($class).'.php'; + continue; + } - if ( ! file_exists($baseclass)) - { - log_message('error', "Unable to load the requested class: ".$class); - show_error("Unable to load the requested class: ".$class); - } + $filepath = $path.'libraries/'.$subdir.$class.'.php'; - // Safety: Was the class already loaded by a previous call? - if (in_array($subclass, $this->_ci_loaded_files)) + // Safety: Was the class already loaded by a previous call? + if (class_exists($class, FALSE)) + { + // Before we deem this to be a duplicate request, let's see + // if a custom object name is being supplied. If so, we'll + // return a new instance of the object + if ($object_name !== NULL) { - // Before we deem this to be a duplicate request, let's see - // if a custom object name is being supplied. If so, we'll - // return a new instance of the object - if ( ! is_null($object_name)) + $CI =& get_instance(); + if ( ! isset($CI->$object_name)) { - $CI =& get_instance(); - if ( ! isset($CI->$object_name)) - { - return $this->_ci_init_class($class, config_item('subclass_prefix'), $params, $object_name); - } + return $this->_ci_init_library($class, '', $params, $object_name); } - - $is_duplicate = TRUE; - log_message('debug', $class." class already loaded. Second attempt ignored."); - return; } - include_once($baseclass); - include_once($subclass); - $this->_ci_loaded_files[] = $subclass; - - return $this->_ci_init_class($class, config_item('subclass_prefix'), $params, $object_name); + log_message('debug', $class.' class already loaded. Second attempt ignored.'); + return; + } + // Does the file exist? No? Bummer... + elseif ( ! file_exists($filepath)) + { + continue; } - // Lets search for the requested library file and load it. - $is_duplicate = FALSE; - foreach ($this->_ci_library_paths as $path) + include_once($filepath); + return $this->_ci_init_library($class, '', $params, $object_name); + } + + // One last attempt. Maybe the library is in a subdirectory, but it wasn't specified? + if ($subdir === '') + { + return $this->_ci_load_library($class.'/'.$class, $params, $object_name); + } + + // If we got this far we were unable to find the requested class. + log_message('error', 'Unable to load the requested class: '.$class); + show_error('Unable to load the requested class: '.$class); + } + + // -------------------------------------------------------------------- + + /** + * Internal CI Stock Library Loader + * + * @used-by CI_Loader::_ci_load_library() + * @uses CI_Loader::_ci_init_library() + * + * @param string $library_name Library name to load + * @param string $file_path Path to the library filename, relative to libraries/ + * @param mixed $params Optional parameters to pass to the class constructor + * @param string $object_name Optional object name to assign to + * @return void + */ + protected function _ci_load_stock_library($library_name, $file_path, $params, $object_name) + { + $prefix = 'CI_'; + + if (class_exists($prefix.$library_name, FALSE)) + { + if (class_exists(config_item('subclass_prefix').$library_name, FALSE)) { - $filepath = $path.'libraries/'.$subdir.$class.'.php'; + $prefix = config_item('subclass_prefix'); + } - // Does the file exist? No? Bummer... - if ( ! file_exists($filepath)) + // Before we deem this to be a duplicate request, let's see + // if a custom object name is being supplied. If so, we'll + // return a new instance of the object + if ($object_name !== NULL) + { + $CI =& get_instance(); + if ( ! isset($CI->$object_name)) { - continue; + return $this->_ci_init_library($library_name, $prefix, $params, $object_name); } + } - // Safety: Was the class already loaded by a previous call? - if (in_array($filepath, $this->_ci_loaded_files)) - { - // Before we deem this to be a duplicate request, let's see - // if a custom object name is being supplied. If so, we'll - // return a new instance of the object - if ( ! is_null($object_name)) - { - $CI =& get_instance(); - if ( ! isset($CI->$object_name)) - { - return $this->_ci_init_class($class, '', $params, $object_name); - } - } + log_message('debug', $library_name.' class already loaded. Second attempt ignored.'); + return; + } - $is_duplicate = TRUE; - log_message('debug', $class." class already loaded. Second attempt ignored."); - return; - } + $paths = $this->_ci_library_paths; + array_pop($paths); // BASEPATH + array_pop($paths); // APPPATH (needs to be the first path checked) + array_unshift($paths, APPPATH); - include_once($filepath); - $this->_ci_loaded_files[] = $filepath; - return $this->_ci_init_class($class, '', $params, $object_name); + foreach ($paths as $path) + { + if (file_exists($path = $path.'libraries/'.$file_path.$library_name.'.php')) + { + // Override + include_once($path); + if (class_exists($prefix.$library_name, FALSE)) + { + return $this->_ci_init_library($library_name, $prefix, $params, $object_name); + } + else + { + log_message('debug', $path.' exists, but does not declare '.$prefix.$library_name); + } } + } - } // END FOREACH + include_once(BASEPATH.'libraries/'.$file_path.$library_name.'.php'); - // One last attempt. Maybe the library is in a subdirectory, but it wasn't specified? - if ($subdir == '') + // Check for extensions + $subclass = config_item('subclass_prefix').$library_name; + foreach ($paths as $path) { - $path = strtolower($class).'/'.$class; - return $this->_ci_load_class($path, $params); + if (file_exists($path = $path.'libraries/'.$file_path.$subclass.'.php')) + { + include_once($path); + if (class_exists($subclass, FALSE)) + { + $prefix = config_item('subclass_prefix'); + break; + } + else + { + log_message('debug', $path.' exists, but does not declare '.$subclass); + } + } } - // If we got this far we were unable to find the requested class. - // We do not issue errors if the load call failed due to a duplicate request - if ($is_duplicate == FALSE) - { - log_message('error', "Unable to load the requested class: ".$class); - show_error("Unable to load the requested class: ".$class); - } + return $this->_ci_init_library($library_name, $prefix, $params, $object_name); } // -------------------------------------------------------------------- /** - * Instantiates a class + * Internal CI Library Instantiator + * + * @used-by CI_Loader::_ci_load_stock_library() + * @used-by CI_Loader::_ci_load_library() * - * @param string - * @param string - * @param bool - * @param string an optional object name - * @return null + * @param string $class Class name + * @param string $prefix Class name prefix + * @param array|null|bool $config Optional configuration to pass to the class constructor: + * FALSE to skip; + * NULL to search in config paths; + * array containing configuration data + * @param string $object_name Optional object name to assign to + * @return void */ - protected function _ci_init_class($class, $prefix = '', $config = FALSE, $object_name = NULL) + protected function _ci_init_library($class, $prefix, $config = FALSE, $object_name = NULL) { - // Is there an associated config file for this class? Note: these should always be lowercase + // Is there an associated config file for this class? Note: these should always be lowercase if ($config === NULL) { // Fetch the config paths containing any package paths @@ -1014,117 +1201,111 @@ class CI_Loader { if (is_array($config_component->_config_paths)) { - // Break on the first found file, thus package files - // are not overridden by default paths + $found = FALSE; foreach ($config_component->_config_paths as $path) { // We test for both uppercase and lowercase, for servers that - // are case-sensitive with regard to file names. Check for environment - // first, global next - if (defined('ENVIRONMENT') AND file_exists($path .'config/'.ENVIRONMENT.'/'.strtolower($class).'.php')) + // are case-sensitive with regard to file names. Load global first, + // override with environment next + if (file_exists($path.'config/'.strtolower($class).'.php')) { - include($path .'config/'.ENVIRONMENT.'/'.strtolower($class).'.php'); - break; + include($path.'config/'.strtolower($class).'.php'); + $found = TRUE; } - elseif (defined('ENVIRONMENT') AND file_exists($path .'config/'.ENVIRONMENT.'/'.ucfirst(strtolower($class)).'.php')) + elseif (file_exists($path.'config/'.ucfirst(strtolower($class)).'.php')) { - include($path .'config/'.ENVIRONMENT.'/'.ucfirst(strtolower($class)).'.php'); - break; + include($path.'config/'.ucfirst(strtolower($class)).'.php'); + $found = TRUE; } - elseif (file_exists($path .'config/'.strtolower($class).'.php')) + + if (file_exists($path.'config/'.ENVIRONMENT.'/'.strtolower($class).'.php')) { - include($path .'config/'.strtolower($class).'.php'); - break; + include($path.'config/'.ENVIRONMENT.'/'.strtolower($class).'.php'); + $found = TRUE; } - elseif (file_exists($path .'config/'.ucfirst(strtolower($class)).'.php')) + elseif (file_exists($path.'config/'.ENVIRONMENT.'/'.ucfirst(strtolower($class)).'.php')) + { + include($path.'config/'.ENVIRONMENT.'/'.ucfirst(strtolower($class)).'.php'); + $found = TRUE; + } + + // Break on the first found configuration, thus package + // files are not overridden by default paths + if ($found === TRUE) { - include($path .'config/'.ucfirst(strtolower($class)).'.php'); break; } } } } - if ($prefix == '') - { - if (class_exists('CI_'.$class)) - { - $name = 'CI_'.$class; - } - elseif (class_exists(config_item('subclass_prefix').$class)) - { - $name = config_item('subclass_prefix').$class; - } - else - { - $name = $class; - } - } - else - { - $name = $prefix.$class; - } + $class_name = $prefix.$class; // Is the class name valid? - if ( ! class_exists($name)) + if ( ! class_exists($class_name, FALSE)) { - log_message('error', "Non-existent class: ".$name); - show_error("Non-existent class: ".$class); + log_message('error', 'Non-existent class: '.$class_name); + show_error('Non-existent class: '.$class_name); } // Set the variable name we will assign the class to - // Was a custom class name supplied? If so we'll use it - $class = strtolower($class); - - if (is_null($object_name)) + // Was a custom class name supplied? If so we'll use it + if (empty($object_name)) { - $classvar = ( ! isset($this->_ci_varmap[$class])) ? $class : $this->_ci_varmap[$class]; + $object_name = strtolower($class); + if (isset($this->_ci_varmap[$object_name])) + { + $object_name = $this->_ci_varmap[$object_name]; + } } - else + + // Don't overwrite existing properties + $CI =& get_instance(); + if (isset($CI->$object_name)) { - $classvar = $object_name; + if ($CI->$object_name instanceof $class_name) + { + log_message('debug', $class_name." has already been instantiated as '".$object_name."'. Second attempt aborted."); + return; + } + + show_error("Resource '".$object_name."' already exists and is not a ".$class_name." instance."); } // Save the class name and object name - $this->_ci_classes[$class] = $classvar; + $this->_ci_classes[$object_name] = $class; // Instantiate the class - $CI =& get_instance(); - if ($config !== NULL) - { - $CI->$classvar = new $name($config); - } - else - { - $CI->$classvar = new $name; - } + $CI->$object_name = isset($config) + ? new $class_name($config) + : new $class_name(); } // -------------------------------------------------------------------- /** - * Autoloader + * CI Autoloader * - * The config/autoload.php file contains an array that permits sub-systems, - * libraries, and helpers to be loaded automatically. + * Loads component listed in the config/autoload.php file. * - * @param array + * @used-by CI_Loader::initialize() * @return void */ - private function _ci_autoloader() + protected function _ci_autoloader() { - if (defined('ENVIRONMENT') AND file_exists(APPPATH.'config/'.ENVIRONMENT.'/autoload.php')) + if (file_exists(APPPATH.'config/autoload.php')) { - include(APPPATH.'config/'.ENVIRONMENT.'/autoload.php'); + include(APPPATH.'config/autoload.php'); } - else + + if (file_exists(APPPATH.'config/'.ENVIRONMENT.'/autoload.php')) { - include(APPPATH.'config/autoload.php'); + include(APPPATH.'config/'.ENVIRONMENT.'/autoload.php'); } if ( ! isset($autoload)) { - return FALSE; + return; } // Autoload packages @@ -1139,31 +1320,29 @@ class CI_Loader { // Load any custom config file if (count($autoload['config']) > 0) { - $CI =& get_instance(); - foreach ($autoload['config'] as $key => $val) + foreach ($autoload['config'] as $val) { - $CI->config->load($val); + $this->config($val); } } // Autoload helpers and languages foreach (array('helper', 'language') as $type) { - if (isset($autoload[$type]) AND count($autoload[$type]) > 0) + if (isset($autoload[$type]) && count($autoload[$type]) > 0) { $this->$type($autoload[$type]); } } - // A little tweak to remain backward compatible - // The $autoload['core'] item was deprecated - if ( ! isset($autoload['libraries']) AND isset($autoload['core'])) + // Autoload drivers + if (isset($autoload['drivers'])) { - $autoload['libraries'] = $autoload['core']; + $this->driver($autoload['drivers']); } // Load libraries - if (isset($autoload['libraries']) AND count($autoload['libraries']) > 0) + if (isset($autoload['libraries']) && count($autoload['libraries']) > 0) { // Load the database driver. if (in_array('database', $autoload['libraries'])) @@ -1173,10 +1352,7 @@ class CI_Loader { } // Load all other libraries - foreach ($autoload['libraries'] as $item) - { - $this->library($item); - } + $this->library($autoload['libraries']); } // Autoload models @@ -1189,24 +1365,42 @@ class CI_Loader { // -------------------------------------------------------------------- /** - * Object to Array + * Prepare variables for _ci_vars, to be later extract()-ed inside views * - * Takes an object as input and converts the class variables to array key/vals + * Converts objects to associative arrays and filters-out internal + * variable names (i.e. keys prefixed with '_ci_'). * - * @param object + * @param mixed $vars * @return array */ - protected function _ci_object_to_array($object) + protected function _ci_prepare_view_vars($vars) { - return (is_object($object)) ? get_object_vars($object) : $object; + if ( ! is_array($vars)) + { + $vars = is_object($vars) + ? get_object_vars($vars) + : array(); + } + + foreach (array_keys($vars) as $key) + { + if (strncmp($key, '_ci_', 4) === 0) + { + unset($vars[$key]); + } + } + + return $vars; } // -------------------------------------------------------------------- /** - * Get a reference to a specific library or model + * CI Component getter + * + * Get a reference to a specific library or model. * - * @param string + * @param string $component Component name * @return bool */ protected function &_ci_get_component($component) @@ -1214,35 +1408,4 @@ class CI_Loader { $CI =& get_instance(); return $CI->$component; } - - // -------------------------------------------------------------------- - - /** - * Prep filename - * - * This function preps the name of various items to make loading them more reliable. - * - * @param mixed - * @param string - * @return array - */ - protected function _ci_prep_filename($filename, $extension) - { - if ( ! is_array($filename)) - { - return array(strtolower(str_replace('.php', '', str_replace($extension, '', $filename)).$extension)); - } - else - { - foreach ($filename as $key => $val) - { - $filename[$key] = strtolower(str_replace('.php', '', str_replace($extension, '', $val)).$extension); - } - - return $filename; - } - } } - -/* End of file Loader.php */ -/* Location: ./system/core/Loader.php */
\ No newline at end of file diff --git a/system/core/Log.php b/system/core/Log.php new file mode 100644 index 000000000..d443aedb8 --- /dev/null +++ b/system/core/Log.php @@ -0,0 +1,296 @@ +<?php +/** + * CodeIgniter + * + * An open source application development framework for PHP + * + * This content is released under the MIT License (MIT) + * + * Copyright (c) 2014 - 2017, British Columbia Institute of Technology + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @package CodeIgniter + * @author EllisLab Dev Team + * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/) + * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) + * @license http://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com + * @since Version 1.0.0 + * @filesource + */ +defined('BASEPATH') OR exit('No direct script access allowed'); + +/** + * Logging Class + * + * @package CodeIgniter + * @subpackage Libraries + * @category Logging + * @author EllisLab Dev Team + * @link https://codeigniter.com/user_guide/general/errors.html + */ +class CI_Log { + + /** + * Path to save log files + * + * @var string + */ + protected $_log_path; + + /** + * File permissions + * + * @var int + */ + protected $_file_permissions = 0644; + + /** + * Level of logging + * + * @var int + */ + protected $_threshold = 1; + + /** + * Array of threshold levels to log + * + * @var array + */ + protected $_threshold_array = array(); + + /** + * Format of timestamp for log files + * + * @var string + */ + protected $_date_fmt = 'Y-m-d H:i:s'; + + /** + * Filename extension + * + * @var string + */ + protected $_file_ext; + + /** + * Whether or not the logger can write to the log files + * + * @var bool + */ + protected $_enabled = TRUE; + + /** + * Predefined logging levels + * + * @var array + */ + protected $_levels = array('ERROR' => 1, 'DEBUG' => 2, 'INFO' => 3, 'ALL' => 4); + + /** + * mbstring.func_overload flag + * + * @var bool + */ + protected static $func_overload; + + // -------------------------------------------------------------------- + + /** + * Class constructor + * + * @return void + */ + public function __construct() + { + $config =& get_config(); + + isset(self::$func_overload) OR self::$func_overload = (extension_loaded('mbstring') && ini_get('mbstring.func_overload')); + + $this->_log_path = ($config['log_path'] !== '') ? $config['log_path'] : APPPATH.'logs/'; + $this->_file_ext = (isset($config['log_file_extension']) && $config['log_file_extension'] !== '') + ? ltrim($config['log_file_extension'], '.') : 'php'; + + file_exists($this->_log_path) OR mkdir($this->_log_path, 0755, TRUE); + + if ( ! is_dir($this->_log_path) OR ! is_really_writable($this->_log_path)) + { + $this->_enabled = FALSE; + } + + if (is_numeric($config['log_threshold'])) + { + $this->_threshold = (int) $config['log_threshold']; + } + elseif (is_array($config['log_threshold'])) + { + $this->_threshold = 0; + $this->_threshold_array = array_flip($config['log_threshold']); + } + + if ( ! empty($config['log_date_format'])) + { + $this->_date_fmt = $config['log_date_format']; + } + + if ( ! empty($config['log_file_permissions']) && is_int($config['log_file_permissions'])) + { + $this->_file_permissions = $config['log_file_permissions']; + } + } + + // -------------------------------------------------------------------- + + /** + * Write Log File + * + * Generally this function will be called using the global log_message() function + * + * @param string $level The error level: 'error', 'debug' or 'info' + * @param string $msg The error message + * @return bool + */ + public function write_log($level, $msg) + { + if ($this->_enabled === FALSE) + { + return FALSE; + } + + $level = strtoupper($level); + + if (( ! isset($this->_levels[$level]) OR ($this->_levels[$level] > $this->_threshold)) + && ! isset($this->_threshold_array[$this->_levels[$level]])) + { + return FALSE; + } + + $filepath = $this->_log_path.'log-'.date('Y-m-d').'.'.$this->_file_ext; + $message = ''; + + if ( ! file_exists($filepath)) + { + $newfile = TRUE; + // Only add protection to php files + if ($this->_file_ext === 'php') + { + $message .= "<?php defined('BASEPATH') OR exit('No direct script access allowed'); ?>\n\n"; + } + } + + if ( ! $fp = @fopen($filepath, 'ab')) + { + return FALSE; + } + + flock($fp, LOCK_EX); + + // Instantiating DateTime with microseconds appended to initial date is needed for proper support of this format + if (strpos($this->_date_fmt, 'u') !== FALSE) + { + $microtime_full = microtime(TRUE); + $microtime_short = sprintf("%06d", ($microtime_full - floor($microtime_full)) * 1000000); + $date = new DateTime(date('Y-m-d H:i:s.'.$microtime_short, $microtime_full)); + $date = $date->format($this->_date_fmt); + } + else + { + $date = date($this->_date_fmt); + } + + $message .= $this->_format_line($level, $date, $msg); + + for ($written = 0, $length = self::strlen($message); $written < $length; $written += $result) + { + if (($result = fwrite($fp, self::substr($message, $written))) === FALSE) + { + break; + } + } + + flock($fp, LOCK_UN); + fclose($fp); + + if (isset($newfile) && $newfile === TRUE) + { + chmod($filepath, $this->_file_permissions); + } + + return is_int($result); + } + + // -------------------------------------------------------------------- + + /** + * Format the log line. + * + * This is for extensibility of log formatting + * If you want to change the log format, extend the CI_Log class and override this method + * + * @param string $level The error level + * @param string $date Formatted date string + * @param string $message The log message + * @return string Formatted log line with a new line character '\n' at the end + */ + protected function _format_line($level, $date, $message) + { + return $level.' - '.$date.' --> '.$message."\n"; + } + + // -------------------------------------------------------------------- + + /** + * Byte-safe strlen() + * + * @param string $str + * @return int + */ + protected static function strlen($str) + { + return (self::$func_overload) + ? mb_strlen($str, '8bit') + : strlen($str); + } + + // -------------------------------------------------------------------- + + /** + * Byte-safe substr() + * + * @param string $str + * @param int $start + * @param int $length + * @return string + */ + protected static function substr($str, $start, $length = NULL) + { + if (self::$func_overload) + { + // mb_substr($str, $start, null, '8bit') returns an empty + // string on PHP 5.3 + isset($length) OR $length = ($start >= 0 ? self::strlen($str) - $start : -$start); + return mb_substr($str, $start, $length, '8bit'); + } + + return isset($length) + ? substr($str, $start, $length) + : substr($str, $start); + } +} diff --git a/system/core/Model.php b/system/core/Model.php index 1f142509e..c809e7b84 100644 --- a/system/core/Model.php +++ b/system/core/Model.php @@ -1,57 +1,80 @@ -<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); +<?php /** * CodeIgniter * - * An open source application development framework for PHP 5.1.6 or newer + * An open source application development framework for PHP * - * @package CodeIgniter - * @author ExpressionEngine Dev Team - * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. - * @license http://codeigniter.com/user_guide/license.html - * @link http://codeigniter.com - * @since Version 1.0 + * This content is released under the MIT License (MIT) + * + * Copyright (c) 2014 - 2017, British Columbia Institute of Technology + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @package CodeIgniter + * @author EllisLab Dev Team + * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/) + * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) + * @license http://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com + * @since Version 1.0.0 * @filesource */ - -// ------------------------------------------------------------------------ +defined('BASEPATH') OR exit('No direct script access allowed'); /** - * CodeIgniter Model Class + * Model Class * * @package CodeIgniter * @subpackage Libraries * @category Libraries - * @author ExpressionEngine Dev Team - * @link http://codeigniter.com/user_guide/libraries/config.html + * @author EllisLab Dev Team + * @link https://codeigniter.com/user_guide/libraries/config.html */ class CI_Model { /** - * Constructor + * Class constructor * - * @access public + * @return void */ - function __construct() + public function __construct() { - log_message('debug', "Model Class Initialized"); + log_message('info', 'Model Class Initialized'); } + // -------------------------------------------------------------------- + /** - * __get + * __get magic * * Allows models to access CI's loaded classes using the same * syntax as controllers. * - * @param string - * @access private + * @param string $key */ - function __get($key) + public function __get($key) { - $CI =& get_instance(); - return $CI->$key; + // Debugging note: + // If you're here because you're getting an error message + // saying 'Undefined Property: system/core/Model.php', it's + // most likely a typo in your model code. + return get_instance()->$key; } -} -// END Model Class -/* End of file Model.php */ -/* Location: ./system/core/Model.php */
\ No newline at end of file +} diff --git a/system/core/Output.php b/system/core/Output.php index 7959befb7..a3155fece 100644 --- a/system/core/Output.php +++ b/system/core/Output.php @@ -1,112 +1,156 @@ -<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); +<?php /** * CodeIgniter * - * An open source application development framework for PHP 5.1.6 or newer + * An open source application development framework for PHP * - * @package CodeIgniter - * @author ExpressionEngine Dev Team - * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. - * @license http://codeigniter.com/user_guide/license.html - * @link http://codeigniter.com - * @since Version 1.0 + * This content is released under the MIT License (MIT) + * + * Copyright (c) 2014 - 2017, British Columbia Institute of Technology + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @package CodeIgniter + * @author EllisLab Dev Team + * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/) + * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) + * @license http://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com + * @since Version 1.0.0 * @filesource */ - -// ------------------------------------------------------------------------ +defined('BASEPATH') OR exit('No direct script access allowed'); /** * Output Class * - * Responsible for sending final output to browser + * Responsible for sending final output to the browser. * * @package CodeIgniter * @subpackage Libraries * @category Output - * @author ExpressionEngine Dev Team - * @link http://codeigniter.com/user_guide/libraries/output.html + * @author EllisLab Dev Team + * @link https://codeigniter.com/user_guide/libraries/output.html */ class CI_Output { /** - * Current output string + * Final output string * - * @var string - * @access protected + * @var string */ - protected $final_output; + public $final_output; + /** * Cache expiration time * - * @var int - * @access protected + * @var int */ - protected $cache_expiration = 0; + public $cache_expiration = 0; + /** * List of server headers * - * @var array - * @access protected + * @var array */ - protected $headers = array(); + public $headers = array(); + /** * List of mime types * - * @var array - * @access protected + * @var array */ - protected $mime_types = array(); + public $mimes = array(); + /** - * Determines wether profiler is enabled + * Mime-type for the current page * - * @var book - * @access protected + * @var string */ - protected $enable_profiler = FALSE; + protected $mime_type = 'text/html'; + /** - * Determines if output compression is enabled + * Enable Profiler flag * - * @var bool - * @access protected + * @var bool */ - protected $_zlib_oc = FALSE; + public $enable_profiler = FALSE; + + /** + * php.ini zlib.output_compression flag + * + * @var bool + */ + protected $_zlib_oc = FALSE; + + /** + * CI output compression flag + * + * @var bool + */ + protected $_compress_output = FALSE; + /** * List of profiler sections * - * @var array - * @access protected + * @var array */ - protected $_profiler_sections = array(); + protected $_profiler_sections = array(); + /** - * Whether or not to parse variables like {elapsed_time} and {memory_usage} + * Parse markers flag * - * @var bool - * @access protected + * Whether or not to parse variables like {elapsed_time} and {memory_usage}. + * + * @var bool */ - protected $parse_exec_vars = TRUE; + public $parse_exec_vars = TRUE; /** - * Constructor + * mbstring.func_overload flag * + * @var bool */ - function __construct() - { - $this->_zlib_oc = @ini_get('zlib.output_compression'); + protected static $func_overload; - // Get mime types for later - if (defined('ENVIRONMENT') AND file_exists(APPPATH.'config/'.ENVIRONMENT.'/mimes.php')) - { - include APPPATH.'config/'.ENVIRONMENT.'/mimes.php'; - } - else - { - include APPPATH.'config/mimes.php'; - } + /** + * Class constructor + * + * Determines whether zLib output compression will be used. + * + * @return void + */ + public function __construct() + { + $this->_zlib_oc = (bool) ini_get('zlib.output_compression'); + $this->_compress_output = ( + $this->_zlib_oc === FALSE + && config_item('compress_output') === TRUE + && extension_loaded('zlib') + ); + isset(self::$func_overload) OR self::$func_overload = (extension_loaded('mbstring') && ini_get('mbstring.func_overload')); - $this->mime_types = $mimes; + // Get mime types for later + $this->mimes =& get_mimes(); - log_message('debug', "Output Class Initialized"); + log_message('info', 'Output Class Initialized'); } // -------------------------------------------------------------------- @@ -114,12 +158,11 @@ class CI_Output { /** * Get Output * - * Returns the current output string + * Returns the current output string. * - * @access public * @return string */ - function get_output() + public function get_output() { return $this->final_output; } @@ -129,16 +172,14 @@ class CI_Output { /** * Set Output * - * Sets the output string + * Sets the output string. * - * @access public - * @param string - * @return void + * @param string $output Output data + * @return CI_Output */ - function set_output($output) + public function set_output($output) { $this->final_output = $output; - return $this; } @@ -147,23 +188,14 @@ class CI_Output { /** * Append Output * - * Appends data onto the output string + * Appends data onto the output string. * - * @access public - * @param string - * @return void + * @param string $output Data to append + * @return CI_Output */ - function append_output($output) + public function append_output($output) { - if ($this->final_output == '') - { - $this->final_output = $output; - } - else - { - $this->final_output .= $output; - } - + $this->final_output .= $output; return $this; } @@ -172,52 +204,49 @@ class CI_Output { /** * Set Header * - * Lets you set a server header which will be outputted with the final display. + * Lets you set a server header which will be sent with the final output. * - * Note: If a file is cached, headers will not be sent. We need to figure out - * how to permit header data to be saved with the cache data... + * Note: If a file is cached, headers will not be sent. + * @todo We need to figure out how to permit headers to be cached. * - * @access public - * @param string - * @param bool - * @return void + * @param string $header Header + * @param bool $replace Whether to replace the old header value, if already set + * @return CI_Output */ - function set_header($header, $replace = TRUE) + public function set_header($header, $replace = TRUE) { // If zlib.output_compression is enabled it will compress the output, // but it will not modify the content-length header to compensate for // the reduction, causing the browser to hang waiting for more data. // We'll just skip content-length in those cases. - - if ($this->_zlib_oc && strncasecmp($header, 'content-length', 14) == 0) + if ($this->_zlib_oc && strncasecmp($header, 'content-length', 14) === 0) { - return; + return $this; } $this->headers[] = array($header, $replace); - return $this; } // -------------------------------------------------------------------- /** - * Set Content Type Header + * Set Content-Type Header * - * @access public - * @param string extension of the file we're outputting - * @return void + * @param string $mime_type Extension of the file we're outputting + * @param string $charset Character set (default: NULL) + * @return CI_Output */ - function set_content_type($mime_type) + public function set_content_type($mime_type, $charset = NULL) { if (strpos($mime_type, '/') === FALSE) { $extension = ltrim($mime_type, '.'); // Is this extension supported? - if (isset($this->mime_types[$extension])) + if (isset($this->mimes[$extension])) { - $mime_type =& $this->mime_types[$extension]; + $mime_type =& $this->mimes[$extension]; if (is_array($mime_type)) { @@ -226,28 +255,89 @@ class CI_Output { } } - $header = 'Content-Type: '.$mime_type; + $this->mime_type = $mime_type; - $this->headers[] = array($header, TRUE); + if (empty($charset)) + { + $charset = config_item('charset'); + } + + $header = 'Content-Type: '.$mime_type + .(empty($charset) ? '' : '; charset='.$charset); + $this->headers[] = array($header, TRUE); return $this; } // -------------------------------------------------------------------- /** + * Get Current Content-Type Header + * + * @return string 'text/html', if not already set + */ + public function get_content_type() + { + for ($i = 0, $c = count($this->headers); $i < $c; $i++) + { + if (sscanf($this->headers[$i][0], 'Content-Type: %[^;]', $content_type) === 1) + { + return $content_type; + } + } + + return 'text/html'; + } + + // -------------------------------------------------------------------- + + /** + * Get Header + * + * @param string $header + * @return string + */ + public function get_header($header) + { + // Combine headers already sent with our batched headers + $headers = array_merge( + // We only need [x][0] from our multi-dimensional array + array_map('array_shift', $this->headers), + headers_list() + ); + + if (empty($headers) OR empty($header)) + { + return NULL; + } + + // Count backwards, in order to get the last matching header + for ($c = count($headers) - 1; $c > -1; $c--) + { + if (strncasecmp($header, $headers[$c], $l = self::strlen($header)) === 0) + { + return trim(self::substr($headers[$c], $l+1)); + } + } + + return NULL; + } + + // -------------------------------------------------------------------- + + /** * Set HTTP Status Header - * moved to Common procedural functions in 1.7.2 * - * @access public - * @param int the status code - * @param string - * @return void + * As of version 1.7.2, this is an alias for common function + * set_status_header(). + * + * @param int $code Status code (default: 200) + * @param string $text Optional message + * @return CI_Output */ - function set_status_header($code = 200, $text = '') + public function set_status_header($code = 200, $text = '') { set_status_header($code, $text); - return $this; } @@ -256,14 +346,12 @@ class CI_Output { /** * Enable/disable Profiler * - * @access public - * @param bool - * @return void + * @param bool $val TRUE to enable or FALSE to disable + * @return CI_Output */ - function enable_profiler($val = TRUE) + public function enable_profiler($val = TRUE) { - $this->enable_profiler = (is_bool($val)) ? $val : TRUE; - + $this->enable_profiler = is_bool($val) ? $val : TRUE; return $this; } @@ -272,17 +360,23 @@ class CI_Output { /** * Set Profiler Sections * - * Allows override of default / config settings for Profiler section display + * Allows override of default/config settings for + * Profiler section display. * - * @access public - * @param array - * @return void + * @param array $sections Profiler sections + * @return CI_Output */ - function set_profiler_sections($sections) + public function set_profiler_sections($sections) { + if (isset($sections['query_toggle_count'])) + { + $this->_profiler_sections['query_toggle_count'] = (int) $sections['query_toggle_count']; + unset($sections['query_toggle_count']); + } + foreach ($sections as $section => $enable) { - $this->_profiler_sections[$section] = ($enable !== FALSE) ? TRUE : FALSE; + $this->_profiler_sections[$section] = ($enable !== FALSE); } return $this; @@ -293,14 +387,12 @@ class CI_Output { /** * Set Cache * - * @access public - * @param integer - * @return void + * @param int $time Cache expiration time in minutes + * @return CI_Output */ - function cache($time) + public function cache($time) { - $this->cache_expiration = ( ! is_numeric($time)) ? 0 : $time; - + $this->cache_expiration = is_numeric($time) ? $time : 0; return $this; } @@ -309,27 +401,27 @@ class CI_Output { /** * Display Output * - * All "view" data is automatically put into this variable by the controller class: - * - * $this->final_output + * Processes and sends finalized output data to the browser along + * with any server headers and profile data. It also stops benchmark + * timers so the page rendering speed and memory usage can be shown. * - * This function sends the finalized output data to the browser along - * with any server headers and profile data. It also stops the - * benchmark timer so the page rendering speed and memory usage can be shown. + * Note: All "view" data is automatically put into $this->final_output + * by controller class. * - * @access public - * @param string - * @return mixed + * @uses CI_Output::$final_output + * @param string $output Output data override + * @return void */ - function _display($output = '') + public function _display($output = '') { - // Note: We use globals because we can't use $CI =& get_instance() + // Note: We use load_class() because we can't use $CI =& get_instance() // since this function is sometimes called by the caching mechanism, // which happens before the CI super object is available. - global $BM, $CFG; + $BM =& load_class('Benchmark', 'core'); + $CFG =& load_class('Config', 'core'); // Grab the super object if we can. - if (class_exists('CI_Controller')) + if (class_exists('CI_Controller', FALSE)) { $CI =& get_instance(); } @@ -337,14 +429,14 @@ class CI_Output { // -------------------------------------------------------------------- // Set the output data - if ($output == '') + if ($output === '') { $output =& $this->final_output; } // -------------------------------------------------------------------- - // Do we need to write a cache file? Only if the controller does not have its + // Do we need to write a cache file? Only if the controller does not have its // own _output() method and we are not dealing with a cache file, which we // can determine by the existence of the $CI object above if ($this->cache_expiration > 0 && isset($CI) && ! method_exists($CI, '_output')) @@ -361,24 +453,18 @@ class CI_Output { if ($this->parse_exec_vars === TRUE) { - $memory = ( ! function_exists('memory_get_usage')) ? '0' : round(memory_get_usage()/1024/1024, 2).'MB'; - - $output = str_replace('{elapsed_time}', $elapsed, $output); - $output = str_replace('{memory_usage}', $memory, $output); + $memory = round(memory_get_usage() / 1024 / 1024, 2).'MB'; + $output = str_replace(array('{elapsed_time}', '{memory_usage}'), array($elapsed, $memory), $output); } // -------------------------------------------------------------------- // Is compression requested? - if ($CFG->item('compress_output') === TRUE && $this->_zlib_oc == FALSE) + if (isset($CI) // This means that we're not serving a cache file, if we were, it would already be compressed + && $this->_compress_output === TRUE + && isset($_SERVER['HTTP_ACCEPT_ENCODING']) && strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== FALSE) { - if (extension_loaded('zlib')) - { - if (isset($_SERVER['HTTP_ACCEPT_ENCODING']) AND strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== FALSE) - { - ob_start('ob_gzhandler'); - } - } + ob_start('ob_gzhandler'); } // -------------------------------------------------------------------- @@ -399,20 +485,34 @@ class CI_Output { // simply echo out the data and exit. if ( ! isset($CI)) { + if ($this->_compress_output === TRUE) + { + if (isset($_SERVER['HTTP_ACCEPT_ENCODING']) && strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== FALSE) + { + header('Content-Encoding: gzip'); + header('Content-Length: '.self::strlen($output)); + } + else + { + // User agent doesn't support gzip compression, + // so we'll have to decompress our cache + $output = gzinflate(self::substr($output, 10, -8)); + } + } + echo $output; - log_message('debug', "Final output sent to browser"); - log_message('debug', "Total execution time: ".$elapsed); - return TRUE; + log_message('info', 'Final output sent to browser'); + log_message('debug', 'Total execution time: '.$elapsed); + return; } // -------------------------------------------------------------------- // Do we need to generate profile data? // If so, load the Profile class and run it. - if ($this->enable_profiler == TRUE) + if ($this->enable_profiler === TRUE) { $CI->load->library('profiler'); - if ( ! empty($this->_profiler_sections)) { $CI->profiler->set_sections($this->_profiler_sections); @@ -420,20 +520,13 @@ class CI_Output { // If the output data contains closing </body> and </html> tags // we will remove them and add them back after we insert the profile data - if (preg_match("|</body>.*?</html>|is", $output)) + $output = preg_replace('|</body>.*?</html>|is', '', $output, -1, $count).$CI->profiler->run(); + if ($count > 0) { - $output = preg_replace("|</body>.*?</html>|is", '', $output); - $output .= $CI->profiler->run(); $output .= '</body></html>'; } - else - { - $output .= $CI->profiler->run(); - } } - // -------------------------------------------------------------------- - // Does the controller contain a function named _output()? // If so send the output there. Otherwise, echo it. if (method_exists($CI, '_output')) @@ -442,133 +535,308 @@ class CI_Output { } else { - echo $output; // Send it to the browser! + echo $output; // Send it to the browser! } - log_message('debug', "Final output sent to browser"); - log_message('debug', "Total execution time: ".$elapsed); + log_message('info', 'Final output sent to browser'); + log_message('debug', 'Total execution time: '.$elapsed); } // -------------------------------------------------------------------- /** - * Write a Cache File + * Write Cache * - * @access public - * @param string + * @param string $output Output data to cache * @return void */ - function _write_cache($output) + public function _write_cache($output) { $CI =& get_instance(); $path = $CI->config->item('cache_path'); - - $cache_path = ($path == '') ? APPPATH.'cache/' : $path; + $cache_path = ($path === '') ? APPPATH.'cache/' : $path; if ( ! is_dir($cache_path) OR ! is_really_writable($cache_path)) { - log_message('error', "Unable to write cache file: ".$cache_path); + log_message('error', 'Unable to write cache file: '.$cache_path); return; } - $uri = $CI->config->item('base_url'). - $CI->config->item('index_page'). - $CI->uri->uri_string(); + $uri = $CI->config->item('base_url') + .$CI->config->item('index_page') + .$CI->uri->uri_string(); + + if (($cache_query_string = $CI->config->item('cache_query_string')) && ! empty($_SERVER['QUERY_STRING'])) + { + if (is_array($cache_query_string)) + { + $uri .= '?'.http_build_query(array_intersect_key($_GET, array_flip($cache_query_string))); + } + else + { + $uri .= '?'.$_SERVER['QUERY_STRING']; + } + } $cache_path .= md5($uri); - if ( ! $fp = @fopen($cache_path, FOPEN_WRITE_CREATE_DESTRUCTIVE)) + if ( ! $fp = @fopen($cache_path, 'w+b')) { - log_message('error', "Unable to write cache file: ".$cache_path); + log_message('error', 'Unable to write cache file: '.$cache_path); return; } + if ( ! flock($fp, LOCK_EX)) + { + log_message('error', 'Unable to secure a file lock for file at: '.$cache_path); + fclose($fp); + return; + } + + // If output compression is enabled, compress the cache + // itself, so that we don't have to do that each time + // we're serving it + if ($this->_compress_output === TRUE) + { + $output = gzencode($output); + + if ($this->get_header('content-type') === NULL) + { + $this->set_content_type($this->mime_type); + } + } + $expire = time() + ($this->cache_expiration * 60); - if (flock($fp, LOCK_EX)) + // Put together our serialized info. + $cache_info = serialize(array( + 'expire' => $expire, + 'headers' => $this->headers + )); + + $output = $cache_info.'ENDCI--->'.$output; + + for ($written = 0, $length = self::strlen($output); $written < $length; $written += $result) { - fwrite($fp, $expire.'TS--->'.$output); - flock($fp, LOCK_UN); + if (($result = fwrite($fp, self::substr($output, $written))) === FALSE) + { + break; + } } - else + + flock($fp, LOCK_UN); + fclose($fp); + + if ( ! is_int($result)) { - log_message('error', "Unable to secure a file lock for file at: ".$cache_path); + @unlink($cache_path); + log_message('error', 'Unable to write the complete cache content at: '.$cache_path); return; } - fclose($fp); - @chmod($cache_path, FILE_WRITE_MODE); - log_message('debug', "Cache file written: ".$cache_path); + chmod($cache_path, 0640); + log_message('debug', 'Cache file written: '.$cache_path); + + // Send HTTP cache-control headers to browser to match file cache settings. + $this->set_cache_header($_SERVER['REQUEST_TIME'], $expire); } // -------------------------------------------------------------------- /** - * Update/serve a cached file + * Update/serve cached output * - * @access public - * @param object config class - * @param object uri class - * @return void + * @uses CI_Config + * @uses CI_URI + * + * @param object &$CFG CI_Config class instance + * @param object &$URI CI_URI class instance + * @return bool TRUE on success or FALSE on failure */ - function _display_cache(&$CFG, &$URI) + public function _display_cache(&$CFG, &$URI) { - $cache_path = ($CFG->item('cache_path') == '') ? APPPATH.'cache/' : $CFG->item('cache_path'); + $cache_path = ($CFG->item('cache_path') === '') ? APPPATH.'cache/' : $CFG->item('cache_path'); - // Build the file path. The file name is an MD5 hash of the full URI - $uri = $CFG->item('base_url'). - $CFG->item('index_page'). - $URI->uri_string; + // Build the file path. The file name is an MD5 hash of the full URI + $uri = $CFG->item('base_url').$CFG->item('index_page').$URI->uri_string; + + if (($cache_query_string = $CFG->item('cache_query_string')) && ! empty($_SERVER['QUERY_STRING'])) + { + if (is_array($cache_query_string)) + { + $uri .= '?'.http_build_query(array_intersect_key($_GET, array_flip($cache_query_string))); + } + else + { + $uri .= '?'.$_SERVER['QUERY_STRING']; + } + } $filepath = $cache_path.md5($uri); - if ( ! @file_exists($filepath)) + if ( ! file_exists($filepath) OR ! $fp = @fopen($filepath, 'rb')) { return FALSE; } - if ( ! $fp = @fopen($filepath, FOPEN_READ)) + flock($fp, LOCK_SH); + + $cache = (filesize($filepath) > 0) ? fread($fp, filesize($filepath)) : ''; + + flock($fp, LOCK_UN); + fclose($fp); + + // Look for embedded serialized file info. + if ( ! preg_match('/^(.*)ENDCI--->/', $cache, $match)) { return FALSE; } - flock($fp, LOCK_SH); + $cache_info = unserialize($match[1]); + $expire = $cache_info['expire']; + + $last_modified = filemtime($filepath); - $cache = ''; - if (filesize($filepath) > 0) + // Has the file expired? + if ($_SERVER['REQUEST_TIME'] >= $expire && is_really_writable($cache_path)) { - $cache = fread($fp, filesize($filepath)); + // If so we'll delete it. + @unlink($filepath); + log_message('debug', 'Cache file has expired. File deleted.'); + return FALSE; } - flock($fp, LOCK_UN); - fclose($fp); + // Send the HTTP cache control headers + $this->set_cache_header($last_modified, $expire); - // Strip out the embedded timestamp - if ( ! preg_match("/(\d+TS--->)/", $cache, $match)) + // Add headers from cache file. + foreach ($cache_info['headers'] as $header) { + $this->set_header($header[0], $header[1]); + } + + // Display the cache + $this->_display(self::substr($cache, self::strlen($match[0]))); + log_message('debug', 'Cache file is current. Sending it to browser.'); + return TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Delete cache + * + * @param string $uri URI string + * @return bool + */ + public function delete_cache($uri = '') + { + $CI =& get_instance(); + $cache_path = $CI->config->item('cache_path'); + if ($cache_path === '') + { + $cache_path = APPPATH.'cache/'; + } + + if ( ! is_dir($cache_path)) + { + log_message('error', 'Unable to find cache path: '.$cache_path); return FALSE; } - // Has the file expired? If so we'll delete it. - if (time() >= trim(str_replace('TS--->', '', $match['1']))) + if (empty($uri)) { - if (is_really_writable($cache_path)) + $uri = $CI->uri->uri_string(); + + if (($cache_query_string = $CI->config->item('cache_query_string')) && ! empty($_SERVER['QUERY_STRING'])) { - @unlink($filepath); - log_message('debug', "Cache file has expired. File deleted"); - return FALSE; + if (is_array($cache_query_string)) + { + $uri .= '?'.http_build_query(array_intersect_key($_GET, array_flip($cache_query_string))); + } + else + { + $uri .= '?'.$_SERVER['QUERY_STRING']; + } } } - // Display the cache - $this->_display(str_replace($match['0'], '', $cache)); - log_message('debug', "Cache file is current. Sending it to browser."); + $cache_path .= md5($CI->config->item('base_url').$CI->config->item('index_page').ltrim($uri, '/')); + + if ( ! @unlink($cache_path)) + { + log_message('error', 'Unable to delete cache file for '.$uri); + return FALSE; + } + return TRUE; } + // -------------------------------------------------------------------- -} -// END Output Class + /** + * Set Cache Header + * + * Set the HTTP headers to match the server-side file cache settings + * in order to reduce bandwidth. + * + * @param int $last_modified Timestamp of when the page was last modified + * @param int $expiration Timestamp of when should the requested page expire from cache + * @return void + */ + public function set_cache_header($last_modified, $expiration) + { + $max_age = $expiration - $_SERVER['REQUEST_TIME']; + + if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && $last_modified <= strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE'])) + { + $this->set_status_header(304); + exit; + } + + header('Pragma: public'); + header('Cache-Control: max-age='.$max_age.', public'); + header('Expires: '.gmdate('D, d M Y H:i:s', $expiration).' GMT'); + header('Last-modified: '.gmdate('D, d M Y H:i:s', $last_modified).' GMT'); + } + + // -------------------------------------------------------------------- + + /** + * Byte-safe strlen() + * + * @param string $str + * @return int + */ + protected static function strlen($str) + { + return (self::$func_overload) + ? mb_strlen($str, '8bit') + : strlen($str); + } + + // -------------------------------------------------------------------- + + /** + * Byte-safe substr() + * + * @param string $str + * @param int $start + * @param int $length + * @return string + */ + protected static function substr($str, $start, $length = NULL) + { + if (self::$func_overload) + { + // mb_substr($str, $start, null, '8bit') returns an empty + // string on PHP 5.3 + isset($length) OR $length = ($start >= 0 ? self::strlen($str) - $start : -$start); + return mb_substr($str, $start, $length, '8bit'); + } -/* End of file Output.php */ -/* Location: ./system/core/Output.php */
\ No newline at end of file + return isset($length) + ? substr($str, $start, $length) + : substr($str, $start); + } +} diff --git a/system/core/Router.php b/system/core/Router.php index b48a34562..1abe4c4e5 100644 --- a/system/core/Router.php +++ b/system/core/Router.php @@ -1,19 +1,41 @@ -<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); +<?php /** * CodeIgniter * - * An open source application development framework for PHP 5.1.6 or newer + * An open source application development framework for PHP * - * @package CodeIgniter - * @author ExpressionEngine Dev Team - * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. - * @license http://codeigniter.com/user_guide/license.html - * @link http://codeigniter.com - * @since Version 1.0 + * This content is released under the MIT License (MIT) + * + * Copyright (c) 2014 - 2017, British Columbia Institute of Technology + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @package CodeIgniter + * @author EllisLab Dev Team + * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/) + * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) + * @license http://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com + * @since Version 1.0.0 * @filesource */ - -// ------------------------------------------------------------------------ +defined('BASEPATH') OR exit('No direct script access allowed'); /** * Router Class @@ -22,389 +44,394 @@ * * @package CodeIgniter * @subpackage Libraries - * @author ExpressionEngine Dev Team * @category Libraries - * @link http://codeigniter.com/user_guide/general/routing.html + * @author EllisLab Dev Team + * @link https://codeigniter.com/user_guide/general/routing.html */ class CI_Router { /** - * Config class + * CI_Config class object * - * @var object - * @access public + * @var object */ - var $config; + public $config; + /** * List of routes * - * @var array - * @access public - */ - var $routes = array(); - /** - * List of error routes - * - * @var array - * @access public + * @var array */ - var $error_routes = array(); + public $routes = array(); + /** * Current class name * - * @var string - * @access public + * @var string */ - var $class = ''; + public $class = ''; + /** * Current method name * - * @var string - * @access public + * @var string */ - var $method = 'index'; + public $method = 'index'; + /** * Sub-directory that contains the requested controller class * - * @var string - * @access public + * @var string */ - var $directory = ''; + public $directory; + /** * Default controller (and method if specific) * - * @var string - * @access public + * @var string + */ + public $default_controller; + + /** + * Translate URI dashes + * + * Determines whether dashes in controller & method segments + * should be automatically replaced by underscores. + * + * @var bool + */ + public $translate_uri_dashes = FALSE; + + /** + * Enable query strings flag + * + * Determines whether to use GET parameters or segment URIs + * + * @var bool */ - var $default_controller; + public $enable_query_strings = FALSE; + + // -------------------------------------------------------------------- /** - * Constructor + * Class constructor * * Runs the route mapping function. + * + * @param array $routing + * @return void */ - function __construct() + public function __construct($routing = NULL) { $this->config =& load_class('Config', 'core'); $this->uri =& load_class('URI', 'core'); - log_message('debug', "Router Class Initialized"); + + $this->enable_query_strings = ( ! is_cli() && $this->config->item('enable_query_strings') === TRUE); + + // If a directory override is configured, it has to be set before any dynamic routing logic + is_array($routing) && isset($routing['directory']) && $this->set_directory($routing['directory']); + $this->_set_routing(); + + // Set any routing overrides that may exist in the main index file + if (is_array($routing)) + { + empty($routing['controller']) OR $this->set_class($routing['controller']); + empty($routing['function']) OR $this->set_method($routing['function']); + } + + log_message('info', 'Router Class Initialized'); } // -------------------------------------------------------------------- /** - * Set the route mapping + * Set route mapping * - * This function determines what should be served based on the URI request, + * Determines what should be served based on the URI request, * as well as any "routes" that have been set in the routing config file. * - * @access private * @return void */ - function _set_routing() + protected function _set_routing() { - // Are query strings enabled in the config file? Normally CI doesn't utilize query strings - // since URI segments are more search-engine friendly, but they can optionally be used. - // If this feature is enabled, we will gather the directory/class/method a little differently - $segments = array(); - if ($this->config->item('enable_query_strings') === TRUE AND isset($_GET[$this->config->item('controller_trigger')])) + // Load the routes.php file. It would be great if we could + // skip this for enable_query_strings = TRUE, but then + // default_controller would be empty ... + if (file_exists(APPPATH.'config/routes.php')) { - if (isset($_GET[$this->config->item('directory_trigger')])) - { - $this->set_directory(trim($this->uri->_filter_uri($_GET[$this->config->item('directory_trigger')]))); - $segments[] = $this->fetch_directory(); - } - - if (isset($_GET[$this->config->item('controller_trigger')])) - { - $this->set_class(trim($this->uri->_filter_uri($_GET[$this->config->item('controller_trigger')]))); - $segments[] = $this->fetch_class(); - } - - if (isset($_GET[$this->config->item('function_trigger')])) - { - $this->set_method(trim($this->uri->_filter_uri($_GET[$this->config->item('function_trigger')]))); - $segments[] = $this->fetch_method(); - } + include(APPPATH.'config/routes.php'); } - // Load the routes.php file. - if (defined('ENVIRONMENT') AND is_file(APPPATH.'config/'.ENVIRONMENT.'/routes.php')) + if (file_exists(APPPATH.'config/'.ENVIRONMENT.'/routes.php')) { include(APPPATH.'config/'.ENVIRONMENT.'/routes.php'); } - elseif (is_file(APPPATH.'config/routes.php')) - { - include(APPPATH.'config/routes.php'); - } - - $this->routes = ( ! isset($route) OR ! is_array($route)) ? array() : $route; - unset($route); - - // Set the default controller so we can display it in the event - // the URI doesn't correlated to a valid controller. - $this->default_controller = ( ! isset($this->routes['default_controller']) OR $this->routes['default_controller'] == '') ? FALSE : strtolower($this->routes['default_controller']); - // Were there any query string segments? If so, we'll validate them and bail out since we're done. - if (count($segments) > 0) + // Validate & get reserved routes + if (isset($route) && is_array($route)) { - return $this->_validate_request($segments); + isset($route['default_controller']) && $this->default_controller = $route['default_controller']; + isset($route['translate_uri_dashes']) && $this->translate_uri_dashes = $route['translate_uri_dashes']; + unset($route['default_controller'], $route['translate_uri_dashes']); + $this->routes = $route; } - // Fetch the complete URI string - $this->uri->_fetch_uri_string(); - - // Is there a URI string? If not, the default controller specified in the "routes" file will be shown. - if ($this->uri->uri_string == '') + // Are query strings enabled in the config file? Normally CI doesn't utilize query strings + // since URI segments are more search-engine friendly, but they can optionally be used. + // If this feature is enabled, we will gather the directory/class/method a little differently + if ($this->enable_query_strings) { - return $this->_set_default_controller(); - } - - // Do we need to remove the URL suffix? - $this->uri->_remove_url_suffix(); + // If the directory is set at this time, it means an override exists, so skip the checks + if ( ! isset($this->directory)) + { + $_d = $this->config->item('directory_trigger'); + $_d = isset($_GET[$_d]) ? trim($_GET[$_d], " \t\n\r\0\x0B/") : ''; - // Compile the segments into an array - $this->uri->_explode_segments(); + if ($_d !== '') + { + $this->uri->filter_uri($_d); + $this->set_directory($_d); + } + } - // Parse any custom routing that may exist - $this->_parse_routes(); + $_c = trim($this->config->item('controller_trigger')); + if ( ! empty($_GET[$_c])) + { + $this->uri->filter_uri($_GET[$_c]); + $this->set_class($_GET[$_c]); - // Re-index the segment array so that it starts with 1 rather than 0 - $this->uri->_reindex_segments(); - } + $_f = trim($this->config->item('function_trigger')); + if ( ! empty($_GET[$_f])) + { + $this->uri->filter_uri($_GET[$_f]); + $this->set_method($_GET[$_f]); + } - // -------------------------------------------------------------------- + $this->uri->rsegments = array( + 1 => $this->class, + 2 => $this->method + ); + } + else + { + $this->_set_default_controller(); + } - /** - * Set the default controller - * - * @access private - * @return void - */ - function _set_default_controller() - { - if ($this->default_controller === FALSE) - { - show_error("Unable to determine what should be displayed. A default route has not been specified in the routing file."); + // Routing rules don't apply to query strings and we don't need to detect + // directories, so we're done here + return; } - // Is the method being specified? - if (strpos($this->default_controller, '/') !== FALSE) - { - $x = explode('/', $this->default_controller); - $this->set_class($x[0]); - $this->set_method($x[1]); - $this->_set_request($x); + // Is there anything to parse? + if ($this->uri->uri_string !== '') + { + $this->_parse_routes(); } else { - $this->set_class($this->default_controller); - $this->set_method('index'); - $this->_set_request(array($this->default_controller, 'index')); + $this->_set_default_controller(); } - - // re-index the routed segments array so it starts with 1 rather than 0 - $this->uri->_reindex_segments(); - - log_message('debug', "No URI present. Default controller set."); } // -------------------------------------------------------------------- /** - * Set the Route + * Set request route * - * This function takes an array of URI segments as - * input, and sets the current class/method + * Takes an array of URI segments as input and sets the class/method + * to be called. * - * @access private - * @param array - * @param bool + * @used-by CI_Router::_parse_routes() + * @param array $segments URI segments * @return void */ - function _set_request($segments = array()) + protected function _set_request($segments = array()) { $segments = $this->_validate_request($segments); + // If we don't have any segments left - try the default controller; + // WARNING: Directories get shifted out of the segments array! + if (empty($segments)) + { + $this->_set_default_controller(); + return; + } - if (count($segments) == 0) + if ($this->translate_uri_dashes === TRUE) { - return $this->_set_default_controller(); + $segments[0] = str_replace('-', '_', $segments[0]); + if (isset($segments[1])) + { + $segments[1] = str_replace('-', '_', $segments[1]); + } } $this->set_class($segments[0]); - if (isset($segments[1])) { - // A standard method request $this->set_method($segments[1]); } else { - // This lets the "routed" segment array identify that the default - // index method is being used. $segments[1] = 'index'; } - // Update our "routed" segment array to contain the segments. - // Note: If there is no custom routing, this array will be - // identical to $this->uri->segments + array_unshift($segments, NULL); + unset($segments[0]); $this->uri->rsegments = $segments; } // -------------------------------------------------------------------- /** - * Validates the supplied segments. Attempts to determine the path to - * the controller. + * Set default controller * - * @access private - * @param array - * @return array + * @return void */ - function _validate_request($segments) + protected function _set_default_controller() { - if (count($segments) == 0) + if (empty($this->default_controller)) { - return $segments; + show_error('Unable to determine what should be displayed. A default route has not been specified in the routing file.'); } - // Does the requested controller exist in the root folder? - if (file_exists(APPPATH.'controllers/'.$segments[0].'.php')) + // Is the method being specified? + if (sscanf($this->default_controller, '%[^/]/%s', $class, $method) !== 2) { - return $segments; + $method = 'index'; } - // Is the controller in a sub-folder? - if (is_dir(APPPATH.'controllers/'.$segments[0])) + if ( ! file_exists(APPPATH.'controllers/'.$this->directory.ucfirst($class).'.php')) { - // Set the directory and remove it from the segment array - $this->set_directory($segments[0]); - $segments = array_slice($segments, 1); + // This will trigger 404 later + return; + } - if (count($segments) > 0) - { - // Does the requested controller exist in the sub-folder? - if ( ! file_exists(APPPATH.'controllers/'.$this->fetch_directory().$segments[0].'.php')) - { - if ( ! empty($this->routes['404_override'])) - { - $x = explode('/', $this->routes['404_override']); - - $this->set_directory(''); - $this->set_class($x[0]); - $this->set_method(isset($x[1]) ? $x[1] : 'index'); - - return $x; - } - else - { - show_404($this->fetch_directory().$segments[0]); - } - } - } - else - { - // Is the method being specified in the route? - if (strpos($this->default_controller, '/') !== FALSE) - { - $x = explode('/', $this->default_controller); + $this->set_class($class); + $this->set_method($method); - $this->set_class($x[0]); - $this->set_method($x[1]); - } - else - { - $this->set_class($this->default_controller); - $this->set_method('index'); - } + // Assign routed segments, index starting from 1 + $this->uri->rsegments = array( + 1 => $class, + 2 => $method + ); - // Does the default controller exist in the sub-folder? - if ( ! file_exists(APPPATH.'controllers/'.$this->fetch_directory().$this->default_controller.'.php')) - { - $this->directory = ''; - return array(); - } - - } + log_message('debug', 'No URI present. Default controller set.'); + } - return $segments; - } + // -------------------------------------------------------------------- + /** + * Validate request + * + * Attempts validate the URI request and determine the controller path. + * + * @used-by CI_Router::_set_request() + * @param array $segments URI segments + * @return mixed URI segments + */ + protected function _validate_request($segments) + { + $c = count($segments); + $directory_override = isset($this->directory); - // If we've gotten this far it means that the URI does not correlate to a valid - // controller class. We will now see if there is an override - if ( ! empty($this->routes['404_override'])) + // Loop through our segments and return as soon as a controller + // is found or when such a directory doesn't exist + while ($c-- > 0) { - $x = explode('/', $this->routes['404_override']); + $test = $this->directory + .ucfirst($this->translate_uri_dashes === TRUE ? str_replace('-', '_', $segments[0]) : $segments[0]); - $this->set_class($x[0]); - $this->set_method(isset($x[1]) ? $x[1] : 'index'); + if ( ! file_exists(APPPATH.'controllers/'.$test.'.php') + && $directory_override === FALSE + && is_dir(APPPATH.'controllers/'.$this->directory.$segments[0]) + ) + { + $this->set_directory(array_shift($segments), TRUE); + continue; + } - return $x; + return $segments; } - - // Nothing else to do at this point but show a 404 - show_404($segments[0]); + // This means that all segments were actually directories + return $segments; } // -------------------------------------------------------------------- /** - * Parse Routes + * Parse Routes * - * This function matches any routes that may exist in - * the config/routes.php file against the URI to - * determine if the class/method need to be remapped. + * Matches any routes that may exist in the config/routes.php file + * against the URI to determine if the class/method need to be remapped. * - * @access private * @return void */ - function _parse_routes() + protected function _parse_routes() { // Turn the segment array into a URI string $uri = implode('/', $this->uri->segments); - // Is there a literal match? If so we're done - if (isset($this->routes[$uri])) - { - return $this->_set_request(explode('/', $this->routes[$uri])); - } + // Get HTTP verb + $http_verb = isset($_SERVER['REQUEST_METHOD']) ? strtolower($_SERVER['REQUEST_METHOD']) : 'cli'; - // Loop through the route array looking for wild-cards + // Loop through the route array looking for wildcards foreach ($this->routes as $key => $val) { - // Convert wild-cards to RegEx - $key = str_replace(':any', '.+', str_replace(':num', '[0-9]+', $key)); + // Check if route format is using HTTP verbs + if (is_array($val)) + { + $val = array_change_key_case($val, CASE_LOWER); + if (isset($val[$http_verb])) + { + $val = $val[$http_verb]; + } + else + { + continue; + } + } + + // Convert wildcards to RegEx + $key = str_replace(array(':any', ':num'), array('[^/]+', '[0-9]+'), $key); // Does the RegEx match? - if (preg_match('#^'.$key.'$#', $uri)) + if (preg_match('#^'.$key.'$#', $uri, $matches)) { - // Do we have a back-reference? - if (strpos($val, '$') !== FALSE AND strpos($key, '(') !== FALSE) + // Are we using callbacks to process back-references? + if ( ! is_string($val) && is_callable($val)) + { + // Remove the original string from the matches array. + array_shift($matches); + + // Execute the callback using the values in matches as its parameters. + $val = call_user_func_array($val, $matches); + } + // Are we using the default routing method for back-references? + elseif (strpos($val, '$') !== FALSE && strpos($key, '(') !== FALSE) { $val = preg_replace('#^'.$key.'$#', $val, $uri); } - return $this->_set_request(explode('/', $val)); + $this->_set_request(explode('/', $val)); + return; } } // If we got this far it means we didn't encounter a // matching route so we'll set the site default route - $this->_set_request($this->uri->segments); + $this->_set_request(array_values($this->uri->segments)); } // -------------------------------------------------------------------- /** - * Set the class name + * Set class name * - * @access public - * @param string + * @param string $class Class name * @return void */ - function set_class($class) + public function set_class($class) { $this->class = str_replace(array('/', '.'), '', $class); } @@ -414,10 +441,10 @@ class CI_Router { /** * Fetch the current class * - * @access public + * @deprecated 3.0.0 Read the 'class' property instead * @return string */ - function fetch_class() + public function fetch_class() { return $this->class; } @@ -425,13 +452,12 @@ class CI_Router { // -------------------------------------------------------------------- /** - * Set the method name + * Set method name * - * @access public - * @param string + * @param string $method Method name * @return void */ - function set_method($method) + public function set_method($method) { $this->method = $method; } @@ -439,84 +465,51 @@ class CI_Router { // -------------------------------------------------------------------- /** - * Fetch the current method + * Fetch the current method * - * @access public + * @deprecated 3.0.0 Read the 'method' property instead * @return string */ - function fetch_method() + public function fetch_method() { - if ($this->method == $this->fetch_class()) - { - return 'index'; - } - return $this->method; } // -------------------------------------------------------------------- /** - * Set the directory name + * Set directory name * - * @access public - * @param string + * @param string $dir Directory name + * @param bool $append Whether we're appending rather than setting the full value * @return void */ - function set_directory($dir) + public function set_directory($dir, $append = FALSE) { - $this->directory = str_replace(array('/', '.'), '', $dir).'/'; + if ($append !== TRUE OR empty($this->directory)) + { + $this->directory = str_replace('.', '', trim($dir, '/')).'/'; + } + else + { + $this->directory .= str_replace('.', '', trim($dir, '/')).'/'; + } } // -------------------------------------------------------------------- /** - * Fetch the sub-directory (if any) that contains the requested controller class + * Fetch directory + * + * Feches the sub-directory (if any) that contains the requested + * controller class. * - * @access public + * @deprecated 3.0.0 Read the 'directory' property instead * @return string */ - function fetch_directory() + public function fetch_directory() { return $this->directory; } - // -------------------------------------------------------------------- - - /** - * Set the controller overrides - * - * @access public - * @param array - * @return null - */ - function _set_overrides($routing) - { - if ( ! is_array($routing)) - { - return; - } - - if (isset($routing['directory'])) - { - $this->set_directory($routing['directory']); - } - - if (isset($routing['controller']) AND $routing['controller'] != '') - { - $this->set_class($routing['controller']); - } - - if (isset($routing['function'])) - { - $routing['function'] = ($routing['function'] == '') ? 'index' : $routing['function']; - $this->set_method($routing['function']); - } - } - - } -// END Router Class - -/* End of file Router.php */ -/* Location: ./system/core/Router.php */
\ No newline at end of file diff --git a/system/core/Security.php b/system/core/Security.php index efa2df922..082ffa96b 100644 --- a/system/core/Security.php +++ b/system/core/Security.php @@ -1,19 +1,41 @@ -<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); +<?php /** * CodeIgniter * - * An open source application development framework for PHP 5.1.6 or newer + * An open source application development framework for PHP * - * @package CodeIgniter - * @author ExpressionEngine Dev Team - * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. - * @license http://codeigniter.com/user_guide/license.html - * @link http://codeigniter.com - * @since Version 1.0 + * This content is released under the MIT License (MIT) + * + * Copyright (c) 2014 - 2017, British Columbia Institute of Technology + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @package CodeIgniter + * @author EllisLab Dev Team + * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/) + * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) + * @license http://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com + * @since Version 1.0.0 * @filesource */ - -// ------------------------------------------------------------------------ +defined('BASEPATH') OR exit('No direct script access allowed'); /** * Security Class @@ -21,119 +43,165 @@ * @package CodeIgniter * @subpackage Libraries * @category Security - * @author ExpressionEngine Dev Team - * @link http://codeigniter.com/user_guide/libraries/security.html + * @author EllisLab Dev Team + * @link https://codeigniter.com/user_guide/libraries/security.html */ class CI_Security { /** - * Random Hash for protecting URLs + * List of sanitize filename strings * - * @var string - * @access protected + * @var array */ - protected $_xss_hash = ''; + public $filename_bad_chars = array( + '../', '<!--', '-->', '<', '>', + "'", '"', '&', '$', '#', + '{', '}', '[', ']', '=', + ';', '?', '%20', '%22', + '%3c', // < + '%253c', // < + '%3e', // > + '%0e', // > + '%28', // ( + '%29', // ) + '%2528', // ( + '%26', // & + '%24', // $ + '%3f', // ? + '%3b', // ; + '%3d' // = + ); + /** - * Random Hash for Cross Site Request Forgery Protection Cookie + * Character set + * + * Will be overridden by the constructor. * - * @var string - * @access protected + * @var string */ - protected $_csrf_hash = ''; + public $charset = 'UTF-8'; + /** - * Expiration time for Cross Site Request Forgery Protection Cookie - * Defaults to two hours (in seconds) + * XSS Hash + * + * Random Hash for protecting URLs. * - * @var int - * @access protected + * @var string */ - protected $_csrf_expire = 7200; + protected $_xss_hash; + /** - * Token name for Cross Site Request Forgery Protection Cookie + * CSRF Hash + * + * Random hash for Cross Site Request Forgery protection cookie * - * @var string - * @access protected + * @var string */ - protected $_csrf_token_name = 'ci_csrf_token'; + protected $_csrf_hash; + /** - * Cookie name for Cross Site Request Forgery Protection Cookie + * CSRF Expire time + * + * Expiration time for Cross Site Request Forgery protection cookie. + * Defaults to two hours (in seconds). * - * @var string - * @access protected + * @var int */ - protected $_csrf_cookie_name = 'ci_csrf_token'; + protected $_csrf_expire = 7200; + + /** + * CSRF Token name + * + * Token name for Cross Site Request Forgery protection cookie. + * + * @var string + */ + protected $_csrf_token_name = 'ci_csrf_token'; + + /** + * CSRF Cookie name + * + * Cookie name for Cross Site Request Forgery protection cookie. + * + * @var string + */ + protected $_csrf_cookie_name = 'ci_csrf_token'; + /** * List of never allowed strings * - * @var array - * @access protected + * @var array */ - protected $_never_allowed_str = array( - 'document.cookie' => '[removed]', - 'document.write' => '[removed]', - '.parentNode' => '[removed]', - '.innerHTML' => '[removed]', - 'window.location' => '[removed]', - '-moz-binding' => '[removed]', - '<!--' => '<!--', - '-->' => '-->', - '<![CDATA[' => '<![CDATA[', - '<comment>' => '<comment>' + protected $_never_allowed_str = array( + 'document.cookie' => '[removed]', + 'document.write' => '[removed]', + '.parentNode' => '[removed]', + '.innerHTML' => '[removed]', + '-moz-binding' => '[removed]', + '<!--' => '<!--', + '-->' => '-->', + '<![CDATA[' => '<![CDATA[', + '<comment>' => '<comment>', + '<%' => '<%' ); - /* never allowed, regex replacement */ /** - * List of never allowed regex replacement + * List of never allowed regex replacements * - * @var array - * @access protected + * @var array */ protected $_never_allowed_regex = array( 'javascript\s*:', + '(document|(document\.)?window)\.(location|on\w*)', 'expression\s*(\(|&\#40;)', // CSS and IE 'vbscript\s*:', // IE, surprise! - 'Redirect\s+302', + 'wscript\s*:', // IE + 'jscript\s*:', // IE + 'vbs\s*:', // IE + 'Redirect\s+30\d', "([\"'])?data\s*:[^\\1]*?base64[^\\1]*?,[^\\1]*?\\1?" ); /** - * Constructor + * Class constructor * * @return void */ public function __construct() { // Is CSRF protection enabled? - if (config_item('csrf_protection') === TRUE) + if (config_item('csrf_protection')) { // CSRF config foreach (array('csrf_expire', 'csrf_token_name', 'csrf_cookie_name') as $key) { - if (FALSE !== ($val = config_item($key))) + if (NULL !== ($val = config_item($key))) { $this->{'_'.$key} = $val; } } // Append application specific cookie prefix - if (config_item('cookie_prefix')) + if ($cookie_prefix = config_item('cookie_prefix')) { - $this->_csrf_cookie_name = config_item('cookie_prefix').$this->_csrf_cookie_name; + $this->_csrf_cookie_name = $cookie_prefix.$this->_csrf_cookie_name; } // Set the CSRF hash $this->_csrf_set_hash(); } - log_message('debug', "Security Class Initialized"); + $this->charset = strtoupper(config_item('charset')); + + log_message('info', 'Security Class Initialized'); } // -------------------------------------------------------------------- /** - * Verify Cross Site Request Forgery Protection + * CSRF Verify * - * @return object + * @return CI_Security */ public function csrf_verify() { @@ -143,52 +211,74 @@ class CI_Security { return $this->csrf_set_cookie(); } - // Do the tokens exist in both the _POST and _COOKIE arrays? - if ( ! isset($_POST[$this->_csrf_token_name], $_COOKIE[$this->_csrf_cookie_name])) + // Check if URI has been whitelisted from CSRF checks + if ($exclude_uris = config_item('csrf_exclude_uris')) { - $this->csrf_show_error(); + $uri = load_class('URI', 'core'); + foreach ($exclude_uris as $excluded) + { + if (preg_match('#^'.$excluded.'$#i'.(UTF8_ENABLED ? 'u' : ''), $uri->uri_string())) + { + return $this; + } + } } - // Do the tokens match? - if ($_POST[$this->_csrf_token_name] != $_COOKIE[$this->_csrf_cookie_name]) - { - $this->csrf_show_error(); - } + // Check CSRF token validity, but don't error on mismatch just yet - we'll want to regenerate + $valid = isset($_POST[$this->_csrf_token_name], $_COOKIE[$this->_csrf_cookie_name]) + && hash_equals($_POST[$this->_csrf_token_name], $_COOKIE[$this->_csrf_cookie_name]); - // We kill this since we're done and we don't want to - // polute the _POST array + // We kill this since we're done and we don't want to pollute the _POST array unset($_POST[$this->_csrf_token_name]); - // Nothing should last forever - unset($_COOKIE[$this->_csrf_cookie_name]); + // Regenerate on every submission? + if (config_item('csrf_regenerate')) + { + // Nothing should last forever + unset($_COOKIE[$this->_csrf_cookie_name]); + $this->_csrf_hash = NULL; + } + $this->_csrf_set_hash(); $this->csrf_set_cookie(); - log_message('debug', 'CSRF token verified'); + if ($valid !== TRUE) + { + $this->csrf_show_error(); + } + log_message('info', 'CSRF token verified'); return $this; } // -------------------------------------------------------------------- /** - * Set Cross Site Request Forgery Protection Cookie + * CSRF Set Cookie * - * @return object + * @codeCoverageIgnore + * @return CI_Security */ public function csrf_set_cookie() { $expire = time() + $this->_csrf_expire; - $secure_cookie = (config_item('cookie_secure') === TRUE) ? 1 : 0; + $secure_cookie = (bool) config_item('cookie_secure'); - if ($secure_cookie && (empty($_SERVER['HTTPS']) OR strtolower($_SERVER['HTTPS']) === 'off')) + if ($secure_cookie && ! is_https()) { return FALSE; } - setcookie($this->_csrf_cookie_name, $this->_csrf_hash, $expire, config_item('cookie_path'), config_item('cookie_domain'), $secure_cookie); - - log_message('debug', "CRSF cookie Set"); + setcookie( + $this->_csrf_cookie_name, + $this->_csrf_hash, + $expire, + config_item('cookie_path'), + config_item('cookie_domain'), + $secure_cookie, + config_item('cookie_httponly') + ); + log_message('info', 'CSRF cookie sent'); return $this; } @@ -202,7 +292,7 @@ class CI_Security { */ public function csrf_show_error() { - show_error('The action you have requested is not allowed.'); + show_error('The action you have requested is not allowed.', 403); } // -------------------------------------------------------------------- @@ -210,9 +300,8 @@ class CI_Security { /** * Get CSRF Hash * - * Getter Method - * - * @return string self::_csrf_hash + * @see CI_Security::$_csrf_hash + * @return string CSRF hash */ public function get_csrf_hash() { @@ -224,9 +313,8 @@ class CI_Security { /** * Get CSRF Token Name * - * Getter Method - * - * @return string self::csrf_token_name + * @see CI_Security::$_csrf_token_name + * @return string CSRF token name */ public function get_csrf_token_name() { @@ -239,52 +327,44 @@ class CI_Security { * XSS Clean * * Sanitizes data so that Cross Site Scripting Hacks can be - * prevented. This function does a fair amount of work but + * prevented. This method does a fair amount of work but * it is extremely thorough, designed to prevent even the * most obscure XSS attempts. Nothing is ever 100% foolproof, * of course, but I haven't been able to get anything passed * the filter. * - * Note: This function should only be used to deal with data - * upon submission. It's not something that should - * be used for general runtime processing. + * Note: Should only be used to deal with data upon submission. + * It's not something that should be used for general + * runtime processing. * - * This function was based in part on some code and ideas I - * got from Bitflux: http://channel.bitflux.ch/wiki/XSS_Prevention + * @link http://channel.bitflux.ch/wiki/XSS_Prevention + * Based in part on some code and ideas from Bitflux. * - * To help develop this script I used this great list of - * vulnerabilities along with a few other hacks I've - * harvested from examining vulnerabilities in other programs: - * http://ha.ckers.org/xss.html + * @link http://ha.ckers.org/xss.html + * To help develop this script I used this great list of + * vulnerabilities along with a few other hacks I've + * harvested from examining vulnerabilities in other programs. * - * @param mixed string or array - * @param bool + * @param string|string[] $str Input data + * @param bool $is_image Whether the input is an image * @return string */ public function xss_clean($str, $is_image = FALSE) { - /* - * Is the string an array? - * - */ + // Is the string an array? if (is_array($str)) { - while (list($key) = each($str)) + foreach ($str as $key => &$value) { - $str[$key] = $this->xss_clean($str[$key]); + $str[$key] = $this->xss_clean($value); } return $str; } - /* - * Remove Invisible Characters - */ + // Remove Invisible Characters $str = remove_invisible_characters($str); - // Validate Entities in URLs - $str = $this->_validate_entities($str); - /* * URL Decode * @@ -293,9 +373,18 @@ class CI_Security { * <a href="http://%77%77%77%2E%67%6F%6F%67%6C%65%2E%63%6F%6D">Google</a> * * Note: Use rawurldecode() so it does not remove plus signs - * */ - $str = rawurldecode($str); + if (stripos($str, '%') !== false) + { + do + { + $oldstr = $str; + $str = rawurldecode($str); + $str = preg_replace_callback('#%(?:\s*[0-9a-f]){2,}#i', array($this, '_urldecodespaces'), $str); + } + while ($oldstr !== $str); + unset($oldstr); + } /* * Convert character entities to ASCII @@ -303,16 +392,11 @@ class CI_Security { * This permits our tests below to work reliably. * We only convert entities that are within tags since * these are the ones that will pose security problems. - * */ + $str = preg_replace_callback("/[^a-z0-9>]+[a-z0-9]+=([\'\"]).*?\\1/si", array($this, '_convert_attribute'), $str); + $str = preg_replace_callback('/<\w+.*/si', array($this, '_decode_entity'), $str); - $str = preg_replace_callback("/[a-z]+=([\'\"]).*?\\1/si", array($this, '_convert_attribute'), $str); - - $str = preg_replace_callback("/<\w+.*?(?=>|<|$)/si", array($this, '_decode_entity'), $str); - - /* - * Remove Invisible Characters Again! - */ + // Remove Invisible Characters Again! $str = remove_invisible_characters($str); /* @@ -323,15 +407,9 @@ class CI_Security { * NOTE: preg_replace was found to be amazingly slow here on * large blocks of data, so we use str_replace. */ + $str = str_replace("\t", ' ', $str); - if (strpos($str, "\t") !== FALSE) - { - $str = str_replace("\t", ' ', $str); - } - - /* - * Capture converted string for later comparison - */ + // Capture converted string for later comparison $converted_string = $str; // Remove Strings that are never allowed @@ -351,11 +429,11 @@ class CI_Security { // Images have a tendency to have the PHP short opening and // closing tags every so often so we skip those and only // do the long opening tags. - $str = preg_replace('/<\?(php)/i', "<?\\1", $str); + $str = preg_replace('/<\?(php)/i', '<?\\1', $str); } else { - $str = str_replace(array('<?', '?'.'>'), array('<?', '?>'), $str); + $str = str_replace(array('<?', '?'.'>'), array('<?', '?>'), $str); } /* @@ -365,56 +443,54 @@ class CI_Security { * These words are compacted back to their correct state. */ $words = array( - 'javascript', 'expression', 'vbscript', 'script', 'base64', - 'applet', 'alert', 'document', 'write', 'cookie', 'window' + 'javascript', 'expression', 'vbscript', 'jscript', 'wscript', + 'vbs', 'script', 'base64', 'applet', 'alert', 'document', + 'write', 'cookie', 'window', 'confirm', 'prompt', 'eval' ); foreach ($words as $word) { - $temp = ''; - - for ($i = 0, $wordlen = strlen($word); $i < $wordlen; $i++) - { - $temp .= substr($word, $i, 1)."\s*"; - } + $word = implode('\s*', str_split($word)).'\s*'; // We only want to do this when it is followed by a non-word character // That way valid stuff like "dealer to" does not become "dealerto" - $str = preg_replace_callback('#('.substr($temp, 0, -3).')(\W)#is', array($this, '_compact_exploded_words'), $str); + $str = preg_replace_callback('#('.substr($word, 0, -3).')(\W)#is', array($this, '_compact_exploded_words'), $str); } /* * Remove disallowed Javascript in links or img tags - * We used to do some version comparisons and use of stripos for PHP5, + * We used to do some version comparisons and use of stripos(), * but it is dog slow compared to these simplified non-capturing * preg_match(), especially if the pattern exists in the string + * + * Note: It was reported that not only space characters, but all in + * the following pattern can be parsed as separators between a tag name + * and its attributes: [\d\s"\'`;,\/\=\(\x00\x0B\x09\x0C] + * ... however, remove_invisible_characters() above already strips the + * hex-encoded ones, so we'll skip them below. */ do { $original = $str; - if (preg_match("/<a/i", $str)) + if (preg_match('/<a/i', $str)) { - $str = preg_replace_callback("#<a\s+([^>]*?)(>|$)#si", array($this, '_js_link_removal'), $str); + $str = preg_replace_callback('#<a(?:rea)?[^a-z0-9>]+([^>]*?)(?:>|$)#si', array($this, '_js_link_removal'), $str); } - if (preg_match("/<img/i", $str)) + if (preg_match('/<img/i', $str)) { - $str = preg_replace_callback("#<img\s+([^>]*?)(\s?/?>|$)#si", array($this, '_js_img_removal'), $str); + $str = preg_replace_callback('#<img[^a-z0-9]+([^>]*?)(?:\s?/?>|$)#si', array($this, '_js_img_removal'), $str); } - if (preg_match("/script/i", $str) OR preg_match("/xss/i", $str)) + if (preg_match('/script|xss/i', $str)) { - $str = preg_replace("#<(/*)(script|xss)(.*?)\>#si", '[removed]', $str); + $str = preg_replace('#</*(?:script|xss).*?>#si', '[removed]', $str); } } - while($original != $str); - + while ($original !== $str); unset($original); - // Remove evil attributes such as style, onclick and xmlns - $str = $this->_remove_evil_attributes($str, $is_image); - /* * Sanitize naughty HTML elements * @@ -424,23 +500,47 @@ class CI_Security { * So this: <blink> * Becomes: <blink> */ - $naughty = 'alert|applet|audio|basefont|base|behavior|bgsound|blink|body|embed|expression|form|frameset|frame|head|html|ilayer|iframe|input|isindex|layer|link|meta|object|plaintext|style|script|textarea|title|video|xml|xss'; - $str = preg_replace_callback('#<(/*\s*)('.$naughty.')([^><]*)([><]*)#is', array($this, '_sanitize_naughty_html'), $str); + $pattern = '#' + .'<((?<slash>/*\s*)((?<tagName>[a-z0-9]+)(?=[^a-z0-9]|$)|.+)' // tag start and name, followed by a non-tag character + .'[^\s\042\047a-z0-9>/=]*' // a valid attribute character immediately after the tag would count as a separator + // optional attributes + .'(?<attributes>(?:[\s\042\047/=]*' // non-attribute characters, excluding > (tag close) for obvious reasons + .'[^\s\042\047>/=]+' // attribute characters + // optional attribute-value + .'(?:\s*=' // attribute-value separator + .'(?:[^\s\042\047=><`]+|\s*\042[^\042]*\042|\s*\047[^\047]*\047|\s*(?U:[^\s\042\047=><`]*))' // single, double or non-quoted value + .')?' // end optional attribute-value group + .')*)' // end optional attributes group + .'[^>]*)(?<closeTag>\>)?#isS'; + + // Note: It would be nice to optimize this for speed, BUT + // only matching the naughty elements here results in + // false positives and in turn - vulnerabilities! + do + { + $old_str = $str; + $str = preg_replace_callback($pattern, array($this, '_sanitize_naughty_html'), $str); + } + while ($old_str !== $str); + unset($old_str); /* * Sanitize naughty scripting elements * * Similar to above, only instead of looking for * tags it looks for PHP and JavaScript commands - * that are disallowed. Rather than removing the + * that are disallowed. Rather than removing the * code, it simply converts the parenthesis to entities * rendering the code un-executable. * * For example: eval('some code') - * Becomes: eval('some code') + * Becomes: eval('some code') */ - $str = preg_replace('#(alert|cmd|passthru|eval|exec|expression|system|fopen|fsockopen|file|file_get_contents|readfile|unlink)(\s*)\((.*?)\)#si', "\\1\\2(\\3)", $str); - + $str = preg_replace( + '#(alert|prompt|confirm|cmd|passthru|eval|exec|expression|system|fopen|fsockopen|file|file_get_contents|readfile|unlink)(\s*)\((.*?)\)#si', + '\\1\\2(\\3)', + $str + ); // Final clean up // This adds a bit of extra precaution in case @@ -456,29 +556,32 @@ class CI_Security { * string post-removal of XSS, then it fails, as there was unwanted XSS * code found and removed/changed during processing. */ - if ($is_image === TRUE) { - return ($str == $converted_string) ? TRUE: FALSE; + return ($str === $converted_string); } - log_message('debug', "XSS Filtering completed"); return $str; } // -------------------------------------------------------------------- /** - * Random Hash for protecting URLs + * XSS Hash * - * @return string + * Generates the XSS hash if needed and returns it. + * + * @see CI_Security::$_xss_hash + * @return string XSS hash */ public function xss_hash() { - if ($this->_xss_hash == '') + if ($this->_xss_hash === NULL) { - mt_srand(); - $this->_xss_hash = md5(time() + mt_rand(0, 1999999999)); + $rand = $this->get_random_bytes(16); + $this->_xss_hash = ($rand === FALSE) + ? md5(uniqid(mt_rand(), TRUE)) + : bin2hex($rand); } return $this->_xss_hash; @@ -487,76 +590,158 @@ class CI_Security { // -------------------------------------------------------------------- /** + * Get random bytes + * + * @param int $length Output length + * @return string + */ + public function get_random_bytes($length) + { + if (empty($length) OR ! ctype_digit((string) $length)) + { + return FALSE; + } + + if (function_exists('random_bytes')) + { + try + { + // The cast is required to avoid TypeError + return random_bytes((int) $length); + } + catch (Exception $e) + { + // If random_bytes() can't do the job, we can't either ... + // There's no point in using fallbacks. + log_message('error', $e->getMessage()); + return FALSE; + } + } + + // Unfortunately, none of the following PRNGs is guaranteed to exist ... + if (defined('MCRYPT_DEV_URANDOM') && ($output = mcrypt_create_iv($length, MCRYPT_DEV_URANDOM)) !== FALSE) + { + return $output; + } + + + if (is_readable('/dev/urandom') && ($fp = fopen('/dev/urandom', 'rb')) !== FALSE) + { + // Try not to waste entropy ... + is_php('5.4') && stream_set_chunk_size($fp, $length); + $output = fread($fp, $length); + fclose($fp); + if ($output !== FALSE) + { + return $output; + } + } + + if (function_exists('openssl_random_pseudo_bytes')) + { + return openssl_random_pseudo_bytes($length); + } + + return FALSE; + } + + // -------------------------------------------------------------------- + + /** * HTML Entities Decode * - * This function is a replacement for html_entity_decode() + * A replacement for html_entity_decode() * * The reason we are not using html_entity_decode() by itself is because * while it is not technically correct to leave out the semicolon * at the end of an entity most browsers will still interpret the entity - * correctly. html_entity_decode() does not convert entities without + * correctly. html_entity_decode() does not convert entities without * semicolons, so we are left with our own little solution here. Bummer. * - * @param string - * @param string + * @link http://php.net/html-entity-decode + * + * @param string $str Input + * @param string $charset Character set * @return string */ - public function entity_decode($str, $charset='UTF-8') + public function entity_decode($str, $charset = NULL) { - if (stristr($str, '&') === FALSE) + if (strpos($str, '&') === FALSE) { return $str; } - $str = html_entity_decode($str, ENT_COMPAT, $charset); - $str = preg_replace('~&#x(0*[0-9a-f]{2,5})~ei', 'chr(hexdec("\\1"))', $str); - return preg_replace('~&#([0-9]{2,4})~e', 'chr(\\1)', $str); + static $_entities; + + isset($charset) OR $charset = $this->charset; + $flag = is_php('5.4') + ? ENT_COMPAT | ENT_HTML5 + : ENT_COMPAT; + + if ( ! isset($_entities)) + { + $_entities = array_map('strtolower', get_html_translation_table(HTML_ENTITIES, $flag, $charset)); + + // If we're not on PHP 5.4+, add the possibly dangerous HTML 5 + // entities to the array manually + if ($flag === ENT_COMPAT) + { + $_entities[':'] = ':'; + $_entities['('] = '('; + $_entities[')'] = ')'; + $_entities["\n"] = '
'; + $_entities["\t"] = '	'; + } + } + + do + { + $str_compare = $str; + + // Decode standard entities, avoiding false positives + if (preg_match_all('/&[a-z]{2,}(?![a-z;])/i', $str, $matches)) + { + $replace = array(); + $matches = array_unique(array_map('strtolower', $matches[0])); + foreach ($matches as &$match) + { + if (($char = array_search($match.';', $_entities, TRUE)) !== FALSE) + { + $replace[$match] = $char; + } + } + + $str = str_replace(array_keys($replace), array_values($replace), $str); + } + + // Decode numeric & UTF16 two byte entities + $str = html_entity_decode( + preg_replace('/(&#(?:x0*[0-9a-f]{2,5}(?![0-9a-f;])|(?:0*\d{2,4}(?![0-9;]))))/iS', '$1;', $str), + $flag, + $charset + ); + + if ($flag === ENT_COMPAT) + { + $str = str_replace(array_values($_entities), array_keys($_entities), $str); + } + } + while ($str_compare !== $str); + return $str; } // -------------------------------------------------------------------- /** - * Filename Security + * Sanitize Filename * - * @param string - * @param bool + * @param string $str Input file name + * @param bool $relative_path Whether to preserve paths * @return string */ public function sanitize_filename($str, $relative_path = FALSE) { - $bad = array( - "../", - "<!--", - "-->", - "<", - ">", - "'", - '"', - '&', - '$', - '#', - '{', - '}', - '[', - ']', - '=', - ';', - '?', - "%20", - "%22", - "%3c", // < - "%253c", // < - "%3e", // > - "%0e", // > - "%28", // ( - "%29", // ) - "%2528", // ( - "%26", // & - "%24", // $ - "%3f", // ? - "%3b", // ; - "%3d" // = - ); + $bad = $this->filename_bad_chars; if ( ! $relative_path) { @@ -565,7 +750,53 @@ class CI_Security { } $str = remove_invisible_characters($str, FALSE); - return stripslashes(str_replace($bad, '', $str)); + + do + { + $old = $str; + $str = str_replace($bad, '', $str); + } + while ($old !== $str); + + return stripslashes($str); + } + + // ---------------------------------------------------------------- + + /** + * Strip Image Tags + * + * @param string $str + * @return string + */ + public function strip_image_tags($str) + { + return preg_replace( + array( + '#<img[\s/]+.*?src\s*=\s*(["\'])([^\\1]+?)\\1.*?\>#i', + '#<img[\s/]+.*?src\s*=\s*?(([^\s"\'=<>`]+)).*?\>#i' + ), + '\\2', + $str + ); + } + + // ---------------------------------------------------------------- + + /** + * URL-decode taking spaces into account + * + * @see https://github.com/bcit-ci/CodeIgniter/issues/4877 + * @param array $matches + * @return string + */ + protected function _urldecodespaces($matches) + { + $input = $matches[0]; + $nospaces = preg_replace('#\s+#', '', $input); + return ($nospaces === $input) + ? $input + : rawurldecode($nospaces); } // ---------------------------------------------------------------- @@ -573,11 +804,12 @@ class CI_Security { /** * Compact Exploded Words * - * Callback function for xss_clean() to remove whitespace from - * things like j a v a s c r i p t + * Callback method for xss_clean() to remove whitespace from + * things like 'j a v a s c r i p t'. * - * @param type - * @return type + * @used-by CI_Security::xss_clean() + * @param array $matches + * @return string */ protected function _compact_exploded_words($matches) { @@ -586,86 +818,93 @@ class CI_Security { // -------------------------------------------------------------------- - /* - * Remove Evil HTML Attributes (like evenhandlers and style) + /** + * Sanitize Naughty HTML * - * It removes the evil attribute and either: - * - Everything up until a space - * For example, everything between the pipes: - * <a |style=document.write('hello');alert('world');| class=link> - * - Everything inside the quotes - * For example, everything between the pipes: - * <a |style="document.write('hello'); alert('world');"| class="link"> + * Callback method for xss_clean() to remove naughty HTML elements. * - * @param string $str The string to check - * @param boolean $is_image TRUE if this is an image - * @return string The string with the evil attributes removed + * @used-by CI_Security::xss_clean() + * @param array $matches + * @return string */ - protected function _remove_evil_attributes($str, $is_image) + protected function _sanitize_naughty_html($matches) { - // All javascript event handlers (e.g. onload, onclick, onmouseover), style, and xmlns - $evil_attributes = array('on\w*', 'style', 'xmlns', 'formaction'); + static $naughty_tags = array( + 'alert', 'area', 'prompt', 'confirm', 'applet', 'audio', 'basefont', 'base', 'behavior', 'bgsound', + 'blink', 'body', 'embed', 'expression', 'form', 'frameset', 'frame', 'head', 'html', 'ilayer', + 'iframe', 'input', 'button', 'select', 'isindex', 'layer', 'link', 'meta', 'keygen', 'object', + 'plaintext', 'style', 'script', 'textarea', 'title', 'math', 'video', 'svg', 'xml', 'xss' + ); - if ($is_image === TRUE) + static $evil_attributes = array( + 'on\w+', 'style', 'xmlns', 'formaction', 'form', 'xlink:href', 'FSCommand', 'seekSegmentTime' + ); + + // First, escape unclosed tags + if (empty($matches['closeTag'])) + { + return '<'.$matches[1]; + } + // Is the element that we caught naughty? If so, escape it + elseif (in_array(strtolower($matches['tagName']), $naughty_tags, TRUE)) { - /* - * Adobe Photoshop puts XML metadata into JFIF images, - * including namespacing, so we have to allow this for images. - */ - unset($evil_attributes[array_search('xmlns', $evil_attributes)]); + return '<'.$matches[1].'>'; } + // For other tags, see if their attributes are "evil" and strip those + elseif (isset($matches['attributes'])) + { + // We'll store the already fitlered attributes here + $attributes = array(); - do { - $count = 0; - $attribs = array(); + // Attribute-catching pattern + $attributes_pattern = '#' + .'(?<name>[^\s\042\047>/=]+)' // attribute characters + // optional attribute-value + .'(?:\s*=(?<value>[^\s\042\047=><`]+|\s*\042[^\042]*\042|\s*\047[^\047]*\047|\s*(?U:[^\s\042\047=><`]*)))' // attribute-value separator + .'#i'; - // find occurrences of illegal attribute strings with quotes (042 and 047 are octal quotes) - preg_match_all('/('.implode('|', $evil_attributes).')\s*=\s*(\042|\047)([^\\2]*?)(\\2)/is', $str, $matches, PREG_SET_ORDER); + // Blacklist pattern for evil attribute names + $is_evil_pattern = '#^('.implode('|', $evil_attributes).')$#i'; - foreach ($matches as $attr) + // Each iteration filters a single attribute + do { - $attribs[] = preg_quote($attr[0], '/'); - } + // Strip any non-alpha characters that may precede an attribute. + // Browsers often parse these incorrectly and that has been a + // of numerous XSS issues we've had. + $matches['attributes'] = preg_replace('#^[^a-z]+#i', '', $matches['attributes']); - // find occurrences of illegal attribute strings without quotes - preg_match_all('/('.implode('|', $evil_attributes).')\s*=\s*([^\s>]*)/is', $str, $matches, PREG_SET_ORDER); + if ( ! preg_match($attributes_pattern, $matches['attributes'], $attribute, PREG_OFFSET_CAPTURE)) + { + // No (valid) attribute found? Discard everything else inside the tag + break; + } - foreach ($matches as $attr) - { - $attribs[] = preg_quote($attr[0], '/'); - } + if ( + // Is it indeed an "evil" attribute? + preg_match($is_evil_pattern, $attribute['name'][0]) + // Or does it have an equals sign, but no value and not quoted? Strip that too! + OR (trim($attribute['value'][0]) === '') + ) + { + $attributes[] = 'xss=removed'; + } + else + { + $attributes[] = $attribute[0][0]; + } - // replace illegal attribute strings that are inside an html tag - if (count($attribs) > 0) - { - $str = preg_replace('/(<?)(\/?[^><]+?)([^A-Za-z<>\-])(.*?)('.implode('|', $attribs).')(.*?)([\s><]?)([><]*)/i', '$1$2 $4$6$7$8', $str, -1, $count); + $matches['attributes'] = substr($matches['attributes'], $attribute[0][1] + strlen($attribute[0][0])); } + while ($matches['attributes'] !== ''); - } while ($count); - - return $str; - } - - // -------------------------------------------------------------------- - - /** - * Sanitize Naughty HTML - * - * Callback function for xss_clean() to remove naughty HTML elements - * - * @param array - * @return string - */ - protected function _sanitize_naughty_html($matches) - { - // encode opening brace - $str = '<'.$matches[1].$matches[2].$matches[3]; - - // encode captured opening or closing brace to prevent recursive vectors - $str .= str_replace(array('>', '<'), array('>', '<'), - $matches[4]); + $attributes = empty($attributes) + ? '' + : ' '.implode(' ', $attributes); + return '<'.$matches['slash'].$matches['tagName'].$attributes.'>'; + } - return $str; + return $matches[0]; } // -------------------------------------------------------------------- @@ -673,12 +912,14 @@ class CI_Security { /** * JS Link Removal * - * Callback function for xss_clean() to sanitize links + * Callback method for xss_clean() to sanitize links. + * * This limits the PCRE backtracks, making it more performance friendly * and prevents PREG_BACKTRACK_LIMIT_ERROR from being triggered in - * PHP 5.2+ on link-heavy strings + * PHP 5.2+ on link-heavy strings. * - * @param array + * @used-by CI_Security::xss_clean() + * @param array $match * @return string */ protected function _js_link_removal($match) @@ -686,9 +927,9 @@ class CI_Security { return str_replace( $match[1], preg_replace( - '#href=.*?(alert\(|alert&\#40;|javascript\:|livescript\:|mocha\:|charset\=|window\.|document\.|\.cookie|<script|<xss|data\s*:)#si', + '#href=.*?(?:(?:alert|prompt|confirm)(?:\(|&\#40;)|javascript:|livescript:|mocha:|charset=|window\.|document\.|\.cookie|<script|<xss|d\s*a\s*t\s*a\s*:)#si', '', - $this->_filter_attributes(str_replace(array('<', '>'), '', $match[1])) + $this->_filter_attributes($match[1]) ), $match[0] ); @@ -699,12 +940,14 @@ class CI_Security { /** * JS Image Removal * - * Callback function for xss_clean() to sanitize image tags + * Callback method for xss_clean() to sanitize image tags. + * * This limits the PCRE backtracks, making it more performance friendly * and prevents PREG_BACKTRACK_LIMIT_ERROR from being triggered in - * PHP 5.2+ on image tag heavy strings + * PHP 5.2+ on image tag heavy strings. * - * @param array + * @used-by CI_Security::xss_clean() + * @param array $match * @return string */ protected function _js_img_removal($match) @@ -712,9 +955,9 @@ class CI_Security { return str_replace( $match[1], preg_replace( - '#src=.*?(alert\(|alert&\#40;|javascript\:|livescript\:|mocha\:|charset\=|window\.|document\.|\.cookie|<script|<xss|base64\s*,)#si', + '#src=.*?(?:(?:alert|prompt|confirm|eval)(?:\(|&\#40;)|javascript:|livescript:|mocha:|charset=|window\.|document\.|\.cookie|<script|<xss|base64\s*,)#si', '', - $this->_filter_attributes(str_replace(array('<', '>'), '', $match[1])) + $this->_filter_attributes($match[1]) ), $match[0] ); @@ -725,9 +968,8 @@ class CI_Security { /** * Attribute Conversion * - * Used as a callback for XSS Clean - * - * @param array + * @used-by CI_Security::xss_clean() + * @param array $match * @return string */ protected function _convert_attribute($match) @@ -740,20 +982,21 @@ class CI_Security { /** * Filter Attributes * - * Filters tag attributes for consistency and safety + * Filters tag attributes for consistency and safety. * - * @param string + * @used-by CI_Security::_js_img_removal() + * @used-by CI_Security::_js_link_removal() + * @param string $str * @return string */ protected function _filter_attributes($str) { $out = ''; - if (preg_match_all('#\s*[a-z\-]+\s*=\s*(\042|\047)([^\\1]*?)\\1#is', $str, $matches)) { foreach ($matches[0] as $match) { - $out .= preg_replace("#/\*.*?\*/#s", '', $match); + $out .= preg_replace('#/\*.*?\*/#s', '', $match); } } @@ -765,68 +1008,30 @@ class CI_Security { /** * HTML Entity Decode Callback * - * Used as a callback for XSS Clean - * - * @param array + * @used-by CI_Security::xss_clean() + * @param array $match * @return string */ protected function _decode_entity($match) { - return $this->entity_decode($match[0], strtoupper(config_item('charset'))); - } - - // -------------------------------------------------------------------- - - /** - * Validate URL entities - * - * Called by xss_clean() - * - * @param string - * @return string - */ - protected function _validate_entities($str) - { - /* - * Protect GET variables in URLs - */ - - // 901119URL5918AMP18930PROTECT8198 - - $str = preg_replace('|\&([a-z\_0-9\-]+)\=([a-z\_0-9\-]+)|i', $this->xss_hash()."\\1=\\2", $str); - - /* - * Validate standard character entities - * - * Add a semicolon if missing. We do this to enable - * the conversion of entities to ASCII later. - * - */ - $str = preg_replace('#(&\#?[0-9a-z]{2,})([\x00-\x20])*;?#i', "\\1;\\2", $str); + // Protect GET variables in URLs + // 901119URL5918AMP18930PROTECT8198 + $match = preg_replace('|\&([a-z\_0-9\-]+)\=([a-z\_0-9\-/]+)|i', $this->xss_hash().'\\1=\\2', $match[0]); - /* - * Validate UTF16 two byte encoding (x00) - * - * Just as above, adds a semicolon if missing. - * - */ - $str = preg_replace('#(&\#x?)([0-9A-F]+);?#i',"\\1\\2;",$str); - - /* - * Un-Protect GET variables in URLs - */ - $str = str_replace($this->xss_hash(), '&', $str); - - return $str; + // Decode, then un-protect URL GET vars + return str_replace( + $this->xss_hash(), + '&', + $this->entity_decode($match, $this->charset) + ); } - // ---------------------------------------------------------------------- + // -------------------------------------------------------------------- /** * Do Never Allowed * - * A utility function for xss_clean() - * + * @used-by CI_Security::xss_clean() * @param string * @return string */ @@ -845,31 +1050,31 @@ class CI_Security { // -------------------------------------------------------------------- /** - * Set Cross Site Request Forgery Protection Cookie + * Set CSRF Hash and Cookie * * @return string */ protected function _csrf_set_hash() { - if ($this->_csrf_hash == '') + if ($this->_csrf_hash === NULL) { - // If the cookie exists we will use it's value. + // If the cookie exists we will use its value. // We don't necessarily want to regenerate it with // each page load since a page could contain embedded // sub-pages causing this feature to fail - if (isset($_COOKIE[$this->_csrf_cookie_name]) && - preg_match('#^[0-9a-f]{32}$#iS', $_COOKIE[$this->_csrf_cookie_name]) === 1) + if (isset($_COOKIE[$this->_csrf_cookie_name]) && is_string($_COOKIE[$this->_csrf_cookie_name]) + && preg_match('#^[0-9a-f]{32}$#iS', $_COOKIE[$this->_csrf_cookie_name]) === 1) { return $this->_csrf_hash = $_COOKIE[$this->_csrf_cookie_name]; } - return $this->_csrf_hash = md5(uniqid(rand(), TRUE)); + $rand = $this->get_random_bytes(16); + $this->_csrf_hash = ($rand === FALSE) + ? md5(uniqid(mt_rand(), TRUE)) + : bin2hex($rand); } return $this->_csrf_hash; } } - -/* End of file Security.php */ -/* Location: ./system/libraries/Security.php */ diff --git a/system/core/URI.php b/system/core/URI.php index 3dacd6743..3ccdfa7b0 100644 --- a/system/core/URI.php +++ b/system/core/URI.php @@ -1,24 +1,41 @@ -<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); -/** - * MODIFIED: - * _detect_uri(): ltrim instead of trim at the end to preserve tailing slashes - */ - +<?php /** * CodeIgniter * - * An open source application development framework for PHP 5.1.6 or newer + * An open source application development framework for PHP * - * @package CodeIgniter - * @author ExpressionEngine Dev Team - * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. - * @license http://codeigniter.com/user_guide/license.html - * @link http://codeigniter.com - * @since Version 1.0 + * This content is released under the MIT License (MIT) + * + * Copyright (c) 2014 - 2017, British Columbia Institute of Technology + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @package CodeIgniter + * @author EllisLab Dev Team + * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/) + * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) + * @license http://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com + * @since Version 1.0.0 * @filesource */ - -// ------------------------------------------------------------------------ +defined('BASEPATH') OR exit('No direct script access allowed'); /** * URI Class @@ -28,382 +45,368 @@ * @package CodeIgniter * @subpackage Libraries * @category URI - * @author ExpressionEngine Dev Team - * @link http://codeigniter.com/user_guide/libraries/uri.html + * @author EllisLab Dev Team + * @link https://codeigniter.com/user_guide/libraries/uri.html */ class CI_URI { /** - * List of cached uri segments + * List of cached URI segments * - * @var array - * @access public + * @var array */ - var $keyval = array(); + public $keyval = array(); + /** - * Current uri string + * Current URI string * - * @var string - * @access public + * @var string */ - var $uri_string; + public $uri_string = ''; + /** - * List of uri segments + * List of URI segments + * + * Starts at 1 instead of 0. * - * @var array - * @access public + * @var array */ - var $segments = array(); + public $segments = array(); + /** - * Re-indexed list of uri segments - * Starts at 1 instead of 0 + * List of routed URI segments + * + * Starts at 1 instead of 0. * - * @var array - * @access public + * @var array */ - var $rsegments = array(); + public $rsegments = array(); /** - * Constructor + * Permitted URI chars * - * Simply globalizes the $RTR object. The front - * loads the Router class early on so it's not available - * normally as other classes are. + * PCRE character group allowed in URI segments * - * @access public + * @var string */ - function __construct() - { - $this->config =& load_class('Config', 'core'); - log_message('debug', "URI Class Initialized"); - } - - - // -------------------------------------------------------------------- + protected $_permitted_uri_chars; /** - * Get the URI String + * Class constructor * - * @access private - * @return string + * @return void */ - function _fetch_uri_string() + public function __construct() { - if (strtoupper($this->config->item('uri_protocol')) == 'AUTO') - { - // Is the request coming from the command line? - if (php_sapi_name() == 'cli' or defined('STDIN')) - { - $this->_set_uri_string($this->_parse_cli_args()); - return; - } + $this->config =& load_class('Config', 'core'); - // Let's try the REQUEST_URI first, this will work in most situations - if ($uri = $this->_detect_uri()) - { - $this->_set_uri_string($uri); - return; - } + // If query strings are enabled, we don't need to parse any segments. + // However, they don't make sense under CLI. + if (is_cli() OR $this->config->item('enable_query_strings') !== TRUE) + { + $this->_permitted_uri_chars = $this->config->item('permitted_uri_chars'); - // Is there a PATH_INFO variable? - // Note: some servers seem to have trouble with getenv() so we'll test it two ways - $path = (isset($_SERVER['PATH_INFO'])) ? $_SERVER['PATH_INFO'] : @getenv('PATH_INFO'); - if (trim($path, '/') != '' && $path != "/".SELF) + // If it's a CLI request, ignore the configuration + if (is_cli()) { - $this->_set_uri_string($path); - return; + $uri = $this->_parse_argv(); } - - // No PATH_INFO?... What about QUERY_STRING? - $path = (isset($_SERVER['QUERY_STRING'])) ? $_SERVER['QUERY_STRING'] : @getenv('QUERY_STRING'); - if (trim($path, '/') != '') + else { - $this->_set_uri_string($path); - return; - } + $protocol = $this->config->item('uri_protocol'); + empty($protocol) && $protocol = 'REQUEST_URI'; - // As a last ditch effort lets try using the $_GET array - if (is_array($_GET) && count($_GET) == 1 && trim(key($_GET), '/') != '') - { - $this->_set_uri_string(key($_GET)); - return; + switch ($protocol) + { + case 'AUTO': // For BC purposes only + case 'REQUEST_URI': + $uri = $this->_parse_request_uri(); + break; + case 'QUERY_STRING': + $uri = $this->_parse_query_string(); + break; + case 'PATH_INFO': + default: + $uri = isset($_SERVER[$protocol]) + ? $_SERVER[$protocol] + : $this->_parse_request_uri(); + break; + } } - // We've exhausted all our options... - $this->uri_string = ''; - return; + $this->_set_uri_string($uri); } - $uri = strtoupper($this->config->item('uri_protocol')); - - if ($uri == 'REQUEST_URI') - { - $this->_set_uri_string($this->_detect_uri()); - return; - } - elseif ($uri == 'CLI') - { - $this->_set_uri_string($this->_parse_cli_args()); - return; - } - - $path = (isset($_SERVER[$uri])) ? $_SERVER[$uri] : @getenv($uri); - $this->_set_uri_string($path); + log_message('info', 'URI Class Initialized'); } // -------------------------------------------------------------------- /** - * Set the URI String + * Set URI String * - * @access public - * @param string - * @return string + * @param string $str + * @return void */ - function _set_uri_string($str) + protected function _set_uri_string($str) { - // Filter out control characters - $str = remove_invisible_characters($str, FALSE); + // Filter out control characters and trim slashes + $this->uri_string = trim(remove_invisible_characters($str, FALSE), '/'); + + if ($this->uri_string !== '') + { + // Remove the URL suffix, if present + if (($suffix = (string) $this->config->item('url_suffix')) !== '') + { + $slen = strlen($suffix); + + if (substr($this->uri_string, -$slen) === $suffix) + { + $this->uri_string = substr($this->uri_string, 0, -$slen); + } + } - // If the URI contains only a slash we'll kill it - $this->uri_string = ($str == '/') ? '' : $str; + $this->segments[0] = NULL; + // Populate the segments array + foreach (explode('/', trim($this->uri_string, '/')) as $val) + { + $val = trim($val); + // Filter segments for security + $this->filter_uri($val); + + if ($val !== '') + { + $this->segments[] = $val; + } + } + + unset($this->segments[0]); + } } // -------------------------------------------------------------------- /** - * Detects the URI + * Parse REQUEST_URI * - * This function will detect the URI automatically and fix the query string - * if necessary. + * Will parse REQUEST_URI and automatically detect the URI from it, + * while fixing the query string if necessary. * - * @access private * @return string */ - private function _detect_uri() + protected function _parse_request_uri() { - if ( ! isset($_SERVER['REQUEST_URI']) OR ! isset($_SERVER['SCRIPT_NAME'])) + if ( ! isset($_SERVER['REQUEST_URI'], $_SERVER['SCRIPT_NAME'])) { return ''; } - $uri = $_SERVER['REQUEST_URI']; - if (strpos($uri, $_SERVER['SCRIPT_NAME']) === 0) - { - $uri = substr($uri, strlen($_SERVER['SCRIPT_NAME'])); - } - elseif (strpos($uri, dirname($_SERVER['SCRIPT_NAME'])) === 0) + // parse_url() returns false if no host is present, but the path or query string + // contains a colon followed by a number + $uri = parse_url('http://dummy'.$_SERVER['REQUEST_URI']); + $query = isset($uri['query']) ? $uri['query'] : ''; + $uri = isset($uri['path']) ? $uri['path'] : ''; + + if (isset($_SERVER['SCRIPT_NAME'][0])) { - $uri = substr($uri, strlen(dirname($_SERVER['SCRIPT_NAME']))); + if (strpos($uri, $_SERVER['SCRIPT_NAME']) === 0) + { + $uri = (string) substr($uri, strlen($_SERVER['SCRIPT_NAME'])); + } + elseif (strpos($uri, dirname($_SERVER['SCRIPT_NAME'])) === 0) + { + $uri = (string) substr($uri, strlen(dirname($_SERVER['SCRIPT_NAME']))); + } } // This section ensures that even on servers that require the URI to be in the query string (Nginx) a correct // URI is found, and also fixes the QUERY_STRING server var and $_GET array. - if (strncmp($uri, '?/', 2) === 0) - { - $uri = substr($uri, 2); - } - $parts = preg_split('#\?#i', $uri, 2); - $uri = $parts[0]; - if (isset($parts[1])) + if (trim($uri, '/') === '' && strncmp($query, '/', 1) === 0) { - $_SERVER['QUERY_STRING'] = $parts[1]; - parse_str($_SERVER['QUERY_STRING'], $_GET); + $query = explode('?', $query, 2); + $uri = $query[0]; + $_SERVER['QUERY_STRING'] = isset($query[1]) ? $query[1] : ''; } else { - $_SERVER['QUERY_STRING'] = ''; - $_GET = array(); + $_SERVER['QUERY_STRING'] = $query; } - if ($uri == '/' || empty($uri)) + parse_str($_SERVER['QUERY_STRING'], $_GET); + + if ($uri === '/' OR $uri === '') { return '/'; } - $uri = parse_url($uri, PHP_URL_PATH); - // Do some final cleaning of the URI and return it - return str_replace(array('//', '../'), '/', ltrim($uri, '/')); + return $this->_remove_relative_directory($uri); } // -------------------------------------------------------------------- /** - * Parse cli arguments + * Parse QUERY_STRING * - * Take each command line argument and assume it is a URI segment. + * Will parse QUERY_STRING and automatically detect the URI from it. * - * @access private * @return string */ - private function _parse_cli_args() + protected function _parse_query_string() { - $args = array_slice($_SERVER['argv'], 1); + $uri = isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : @getenv('QUERY_STRING'); - return $args ? '/' . implode('/', $args) : ''; - } - - // -------------------------------------------------------------------- - - /** - * Filter segments for malicious characters - * - * @access private - * @param string - * @return string - */ - function _filter_uri($str) - { - if ($str != '' && $this->config->item('permitted_uri_chars') != '' && $this->config->item('enable_query_strings') == FALSE) + if (trim($uri, '/') === '') { - // preg_quote() in PHP 5.3 escapes -, so the str_replace() and addition of - to preg_quote() is to maintain backwards - // compatibility as many are unaware of how characters in the permitted_uri_chars will be parsed as a regex pattern - if ( ! preg_match("|^[".str_replace(array('\\-', '\-'), '-', preg_quote($this->config->item('permitted_uri_chars'), '-'))."]+$|i", $str)) - { - show_error('The URI you submitted has disallowed characters.', 400); - } + return ''; + } + elseif (strncmp($uri, '/', 1) === 0) + { + $uri = explode('?', $uri, 2); + $_SERVER['QUERY_STRING'] = isset($uri[1]) ? $uri[1] : ''; + $uri = $uri[0]; } - // Convert programatic characters to entities - $bad = array('$', '(', ')', '%28', '%29'); - $good = array('$', '(', ')', '(', ')'); + parse_str($_SERVER['QUERY_STRING'], $_GET); - return str_replace($bad, $good, $str); + return $this->_remove_relative_directory($uri); } // -------------------------------------------------------------------- /** - * Remove the suffix from the URL if needed + * Parse CLI arguments * - * @access private - * @return void + * Take each command line argument and assume it is a URI segment. + * + * @return string */ - function _remove_url_suffix() + protected function _parse_argv() { - if ($this->config->item('url_suffix') != "") - { - $this->uri_string = preg_replace("|".preg_quote($this->config->item('url_suffix'))."$|", "", $this->uri_string); - } + $args = array_slice($_SERVER['argv'], 1); + return $args ? implode('/', $args) : ''; } // -------------------------------------------------------------------- /** - * Explode the URI Segments. The individual segments will - * be stored in the $this->segments array. + * Remove relative directory (../) and multi slashes (///) * - * @access private - * @return void + * Do some final cleaning of the URI and return it, currently only used in self::_parse_request_uri() + * + * @param string $uri + * @return string */ - function _explode_segments() + protected function _remove_relative_directory($uri) { - foreach (explode("/", preg_replace("|/*(.+?)/*$|", "\\1", $this->uri_string)) as $val) + $uris = array(); + $tok = strtok($uri, '/'); + while ($tok !== FALSE) { - // Filter segments for security - $val = trim($this->_filter_uri($val)); - - if ($val != '') + if (( ! empty($tok) OR $tok === '0') && $tok !== '..') { - $this->segments[] = $val; + $uris[] = $tok; } + $tok = strtok('/'); } + + return implode('/', $uris); } // -------------------------------------------------------------------- + /** - * Re-index Segments + * Filter URI * - * This function re-indexes the $this->segment array so that it - * starts at 1 rather than 0. Doing so makes it simpler to - * use functions like $this->uri->segment(n) since there is - * a 1:1 relationship between the segment array and the actual segments. + * Filters segments for malicious characters. * - * @access private + * @param string $str * @return void */ - function _reindex_segments() + public function filter_uri(&$str) { - array_unshift($this->segments, NULL); - array_unshift($this->rsegments, NULL); - unset($this->segments[0]); - unset($this->rsegments[0]); + if ( ! empty($str) && ! empty($this->_permitted_uri_chars) && ! preg_match('/^['.$this->_permitted_uri_chars.']+$/i'.(UTF8_ENABLED ? 'u' : ''), $str)) + { + show_error('The URI you submitted has disallowed characters.', 400); + } } // -------------------------------------------------------------------- /** - * Fetch a URI Segment + * Fetch URI Segment * - * This function returns the URI segment based on the number provided. - * - * @access public - * @param integer - * @param bool - * @return string + * @see CI_URI::$segments + * @param int $n Index + * @param mixed $no_result What to return if the segment index is not found + * @return mixed */ - function segment($n, $no_result = FALSE) + public function segment($n, $no_result = NULL) { - return ( ! isset($this->segments[$n])) ? $no_result : $this->segments[$n]; + return isset($this->segments[$n]) ? $this->segments[$n] : $no_result; } // -------------------------------------------------------------------- /** - * Fetch a URI "routed" Segment + * Fetch URI "routed" Segment * - * This function returns the re-routed URI segment (assuming routing rules are used) - * based on the number provided. If there is no routing this function returns the - * same result as $this->segment() + * Returns the re-routed URI segment (assuming routing rules are used) + * based on the index provided. If there is no routing, will return + * the same result as CI_URI::segment(). * - * @access public - * @param integer - * @param bool - * @return string + * @see CI_URI::$rsegments + * @see CI_URI::segment() + * @param int $n Index + * @param mixed $no_result What to return if the segment index is not found + * @return mixed */ - function rsegment($n, $no_result = FALSE) + public function rsegment($n, $no_result = NULL) { - return ( ! isset($this->rsegments[$n])) ? $no_result : $this->rsegments[$n]; + return isset($this->rsegments[$n]) ? $this->rsegments[$n] : $no_result; } // -------------------------------------------------------------------- /** - * Generate a key value pair from the URI string + * URI to assoc * - * This function generates and associative array of URI data starting - * at the supplied segment. For example, if this is your URI: + * Generates an associative array of URI data starting at the supplied + * segment index. For example, if this is your URI: * * example.com/user/search/name/joe/location/UK/gender/male * - * You can use this function to generate an array with this prototype: + * You can use this method to generate an array with this prototype: * - * array ( - * name => joe - * location => UK - * gender => male - * ) + * array ( + * name => joe + * location => UK + * gender => male + * ) * - * @access public - * @param integer the starting segment number - * @param array an array of default values + * @param int $n Index (default: 3) + * @param array $default Default values * @return array */ - function uri_to_assoc($n = 3, $default = array()) + public function uri_to_assoc($n = 3, $default = array()) { return $this->_uri_to_assoc($n, $default, 'segment'); } + + // -------------------------------------------------------------------- + /** - * Identical to above only it uses the re-routed segment array + * Routed URI to assoc * - * @access public - * @param integer the starting segment number - * @param array an array of default values - * @return array + * Identical to CI_URI::uri_to_assoc(), only it uses the re-routed + * segment array. * + * @see CI_URI::uri_to_assoc() + * @param int $n Index (default: 3) + * @param array $default Default values + * @return array */ - function ruri_to_assoc($n = 3, $default = array()) + public function ruri_to_assoc($n = 3, $default = array()) { return $this->_uri_to_assoc($n, $default, 'rsegment'); } @@ -411,57 +414,43 @@ class CI_URI { // -------------------------------------------------------------------- /** - * Generate a key value pair from the URI string or Re-routed URI string + * Internal URI-to-assoc + * + * Generates a key/value pair from the URI string or re-routed URI string. * - * @access private - * @param integer the starting segment number - * @param array an array of default values - * @param string which array we should use + * @used-by CI_URI::uri_to_assoc() + * @used-by CI_URI::ruri_to_assoc() + * @param int $n Index (default: 3) + * @param array $default Default values + * @param string $which Array name ('segment' or 'rsegment') * @return array */ - function _uri_to_assoc($n = 3, $default = array(), $which = 'segment') + protected function _uri_to_assoc($n = 3, $default = array(), $which = 'segment') { - if ($which == 'segment') - { - $total_segments = 'total_segments'; - $segment_array = 'segment_array'; - } - else - { - $total_segments = 'total_rsegments'; - $segment_array = 'rsegment_array'; - } - if ( ! is_numeric($n)) { return $default; } - if (isset($this->keyval[$n])) + if (isset($this->keyval[$which], $this->keyval[$which][$n])) { - return $this->keyval[$n]; + return $this->keyval[$which][$n]; } + $total_segments = "total_{$which}s"; + $segment_array = "{$which}_array"; + if ($this->$total_segments() < $n) { - if (count($default) == 0) - { - return array(); - } - - $retval = array(); - foreach ($default as $val) - { - $retval[$val] = FALSE; - } - return $retval; + return (count($default) === 0) + ? array() + : array_fill_keys($default, NULL); } $segments = array_slice($this->$segment_array(), ($n - 1)); - $i = 0; $lastval = ''; - $retval = array(); + $retval = array(); foreach ($segments as $seg) { if ($i % 2) @@ -470,7 +459,7 @@ class CI_URI { } else { - $retval[$seg] = FALSE; + $retval[$seg] = NULL; $lastval = $seg; } @@ -483,30 +472,31 @@ class CI_URI { { if ( ! array_key_exists($val, $retval)) { - $retval[$val] = FALSE; + $retval[$val] = NULL; } } } // Cache the array for reuse - $this->keyval[$n] = $retval; + isset($this->keyval[$which]) OR $this->keyval[$which] = array(); + $this->keyval[$which][$n] = $retval; return $retval; } // -------------------------------------------------------------------- /** - * Generate a URI string from an associative array + * Assoc to URI * + * Generates a URI string from an associative array. * - * @access public - * @param array an associative array of key/values - * @return array + * @param array $array Input array of key/value pairs + * @return string URI string */ - function assoc_to_uri($array) + public function assoc_to_uri($array) { $temp = array(); - foreach ((array)$array as $key => $val) + foreach ((array) $array as $key => $val) { $temp[] = $key; $temp[] = $val; @@ -518,14 +508,15 @@ class CI_URI { // -------------------------------------------------------------------- /** - * Fetch a URI Segment and add a trailing slash + * Slash segment + * + * Fetches an URI segment with a slash. * - * @access public - * @param integer - * @param string + * @param int $n Index + * @param string $where Where to add the slash ('trailing' or 'leading') * @return string */ - function slash_segment($n, $where = 'trailing') + public function slash_segment($n, $where = 'trailing') { return $this->_slash_segment($n, $where, 'segment'); } @@ -533,14 +524,15 @@ class CI_URI { // -------------------------------------------------------------------- /** - * Fetch a URI Segment and add a trailing slash + * Slash routed segment * - * @access public - * @param integer - * @param string + * Fetches an URI routed segment with a slash. + * + * @param int $n Index + * @param string $where Where to add the slash ('trailing' or 'leading') * @return string */ - function slash_rsegment($n, $where = 'trailing') + public function slash_rsegment($n, $where = 'trailing') { return $this->_slash_segment($n, $where, 'rsegment'); } @@ -548,24 +540,27 @@ class CI_URI { // -------------------------------------------------------------------- /** - * Fetch a URI Segment and add a trailing slash - helper function + * Internal Slash segment + * + * Fetches an URI Segment and adds a slash to it. * - * @access private - * @param integer - * @param string - * @param string + * @used-by CI_URI::slash_segment() + * @used-by CI_URI::slash_rsegment() + * + * @param int $n Index + * @param string $where Where to add the slash ('trailing' or 'leading') + * @param string $which Array name ('segment' or 'rsegment') * @return string */ - function _slash_segment($n, $where = 'trailing', $which = 'segment') + protected function _slash_segment($n, $where = 'trailing', $which = 'segment') { - $leading = '/'; - $trailing = '/'; + $leading = $trailing = '/'; - if ($where == 'trailing') + if ($where === 'trailing') { $leading = ''; } - elseif ($where == 'leading') + elseif ($where === 'leading') { $trailing = ''; } @@ -578,10 +573,9 @@ class CI_URI { /** * Segment Array * - * @access public - * @return array + * @return array CI_URI::$segments */ - function segment_array() + public function segment_array() { return $this->segments; } @@ -591,10 +585,9 @@ class CI_URI { /** * Routed Segment Array * - * @access public - * @return array + * @return array CI_URI::$rsegments */ - function rsegment_array() + public function rsegment_array() { return $this->rsegments; } @@ -604,10 +597,9 @@ class CI_URI { /** * Total number of segments * - * @access public - * @return integer + * @return int */ - function total_segments() + public function total_segments() { return count($this->segments); } @@ -617,10 +609,9 @@ class CI_URI { /** * Total number of routed segments * - * @access public - * @return integer + * @return int */ - function total_rsegments() + public function total_rsegments() { return count($this->rsegments); } @@ -628,32 +619,25 @@ class CI_URI { // -------------------------------------------------------------------- /** - * Fetch the entire URI string + * Fetch URI string * - * @access public - * @return string + * @return string CI_URI::$uri_string */ - function uri_string() + public function uri_string() { return $this->uri_string; } - // -------------------------------------------------------------------- /** - * Fetch the entire Re-routed URI string + * Fetch Re-routed URI string * - * @access public * @return string */ - function ruri_string() + public function ruri_string() { - return '/'.implode('/', $this->rsegment_array()); + return ltrim(load_class('Router', 'core')->directory, '/').implode('/', $this->rsegments); } } -// END URI Class - -/* End of file URI.php */ -/* Location: ./system/core/URI.php */ diff --git a/system/core/Utf8.php b/system/core/Utf8.php index 1a5bee198..dfbbfff2c 100644 --- a/system/core/Utf8.php +++ b/system/core/Utf8.php @@ -1,19 +1,41 @@ -<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); +<?php /** * CodeIgniter * - * An open source application development framework for PHP 5.1.6 or newer + * An open source application development framework for PHP * - * @package CodeIgniter - * @author ExpressionEngine Dev Team - * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. - * @license http://codeigniter.com/user_guide/license.html - * @link http://codeigniter.com - * @since Version 2.0 + * This content is released under the MIT License (MIT) + * + * Copyright (c) 2014 - 2017, British Columbia Institute of Technology + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @package CodeIgniter + * @author EllisLab Dev Team + * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/) + * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) + * @license http://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com + * @since Version 2.0.0 * @filesource */ - -// ------------------------------------------------------------------------ +defined('BASEPATH') OR exit('No direct script access allowed'); /** * Utf8 Class @@ -23,52 +45,36 @@ * @package CodeIgniter * @subpackage Libraries * @category UTF-8 - * @author ExpressionEngine Dev Team - * @link http://codeigniter.com/user_guide/libraries/utf8.html + * @author EllisLab Dev Team + * @link https://codeigniter.com/user_guide/libraries/utf8.html */ class CI_Utf8 { /** - * Constructor + * Class constructor * - * Determines if UTF-8 support is to be enabled + * Determines if UTF-8 support is to be enabled. * + * @return void */ - function __construct() + public function __construct() { - log_message('debug', "Utf8 Class Initialized"); - - global $CFG; - if ( - preg_match('/./u', 'é') === 1 // PCRE must support UTF-8 - AND function_exists('iconv') // iconv must be installed - AND ini_get('mbstring.func_overload') != 1 // Multibyte string function overloading cannot be enabled - AND $CFG->item('charset') == 'UTF-8' // Application charset must be UTF-8 + defined('PREG_BAD_UTF8_ERROR') // PCRE must support UTF-8 + && (ICONV_ENABLED === TRUE OR MB_ENABLED === TRUE) // iconv or mbstring must be installed + && strtoupper(config_item('charset')) === 'UTF-8' // Application charset must be UTF-8 ) { - log_message('debug', "UTF-8 Support Enabled"); - define('UTF8_ENABLED', TRUE); - - // set internal encoding for multibyte string functions if necessary - // and set a flag so we don't have to repeatedly use extension_loaded() - // or function_exists() - if (extension_loaded('mbstring')) - { - define('MB_ENABLED', TRUE); - mb_internal_encoding('UTF-8'); - } - else - { - define('MB_ENABLED', FALSE); - } + log_message('debug', 'UTF-8 Support Enabled'); } else { - log_message('debug', "UTF-8 Support Disabled"); define('UTF8_ENABLED', FALSE); + log_message('debug', 'UTF-8 Support Disabled'); } + + log_message('info', 'Utf8 Class Initialized'); } // -------------------------------------------------------------------- @@ -76,17 +82,23 @@ class CI_Utf8 { /** * Clean UTF-8 strings * - * Ensures strings are UTF-8 + * Ensures strings contain only valid UTF-8 characters. * - * @access public - * @param string + * @param string $str String to clean * @return string */ - function clean_string($str) + public function clean_string($str) { - if ($this->_is_ascii($str) === FALSE) + if ($this->is_ascii($str) === FALSE) { - $str = @iconv('UTF-8', 'UTF-8//IGNORE', $str); + if (MB_ENABLED) + { + $str = mb_convert_encoding($str, 'UTF-8', 'UTF-8'); + } + elseif (ICONV_ENABLED) + { + $str = @iconv('UTF-8', 'UTF-8//IGNORE', $str); + } } return $str; @@ -99,13 +111,12 @@ class CI_Utf8 { * * Removes all ASCII control characters except horizontal tabs, * line feeds, and carriage returns, as all others can cause - * problems in XML + * problems in XML. * - * @access public - * @param string + * @param string $str String to clean * @return string */ - function safe_ascii_for_xml($str) + public function safe_ascii_for_xml($str) { return remove_invisible_characters($str, FALSE); } @@ -115,29 +126,24 @@ class CI_Utf8 { /** * Convert to UTF-8 * - * Attempts to convert a string to UTF-8 + * Attempts to convert a string to UTF-8. * - * @access public - * @param string - * @param string - input encoding - * @return string + * @param string $str Input string + * @param string $encoding Input encoding + * @return string $str encoded in UTF-8 or FALSE on failure */ - function convert_to_utf8($str, $encoding) + public function convert_to_utf8($str, $encoding) { - if (function_exists('iconv')) + if (MB_ENABLED) { - $str = @iconv($encoding, 'UTF-8', $str); + return mb_convert_encoding($str, 'UTF-8', $encoding); } - elseif (function_exists('mb_convert_encoding')) + elseif (ICONV_ENABLED) { - $str = @mb_convert_encoding($str, 'UTF-8', $encoding); - } - else - { - return FALSE; + return @iconv($encoding, 'UTF-8', $str); } - return $str; + return FALSE; } // -------------------------------------------------------------------- @@ -145,21 +151,14 @@ class CI_Utf8 { /** * Is ASCII? * - * Tests if a string is standard 7-bit ASCII or not + * Tests if a string is standard 7-bit ASCII or not. * - * @access public - * @param string + * @param string $str String to check * @return bool */ - function _is_ascii($str) + public function is_ascii($str) { - return (preg_match('/[^\x00-\x7F]/S', $str) == 0); + return (preg_match('/[^\x00-\x7F]/S', $str) === 0); } - // -------------------------------------------------------------------- - } -// End Utf8 Class - -/* End of file Utf8.php */ -/* Location: ./system/core/Utf8.php */
\ No newline at end of file diff --git a/system/core/compat/hash.php b/system/core/compat/hash.php new file mode 100644 index 000000000..c65203aaf --- /dev/null +++ b/system/core/compat/hash.php @@ -0,0 +1,254 @@ +<?php +/** + * CodeIgniter + * + * An open source application development framework for PHP + * + * This content is released under the MIT License (MIT) + * + * Copyright (c) 2014 - 2017, British Columbia Institute of Technology + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @package CodeIgniter + * @author EllisLab Dev Team + * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/) + * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) + * @license http://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com + * @since Version 3.0.0 + * @filesource + */ +defined('BASEPATH') OR exit('No direct script access allowed'); + +/** + * PHP ext/hash compatibility package + * + * @package CodeIgniter + * @subpackage CodeIgniter + * @category Compatibility + * @author Andrey Andreev + * @link https://codeigniter.com/user_guide/ + * @link http://php.net/hash + */ + +// ------------------------------------------------------------------------ + +if (is_php('5.6')) +{ + return; +} + +// ------------------------------------------------------------------------ + +if ( ! function_exists('hash_equals')) +{ + /** + * hash_equals() + * + * @link http://php.net/hash_equals + * @param string $known_string + * @param string $user_string + * @return bool + */ + function hash_equals($known_string, $user_string) + { + if ( ! is_string($known_string)) + { + trigger_error('hash_equals(): Expected known_string to be a string, '.strtolower(gettype($known_string)).' given', E_USER_WARNING); + return FALSE; + } + elseif ( ! is_string($user_string)) + { + trigger_error('hash_equals(): Expected user_string to be a string, '.strtolower(gettype($user_string)).' given', E_USER_WARNING); + return FALSE; + } + elseif (($length = strlen($known_string)) !== strlen($user_string)) + { + return FALSE; + } + + $diff = 0; + for ($i = 0; $i < $length; $i++) + { + $diff |= ord($known_string[$i]) ^ ord($user_string[$i]); + } + + return ($diff === 0); + } +} + +// ------------------------------------------------------------------------ + +if (is_php('5.5')) +{ + return; +} + +// ------------------------------------------------------------------------ + +if ( ! function_exists('hash_pbkdf2')) +{ + /** + * hash_pbkdf2() + * + * @link http://php.net/hash_pbkdf2 + * @param string $algo + * @param string $password + * @param string $salt + * @param int $iterations + * @param int $length + * @param bool $raw_output + * @return string + */ + function hash_pbkdf2($algo, $password, $salt, $iterations, $length = 0, $raw_output = FALSE) + { + if ( ! in_array(strtolower($algo), hash_algos(), TRUE)) + { + trigger_error('hash_pbkdf2(): Unknown hashing algorithm: '.$algo, E_USER_WARNING); + return FALSE; + } + + if (($type = gettype($iterations)) !== 'integer') + { + if ($type === 'object' && method_exists($iterations, '__toString')) + { + $iterations = (string) $iterations; + } + + if (is_string($iterations) && is_numeric($iterations)) + { + $iterations = (int) $iterations; + } + else + { + trigger_error('hash_pbkdf2() expects parameter 4 to be long, '.$type.' given', E_USER_WARNING); + return NULL; + } + } + + if ($iterations < 1) + { + trigger_error('hash_pbkdf2(): Iterations must be a positive integer: '.$iterations, E_USER_WARNING); + return FALSE; + } + + if (($type = gettype($length)) !== 'integer') + { + if ($type === 'object' && method_exists($length, '__toString')) + { + $length = (string) $length; + } + + if (is_string($length) && is_numeric($length)) + { + $length = (int) $length; + } + else + { + trigger_error('hash_pbkdf2() expects parameter 5 to be long, '.$type.' given', E_USER_WARNING); + return NULL; + } + } + + if ($length < 0) + { + trigger_error('hash_pbkdf2(): Length must be greater than or equal to 0: '.$length, E_USER_WARNING); + return FALSE; + } + + $hash_length = defined('MB_OVERLOAD_STRING') + ? mb_strlen(hash($algo, NULL, TRUE), '8bit') + : strlen(hash($algo, NULL, TRUE)); + empty($length) && $length = $hash_length; + + // Pre-hash password inputs longer than the algorithm's block size + // (i.e. prepare HMAC key) to mitigate potential DoS attacks. + static $block_sizes; + empty($block_sizes) && $block_sizes = array( + 'gost' => 32, + 'haval128,3' => 128, + 'haval160,3' => 128, + 'haval192,3' => 128, + 'haval224,3' => 128, + 'haval256,3' => 128, + 'haval128,4' => 128, + 'haval160,4' => 128, + 'haval192,4' => 128, + 'haval224,4' => 128, + 'haval256,4' => 128, + 'haval128,5' => 128, + 'haval160,5' => 128, + 'haval192,5' => 128, + 'haval224,5' => 128, + 'haval256,5' => 128, + 'md2' => 16, + 'md4' => 64, + 'md5' => 64, + 'ripemd128' => 64, + 'ripemd160' => 64, + 'ripemd256' => 64, + 'ripemd320' => 64, + 'salsa10' => 64, + 'salsa20' => 64, + 'sha1' => 64, + 'sha224' => 64, + 'sha256' => 64, + 'sha384' => 128, + 'sha512' => 128, + 'snefru' => 32, + 'snefru256' => 32, + 'tiger128,3' => 64, + 'tiger160,3' => 64, + 'tiger192,3' => 64, + 'tiger128,4' => 64, + 'tiger160,4' => 64, + 'tiger192,4' => 64, + 'whirlpool' => 64 + ); + + if (isset($block_sizes[$algo], $password[$block_sizes[$algo]])) + { + $password = hash($algo, $password, TRUE); + } + + $hash = ''; + // Note: Blocks are NOT 0-indexed + for ($bc = (int) ceil($length / $hash_length), $bi = 1; $bi <= $bc; $bi++) + { + $key = $derived_key = hash_hmac($algo, $salt.pack('N', $bi), $password, TRUE); + for ($i = 1; $i < $iterations; $i++) + { + $derived_key ^= $key = hash_hmac($algo, $key, $password, TRUE); + } + + $hash .= $derived_key; + } + + // This is not RFC-compatible, but we're aiming for natural PHP compatibility + if ( ! $raw_output) + { + $hash = bin2hex($hash); + } + + return defined('MB_OVERLOAD_STRING') + ? mb_substr($hash, 0, $length, '8bit') + : substr($hash, 0, $length); + } +} diff --git a/system/core/compat/index.html b/system/core/compat/index.html new file mode 100644 index 000000000..b702fbc39 --- /dev/null +++ b/system/core/compat/index.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<html> +<head> + <title>403 Forbidden</title> +</head> +<body> + +<p>Directory access is forbidden.</p> + +</body> +</html> diff --git a/system/core/compat/mbstring.php b/system/core/compat/mbstring.php new file mode 100644 index 000000000..1b2f2c63b --- /dev/null +++ b/system/core/compat/mbstring.php @@ -0,0 +1,149 @@ +<?php +/** + * CodeIgniter + * + * An open source application development framework for PHP + * + * This content is released under the MIT License (MIT) + * + * Copyright (c) 2014 - 2017, British Columbia Institute of Technology + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @package CodeIgniter + * @author EllisLab Dev Team + * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/) + * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) + * @license http://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com + * @since Version 3.0.0 + * @filesource + */ +defined('BASEPATH') OR exit('No direct script access allowed'); + +/** + * PHP ext/mbstring compatibility package + * + * @package CodeIgniter + * @subpackage CodeIgniter + * @category Compatibility + * @author Andrey Andreev + * @link https://codeigniter.com/user_guide/ + * @link http://php.net/mbstring + */ + +// ------------------------------------------------------------------------ + +if (MB_ENABLED === TRUE) +{ + return; +} + +// ------------------------------------------------------------------------ + +if ( ! function_exists('mb_strlen')) +{ + /** + * mb_strlen() + * + * WARNING: This function WILL fall-back to strlen() + * if iconv is not available! + * + * @link http://php.net/mb_strlen + * @param string $str + * @param string $encoding + * @return int + */ + function mb_strlen($str, $encoding = NULL) + { + if (ICONV_ENABLED === TRUE) + { + return iconv_strlen($str, isset($encoding) ? $encoding : config_item('charset')); + } + + log_message('debug', 'Compatibility (mbstring): iconv_strlen() is not available, falling back to strlen().'); + return strlen($str); + } +} + +// ------------------------------------------------------------------------ + +if ( ! function_exists('mb_strpos')) +{ + /** + * mb_strpos() + * + * WARNING: This function WILL fall-back to strpos() + * if iconv is not available! + * + * @link http://php.net/mb_strpos + * @param string $haystack + * @param string $needle + * @param int $offset + * @param string $encoding + * @return mixed + */ + function mb_strpos($haystack, $needle, $offset = 0, $encoding = NULL) + { + if (ICONV_ENABLED === TRUE) + { + return iconv_strpos($haystack, $needle, $offset, isset($encoding) ? $encoding : config_item('charset')); + } + + log_message('debug', 'Compatibility (mbstring): iconv_strpos() is not available, falling back to strpos().'); + return strpos($haystack, $needle, $offset); + } +} + +// ------------------------------------------------------------------------ + +if ( ! function_exists('mb_substr')) +{ + /** + * mb_substr() + * + * WARNING: This function WILL fall-back to substr() + * if iconv is not available. + * + * @link http://php.net/mb_substr + * @param string $str + * @param int $start + * @param int $length + * @param string $encoding + * @return string + */ + function mb_substr($str, $start, $length = NULL, $encoding = NULL) + { + if (ICONV_ENABLED === TRUE) + { + isset($encoding) OR $encoding = config_item('charset'); + return iconv_substr( + $str, + $start, + isset($length) ? $length : iconv_strlen($str, $encoding), // NULL doesn't work + $encoding + ); + } + + log_message('debug', 'Compatibility (mbstring): iconv_substr() is not available, falling back to substr().'); + return isset($length) + ? substr($str, $start, $length) + : substr($str, $start); + } +} diff --git a/system/core/compat/password.php b/system/core/compat/password.php new file mode 100644 index 000000000..8176f0088 --- /dev/null +++ b/system/core/compat/password.php @@ -0,0 +1,251 @@ +<?php +/** + * CodeIgniter + * + * An open source application development framework for PHP + * + * This content is released under the MIT License (MIT) + * + * Copyright (c) 2014 - 2017, British Columbia Institute of Technology + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @package CodeIgniter + * @author EllisLab Dev Team + * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/) + * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) + * @license http://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com + * @since Version 3.0.0 + * @filesource + */ +defined('BASEPATH') OR exit('No direct script access allowed'); + +/** + * PHP ext/standard/password compatibility package + * + * @package CodeIgniter + * @subpackage CodeIgniter + * @category Compatibility + * @author Andrey Andreev + * @link https://codeigniter.com/user_guide/ + * @link http://php.net/password + */ + +// ------------------------------------------------------------------------ + +if (is_php('5.5') OR ! defined('CRYPT_BLOWFISH') OR CRYPT_BLOWFISH !== 1 OR defined('HHVM_VERSION')) +{ + return; +} + +// ------------------------------------------------------------------------ + +defined('PASSWORD_BCRYPT') OR define('PASSWORD_BCRYPT', 1); +defined('PASSWORD_DEFAULT') OR define('PASSWORD_DEFAULT', PASSWORD_BCRYPT); + +// ------------------------------------------------------------------------ + +if ( ! function_exists('password_get_info')) +{ + /** + * password_get_info() + * + * @link http://php.net/password_get_info + * @param string $hash + * @return array + */ + function password_get_info($hash) + { + return (strlen($hash) < 60 OR sscanf($hash, '$2y$%d', $hash) !== 1) + ? array('algo' => 0, 'algoName' => 'unknown', 'options' => array()) + : array('algo' => 1, 'algoName' => 'bcrypt', 'options' => array('cost' => $hash)); + } +} + +// ------------------------------------------------------------------------ + +if ( ! function_exists('password_hash')) +{ + /** + * password_hash() + * + * @link http://php.net/password_hash + * @param string $password + * @param int $algo + * @param array $options + * @return mixed + */ + function password_hash($password, $algo, array $options = array()) + { + static $func_overload; + isset($func_overload) OR $func_overload = (extension_loaded('mbstring') && ini_get('mbstring.func_overload')); + + if ($algo !== 1) + { + trigger_error('password_hash(): Unknown hashing algorithm: '.(int) $algo, E_USER_WARNING); + return NULL; + } + + if (isset($options['cost']) && ($options['cost'] < 4 OR $options['cost'] > 31)) + { + trigger_error('password_hash(): Invalid bcrypt cost parameter specified: '.(int) $options['cost'], E_USER_WARNING); + return NULL; + } + + if (isset($options['salt']) && ($saltlen = ($func_overload ? mb_strlen($options['salt'], '8bit') : strlen($options['salt']))) < 22) + { + trigger_error('password_hash(): Provided salt is too short: '.$saltlen.' expecting 22', E_USER_WARNING); + return NULL; + } + elseif ( ! isset($options['salt'])) + { + if (function_exists('random_bytes')) + { + try + { + $options['salt'] = random_bytes(16); + } + catch (Exception $e) + { + log_message('error', 'compat/password: Error while trying to use random_bytes(): '.$e->getMessage()); + return FALSE; + } + } + elseif (defined('MCRYPT_DEV_URANDOM')) + { + $options['salt'] = mcrypt_create_iv(16, MCRYPT_DEV_URANDOM); + } + elseif (DIRECTORY_SEPARATOR === '/' && (is_readable($dev = '/dev/arandom') OR is_readable($dev = '/dev/urandom'))) + { + if (($fp = fopen($dev, 'rb')) === FALSE) + { + log_message('error', 'compat/password: Unable to open '.$dev.' for reading.'); + return FALSE; + } + + // Try not to waste entropy ... + is_php('5.4') && stream_set_chunk_size($fp, 16); + + $options['salt'] = ''; + for ($read = 0; $read < 16; $read = ($func_overload) ? mb_strlen($options['salt'], '8bit') : strlen($options['salt'])) + { + if (($read = fread($fp, 16 - $read)) === FALSE) + { + log_message('error', 'compat/password: Error while reading from '.$dev.'.'); + return FALSE; + } + $options['salt'] .= $read; + } + + fclose($fp); + } + elseif (function_exists('openssl_random_pseudo_bytes')) + { + $is_secure = NULL; + $options['salt'] = openssl_random_pseudo_bytes(16, $is_secure); + if ($is_secure !== TRUE) + { + log_message('error', 'compat/password: openssl_random_pseudo_bytes() set the $cryto_strong flag to FALSE'); + return FALSE; + } + } + else + { + log_message('error', 'compat/password: No CSPRNG available.'); + return FALSE; + } + + $options['salt'] = str_replace('+', '.', rtrim(base64_encode($options['salt']), '=')); + } + elseif ( ! preg_match('#^[a-zA-Z0-9./]+$#D', $options['salt'])) + { + $options['salt'] = str_replace('+', '.', rtrim(base64_encode($options['salt']), '=')); + } + + isset($options['cost']) OR $options['cost'] = 10; + + return (strlen($password = crypt($password, sprintf('$2y$%02d$%s', $options['cost'], $options['salt']))) === 60) + ? $password + : FALSE; + } +} + +// ------------------------------------------------------------------------ + +if ( ! function_exists('password_needs_rehash')) +{ + /** + * password_needs_rehash() + * + * @link http://php.net/password_needs_rehash + * @param string $hash + * @param int $algo + * @param array $options + * @return bool + */ + function password_needs_rehash($hash, $algo, array $options = array()) + { + $info = password_get_info($hash); + + if ($algo !== $info['algo']) + { + return TRUE; + } + elseif ($algo === 1) + { + $options['cost'] = isset($options['cost']) ? (int) $options['cost'] : 10; + return ($info['options']['cost'] !== $options['cost']); + } + + // Odd at first glance, but according to a comment in PHP's own unit tests, + // because it is an unknown algorithm - it's valid and therefore doesn't + // need rehashing. + return FALSE; + } +} + +// ------------------------------------------------------------------------ + +if ( ! function_exists('password_verify')) +{ + /** + * password_verify() + * + * @link http://php.net/password_verify + * @param string $password + * @param string $hash + * @return bool + */ + function password_verify($password, $hash) + { + if (strlen($hash) !== 60 OR strlen($password = crypt($password, $hash)) !== 60) + { + return FALSE; + } + + $compare = 0; + for ($i = 0; $i < 60; $i++) + { + $compare |= (ord($password[$i]) ^ ord($hash[$i])); + } + + return ($compare === 0); + } +} diff --git a/system/core/compat/standard.php b/system/core/compat/standard.php new file mode 100644 index 000000000..7db2efb57 --- /dev/null +++ b/system/core/compat/standard.php @@ -0,0 +1,182 @@ +<?php +/** + * CodeIgniter + * + * An open source application development framework for PHP + * + * This content is released under the MIT License (MIT) + * + * Copyright (c) 2014 - 2017, British Columbia Institute of Technology + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @package CodeIgniter + * @author EllisLab Dev Team + * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/) + * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) + * @license http://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com + * @since Version 3.0.0 + * @filesource + */ +defined('BASEPATH') OR exit('No direct script access allowed'); + +/** + * PHP ext/standard compatibility package + * + * @package CodeIgniter + * @subpackage CodeIgniter + * @category Compatibility + * @author Andrey Andreev + * @link https://codeigniter.com/user_guide/ + */ + +// ------------------------------------------------------------------------ + +if (is_php('5.5')) +{ + return; +} + +// ------------------------------------------------------------------------ + +if ( ! function_exists('array_column')) +{ + /** + * array_column() + * + * @link http://php.net/array_column + * @param array $array + * @param mixed $column_key + * @param mixed $index_key + * @return array + */ + function array_column(array $array, $column_key, $index_key = NULL) + { + if ( ! in_array($type = gettype($column_key), array('integer', 'string', 'NULL'), TRUE)) + { + if ($type === 'double') + { + $column_key = (int) $column_key; + } + elseif ($type === 'object' && method_exists($column_key, '__toString')) + { + $column_key = (string) $column_key; + } + else + { + trigger_error('array_column(): The column key should be either a string or an integer', E_USER_WARNING); + return FALSE; + } + } + + if ( ! in_array($type = gettype($index_key), array('integer', 'string', 'NULL'), TRUE)) + { + if ($type === 'double') + { + $index_key = (int) $index_key; + } + elseif ($type === 'object' && method_exists($index_key, '__toString')) + { + $index_key = (string) $index_key; + } + else + { + trigger_error('array_column(): The index key should be either a string or an integer', E_USER_WARNING); + return FALSE; + } + } + + $result = array(); + foreach ($array as &$a) + { + if ($column_key === NULL) + { + $value = $a; + } + elseif (is_array($a) && array_key_exists($column_key, $a)) + { + $value = $a[$column_key]; + } + else + { + continue; + } + + if ($index_key === NULL OR ! array_key_exists($index_key, $a)) + { + $result[] = $value; + } + else + { + $result[$a[$index_key]] = $value; + } + } + + return $result; + } +} + +// ------------------------------------------------------------------------ + +if (is_php('5.4')) +{ + return; +} + +// ------------------------------------------------------------------------ + +if ( ! function_exists('hex2bin')) +{ + /** + * hex2bin() + * + * @link http://php.net/hex2bin + * @param string $data + * @return string + */ + function hex2bin($data) + { + if (in_array($type = gettype($data), array('array', 'double', 'object', 'resource'), TRUE)) + { + if ($type === 'object' && method_exists($data, '__toString')) + { + $data = (string) $data; + } + else + { + trigger_error('hex2bin() expects parameter 1 to be string, '.$type.' given', E_USER_WARNING); + return NULL; + } + } + + if (strlen($data) % 2 !== 0) + { + trigger_error('Hexadecimal input string must have an even length', E_USER_WARNING); + return FALSE; + } + elseif ( ! preg_match('/^[0-9a-f]*$/i', $data)) + { + trigger_error('Input string must be hexadecimal string', E_USER_WARNING); + return FALSE; + } + + return pack('H*', $data); + } +} diff --git a/system/core/index.html b/system/core/index.html index c942a79ce..b702fbc39 100644 --- a/system/core/index.html +++ b/system/core/index.html @@ -1,3 +1,4 @@ +<!DOCTYPE html> <html> <head> <title>403 Forbidden</title> @@ -7,4 +8,4 @@ <p>Directory access is forbidden.</p> </body> -</html>
\ No newline at end of file +</html> |