summaryrefslogtreecommitdiffstats
path: root/system/core
diff options
context:
space:
mode:
Diffstat (limited to 'system/core')
-rw-r--r--system/core/Benchmark.php2
-rw-r--r--system/core/CodeIgniter.php233
-rw-r--r--system/core/Common.php92
-rw-r--r--system/core/Config.php31
-rw-r--r--system/core/Controller.php2
-rw-r--r--system/core/Exceptions.php55
-rw-r--r--system/core/Hooks.php41
-rw-r--r--system/core/Input.php144
-rw-r--r--system/core/Lang.php4
-rw-r--r--system/core/Loader.php117
-rw-r--r--system/core/Log.php19
-rw-r--r--system/core/Model.php5
-rw-r--r--system/core/Output.php290
-rw-r--r--system/core/Router.php324
-rw-r--r--system/core/Security.php86
-rw-r--r--system/core/URI.php282
-rw-r--r--system/core/Utf8.php39
-rw-r--r--system/core/compat/mbstring.php141
-rw-r--r--system/core/compat/password.php216
19 files changed, 1339 insertions, 784 deletions
diff --git a/system/core/Benchmark.php b/system/core/Benchmark.php
index f9be18a42..36326f521 100644
--- a/system/core/Benchmark.php
+++ b/system/core/Benchmark.php
@@ -18,7 +18,7 @@
*
* @package CodeIgniter
* @author EllisLab Dev Team
- * @copyright Copyright (c) 2008 - 2013, EllisLab, Inc. (http://ellislab.com/)
+ * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (http://ellislab.com/)
* @license http://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
* @link http://codeigniter.com
* @since Version 1.0
diff --git a/system/core/CodeIgniter.php b/system/core/CodeIgniter.php
index c962fda20..270988a1b 100644
--- a/system/core/CodeIgniter.php
+++ b/system/core/CodeIgniter.php
@@ -18,7 +18,7 @@
*
* @package CodeIgniter
* @author EllisLab Dev Team
- * @copyright Copyright (c) 2008 - 2013, EllisLab, Inc. (http://ellislab.com/)
+ * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (http://ellislab.com/)
* @license http://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
* @link http://codeigniter.com
* @since Version 1.0
@@ -48,24 +48,22 @@ defined('BASEPATH') OR exit('No direct script access allowed');
/*
* ------------------------------------------------------
- * Load the global functions
+ * Load the framework constants
* ------------------------------------------------------
*/
- require_once(BASEPATH.'core/Common.php');
+ if (file_exists(APPPATH.'config/'.ENVIRONMENT.'/constants.php'))
+ {
+ require_once(APPPATH.'config/'.ENVIRONMENT.'/constants.php');
+ }
+
+ require_once(APPPATH.'config/constants.php');
/*
* ------------------------------------------------------
- * Load the framework constants
+ * Load the global functions
* ------------------------------------------------------
*/
- if (file_exists(APPPATH.'config/'.ENVIRONMENT.'/constants.php'))
- {
- require(APPPATH.'config/'.ENVIRONMENT.'/constants.php');
- }
- else
- {
- require(APPPATH.'config/constants.php');
- }
+ require_once(BASEPATH.'core/Common.php');
/*
* ------------------------------------------------------
@@ -73,11 +71,10 @@ defined('BASEPATH') OR exit('No direct script access allowed');
* ------------------------------------------------------
*/
set_error_handler('_exception_handler');
+ register_shutdown_function('_shutdown_handler');
- if ( ! is_php('5.4'))
- {
- @ini_set('magic_quotes_runtime', 0); // Kill magic quotes
- }
+ // Kill magic quotes
+ is_php('5.4') OR @ini_set('magic_quotes_runtime', 0);
/*
* ------------------------------------------------------
@@ -127,6 +124,11 @@ defined('BASEPATH') OR exit('No direct script access allowed');
* ------------------------------------------------------
* 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');
@@ -141,15 +143,59 @@ defined('BASEPATH') OR exit('No direct script access allowed');
/*
* ------------------------------------------------------
- * 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'));
+
+ if (extension_loaded('mbstring'))
+ {
+ define('MB_ENABLED', TRUE);
+ mb_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_set_encoding('internal_encoding', $charset);
+ }
+ else
+ {
+ define('ICONV_ENABLED', FALSE);
+ }
+
+/*
+ * ------------------------------------------------------
+ * Load compatibility features
+ * ------------------------------------------------------
+ */
+
+ require_once(BASEPATH.'core/compat/mbstring.php';
+ require_once(BASEPATH.'core/compat/password.php';
+
+/*
+ * ------------------------------------------------------
+ * Instantiate the UTF-8 class
+ * ------------------------------------------------------
+ */
$UNI =& load_class('Utf8', 'core');
/*
@@ -166,12 +212,6 @@ defined('BASEPATH') OR exit('No direct script access allowed');
*/
$RTR =& load_class('Router', 'core');
- // Set any routing overrides that may exist in the main index file
- if (isset($routing))
- {
- $RTR->_set_overrides($routing);
- }
-
/*
* ------------------------------------------------------
* Instantiate the output class
@@ -218,7 +258,7 @@ defined('BASEPATH') OR exit('No direct script access allowed');
*
*/
// Load the base controller class
- require BASEPATH.'core/Controller.php';
+ require_once BASEPATH.'core/Controller.php';
/**
* Reference to the CI_Controller method.
@@ -234,96 +274,117 @@ defined('BASEPATH') OR exit('No direct script access allowed');
if (file_exists(APPPATH.'core/'.$CFG->config['subclass_prefix'].'Controller.php'))
{
- require 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.
- $class = ucfirst($RTR->class);
- if ( ! file_exists(APPPATH.'controllers/'.$RTR->directory.$class.'.php'))
- {
- show_error('Unable to load your default controller. Please make sure the controller specified in your Routes.php file is valid.');
+ require_once APPPATH.'core/'.$CFG->config['subclass_prefix'].'Controller.php';
}
- include(APPPATH.'controllers/'.$RTR->directory.$class.'.php');
-
// Set a mark point for benchmarking
$BM->mark('loading_time:_base_classes_end');
/*
* ------------------------------------------------------
- * Security check
+ * Sanity checks
* ------------------------------------------------------
*
- * None of the methods in the app controller or the
- * loader class can be called via the URI, nor can
+ * 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.
*/
- $method = $RTR->method;
-
- if ( ! class_exists($class, FALSE) OR $method[0] === '_' OR method_exists('CI_Controller', $method))
- {
- if ( ! empty($RTR->routes['404_override']))
- {
- if (sscanf($RTR->routes['404_override'], '%[^/]/%s', $class, $method) !== 2)
- {
- $method = 'index';
- }
-
- $class = ucfirst($class);
- if ( ! class_exists($class, FALSE))
- {
- if ( ! file_exists(APPPATH.'controllers/'.$class.'.php'))
- {
- show_404($class.'/'.$method);
- }
-
- include_once(APPPATH.'controllers/'.$class.'.php');
- }
- }
- else
- {
- show_404($class.'/'.$method);
- }
- }
+ $e404 = FALSE;
+ $class = ucfirst($RTR->class);
+ $method = $RTR->method;
- if (method_exists($class, '_remap'))
+ if (empty($class) OR ! file_exists(APPPATH.'controllers/'.$RTR->directory.$class.'.php'))
{
- $params = array($method, array_slice($URI->rsegments, 2));
- $method = '_remap';
+ $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';
+ }
// WARNING: It appears that there are issues with is_callable() even in PHP 5.2!
// Furthermore, there are bug reports and feature/change requests related to it
// that make it unreliable to use in this context. Please, DO NOT change this
// work-around until a better alternative is available.
- if ( ! in_array(strtolower($method), array_map('strtolower', get_class_methods($class)), TRUE))
+ elseif ( ! in_array(strtolower($method), array_map('strtolower', get_class_methods($class)), TRUE))
{
- if (empty($RTR->routes['404_override']))
- {
- show_404($class.'/'.$method);
- }
- elseif (sscanf($RTR->routes['404_override'], '%[^/]/%s', $class, $method) !== 2)
+ $e404 = TRUE;
+ }
+ }
+
+ if ($e404)
+ {
+ if ( ! empty($RTR->routes['404_override']))
+ {
+ if (sscanf($RTR->routes['404_override'], '%[^/]/%s', $error_class, $error_method) !== 2)
{
- $method = 'index';
+ $error_method = 'index';
}
- $class = ucfirst($class);
+ $error_class = ucfirst($error_class);
- if ( ! class_exists($class, FALSE))
+ 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);
}
-
- include_once(APPPATH.'controllers/'.$class.'.php');
+ // 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 = '';
+ }
+ }
+ }
+ 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($RTR->directory.$class.'/'.$method);
+ }
+ }
+
+ if ($method !== '_remap')
+ {
$params = array_slice($URI->rsegments, 2);
}
diff --git a/system/core/Common.php b/system/core/Common.php
index 7f296b133..16a916a01 100644
--- a/system/core/Common.php
+++ b/system/core/Common.php
@@ -18,7 +18,7 @@
*
* @package CodeIgniter
* @author EllisLab Dev Team
- * @copyright Copyright (c) 2008 - 2013, EllisLab, Inc. (http://ellislab.com/)
+ * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (http://ellislab.com/)
* @license http://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
* @link http://codeigniter.com
* @since Version 1.0
@@ -76,13 +76,14 @@ if ( ! function_exists('is_really_writable'))
* 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 void
*/
function is_really_writable($file)
{
// If we're on a Unix server with safe_mode off we call is_writable
- if (DIRECTORY_SEPARATOR === '/' && (bool) @ini_get('safe_mode') === FALSE)
+ if (DIRECTORY_SEPARATOR === '/' && (is_php('5.4') OR (bool) @ini_get('safe_mode') === FALSE))
{
return is_writable($file);
}
@@ -355,6 +356,24 @@ if ( ! function_exists('is_https'))
// ------------------------------------------------------------------------
+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'));
+ }
+}
+
+// ------------------------------------------------------------------------
+
if ( ! function_exists('show_error'))
{
/**
@@ -429,10 +448,9 @@ if ( ! function_exists('log_message'))
*
* @param string the error level: 'error', 'debug' or 'info'
* @param string the error message
- * @param bool whether the error is a native PHP error
* @return void
*/
- function log_message($level, $message, $php_error = FALSE)
+ function log_message($level, $message)
{
static $_log;
@@ -442,7 +460,7 @@ if ( ! function_exists('log_message'))
$_log[0] =& load_class('Log', 'core');
}
- $_log[0]->write_log($level, $message, $php_error);
+ $_log[0]->write_log($level, $message);
}
}
@@ -550,14 +568,27 @@ if ( ! function_exists('_exception_handler'))
* to display errors based on the current error_reporting level.
* We do that with the use of a PHP error template.
*
- * @param int
- * @param string
- * @param string
- * @param int
+ * @param int $severity
+ * @param string $message
+ * @param string $filepath
+ * @param int $line
* @return void
*/
function _exception_handler($severity, $message, $filepath, $line)
{
+ $is_error = (((E_ERROR | 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);
+ }
+
$_error =& load_class('Exceptions', 'core');
// Should we ignore the error? We'll get the current error_reporting
@@ -567,13 +598,49 @@ if ( ! function_exists('_exception_handler'))
return;
}
+ $_error->log_exception($severity, $message, $filepath, $line);
+
// Should we display the error?
if ((bool) ini_get('display_errors') === TRUE)
{
$_error->show_php_error($severity, $message, $filepath, $line);
}
- $_error->log_exception($severity, $message, $filepath, $line);
+ // 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)
+ {
+ exit(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 purposivly 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)))
+ {
+ _exception_handler($last_error['type'], $last_error['message'], $last_error['file'], $last_error['line']);
+ }
}
}
@@ -689,6 +756,11 @@ if ( ! function_exists('function_usable'))
* *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 commited 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,
diff --git a/system/core/Config.php b/system/core/Config.php
index 109ee6424..f630d1709 100644
--- a/system/core/Config.php
+++ b/system/core/Config.php
@@ -18,7 +18,7 @@
*
* @package CodeIgniter
* @author EllisLab Dev Team
- * @copyright Copyright (c) 2008 - 2013, EllisLab, Inc. (http://ellislab.com/)
+ * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (http://ellislab.com/)
* @license http://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
* @link http://codeigniter.com
* @since Version 1.0
@@ -228,13 +228,21 @@ class CI_Config {
* @uses CI_Config::_uri_string()
*
* @param string|string[] $uri URI string or an array of segments
+ * @param string $protocol
* @return string
*/
- public function site_url($uri = '')
+ public function site_url($uri = '', $protocol = NULL)
{
+ $base_url = $this->slash_item('base_url');
+
+ if (isset($protocol))
+ {
+ $base_url = $protocol.substr($base_url, strpos($base_url, '://'));
+ }
+
if (empty($uri))
{
- return $this->slash_item('base_url').$this->item('index_page');
+ return $base_url.$this->item('index_page');
}
$uri = $this->_uri_string($uri);
@@ -255,14 +263,14 @@ class CI_Config {
}
}
- return $this->slash_item('base_url').$this->slash_item('index_page').$uri;
+ return $base_url.$this->slash_item('index_page').$uri;
}
elseif (strpos($uri, '?') === FALSE)
{
$uri = '?'.$uri;
}
- return $this->slash_item('base_url').$this->item('index_page').$uri;
+ return $base_url.$this->item('index_page').$uri;
}
// -------------------------------------------------------------
@@ -275,11 +283,19 @@ class CI_Config {
* @uses CI_Config::_uri_string()
*
* @param string|string[] $uri URI string or an array of segments
+ * @param string $protocol
* @return string
*/
- public 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))
+ {
+ $base_url = $protocol.substr($base_url, strpos($base_url, '://'));
+ }
+
+ return $base_url.ltrim($this->_uri_string($uri), '/');
}
// -------------------------------------------------------------
@@ -316,6 +332,7 @@ class CI_Config {
/**
* System URL
*
+ * @deprecated 3.0.0 Encourages insecure practices
* @return string
*/
public function system_url()
diff --git a/system/core/Controller.php b/system/core/Controller.php
index 3fcadcadf..8db222a98 100644
--- a/system/core/Controller.php
+++ b/system/core/Controller.php
@@ -18,7 +18,7 @@
*
* @package CodeIgniter
* @author EllisLab Dev Team
- * @copyright Copyright (c) 2008 - 2013, EllisLab, Inc. (http://ellislab.com/)
+ * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (http://ellislab.com/)
* @license http://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
* @link http://codeigniter.com
* @since Version 1.0
diff --git a/system/core/Exceptions.php b/system/core/Exceptions.php
index 9c68d06a5..54a5bc48b 100644
--- a/system/core/Exceptions.php
+++ b/system/core/Exceptions.php
@@ -18,7 +18,7 @@
*
* @package CodeIgniter
* @author EllisLab Dev Team
- * @copyright Copyright (c) 2008 - 2013, EllisLab, Inc. (http://ellislab.com/)
+ * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (http://ellislab.com/)
* @license http://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
* @link http://codeigniter.com
* @since Version 1.0
@@ -91,7 +91,7 @@ class CI_Exceptions {
public function log_exception($severity, $message, $filepath, $line)
{
$severity = isset($this->levels[$severity]) ? $this->levels[$severity] : $severity;
- log_message('error', 'Severity: '.$severity.' --> '.$message. ' '.$filepath.' '.$line, TRUE);
+ log_message('error', 'Severity: '.$severity.' --> '.$message.' '.$filepath.' '.$line);
}
// --------------------------------------------------------------------
@@ -107,13 +107,21 @@ class CI_Exceptions {
*/
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);
@@ -137,16 +145,24 @@ class CI_Exceptions {
*/
public function show_error($heading, $message, $template = 'error_general', $status_code = 500)
{
- set_status_header($status_code);
-
- $message = '<p>'.implode('</p><p>', is_array($message) ? $message : array($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(VIEWPATH.'errors/'.$template.'.php');
+ include(VIEWPATH.'errors'.DIRECTORY_SEPARATOR.$template.'.php');
$buffer = ob_get_contents();
ob_end_clean();
return $buffer;
@@ -166,13 +182,22 @@ class CI_Exceptions {
public function show_php_error($severity, $message, $filepath, $line)
{
$severity = isset($this->levels[$severity]) ? $this->levels[$severity] : $severity;
- $filepath = str_replace('\\', '/', $filepath);
- // For safety reasons we do not show the full file path
- if (FALSE !== strpos($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);
+ }
+
+ $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)
@@ -180,7 +205,7 @@ class CI_Exceptions {
ob_end_flush();
}
ob_start();
- include(VIEWPATH.'errors/error_php.php');
+ include(VIEWPATH.'errors'.DIRECTORY_SEPARATOR.$template.'.php');
$buffer = ob_get_contents();
ob_end_clean();
echo $buffer;
diff --git a/system/core/Hooks.php b/system/core/Hooks.php
index b3b111991..f6eff4b3f 100644
--- a/system/core/Hooks.php
+++ b/system/core/Hooks.php
@@ -18,7 +18,7 @@
*
* @package CodeIgniter
* @author EllisLab Dev Team
- * @copyright Copyright (c) 2008 - 2013, EllisLab, Inc. (http://ellislab.com/)
+ * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (http://ellislab.com/)
* @license http://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
* @link http://codeigniter.com
* @since Version 1.0
@@ -54,6 +54,13 @@ class CI_Hooks {
public $hooks = array();
/**
+ * Array with class objects to use hooks methods
+ *
+ * @var array
+ */
+ protected $_objects = array();
+
+ /**
* In progress flag
*
* Determines whether hook is in progress, used to prevent infinte loops
@@ -184,7 +191,7 @@ class CI_Hooks {
$function = empty($data['function']) ? FALSE : $data['function'];
$params = isset($data['params']) ? $data['params'] : '';
- if ($class === FALSE && $function === FALSE)
+ if (empty($function))
{
return FALSE;
}
@@ -195,19 +202,39 @@ class CI_Hooks {
// Call the requested class and/or function
if ($class !== FALSE)
{
- if ( ! class_exists($class, FALSE))
+ // 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);
diff --git a/system/core/Input.php b/system/core/Input.php
index 24e21ea08..fdb308b5a 100644
--- a/system/core/Input.php
+++ b/system/core/Input.php
@@ -18,7 +18,7 @@
*
* @package CodeIgniter
* @author EllisLab Dev Team
- * @copyright Copyright (c) 2008 - 2013, EllisLab, Inc. (http://ellislab.com/)
+ * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (http://ellislab.com/)
* @license http://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
* @link http://codeigniter.com
* @since Version 1.0
@@ -63,7 +63,7 @@ class CI_Input {
protected $_allow_get_array = TRUE;
/**
- * Standartize new lines flag
+ * Standardize new lines flag
*
* If set to TRUE, then newlines are standardized.
*
@@ -121,9 +121,10 @@ class CI_Input {
{
log_message('debug', 'Input Class Initialized');
- $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->_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->_sandardize_newlines = (bool) config_item('standardize_newlines');
global $SEC;
$this->security =& $SEC;
@@ -151,8 +152,22 @@ class CI_Input {
* @param bool $xss_clean Whether to apply XSS filtering
* @return mixed
*/
- protected function _fetch_from_array(&$array, $index = '', $xss_clean = FALSE)
+ protected function _fetch_from_array(&$array, $index = NULL, $xss_clean = NULL)
{
+ // If $index is NULL, it means that the whole $array is requested
+ if ($index === NULL)
+ {
+ $output = array();
+ foreach (array_keys($array) as $key)
+ {
+ $output[$key] = $this->_fetch_from_array($array, $key, $xss_clean);
+ }
+
+ return $output;
+ }
+
+ is_bool($xss_clean) OR $xss_clean = $this->_enable_xss;
+
if (isset($array[$index]))
{
$value = $array[$index];
@@ -197,26 +212,8 @@ class CI_Input {
* @param bool $xss_clean Whether to apply XSS filtering
* @return mixed
*/
- public function get($index = NULL, $xss_clean = FALSE)
+ public function get($index = NULL, $xss_clean = NULL)
{
- // Check if a field has been provided
- if ($index === NULL)
- {
- if (empty($_GET))
- {
- return array();
- }
-
- $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);
}
@@ -229,26 +226,8 @@ class CI_Input {
* @param bool $xss_clean Whether to apply XSS filtering
* @return mixed
*/
- public function post($index = NULL, $xss_clean = FALSE)
+ public function post($index = NULL, $xss_clean = NULL)
{
- // Check if a field has been provided
- if ($index === NULL)
- {
- if (empty($_POST))
- {
- return array();
- }
-
- $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);
}
@@ -261,7 +240,7 @@ class CI_Input {
* @param bool $xss_clean Whether to apply XSS filtering
* @return mixed
*/
- public function post_get($index = '', $xss_clean = FALSE)
+ public function post_get($index, $xss_clean = NULL)
{
return isset($_POST[$index])
? $this->post($index, $xss_clean)
@@ -277,7 +256,7 @@ class CI_Input {
* @param bool $xss_clean Whether to apply XSS filtering
* @return mixed
*/
- public function get_post($index = '', $xss_clean = FALSE)
+ public function get_post($index, $xss_clean = NULL)
{
return isset($_GET[$index])
? $this->get($index, $xss_clean)
@@ -293,7 +272,7 @@ class CI_Input {
* @param bool $xss_clean Whether to apply XSS filtering
* @return mixed
*/
- public function cookie($index = '', $xss_clean = FALSE)
+ public function cookie($index = NULL, $xss_clean = NULL)
{
return $this->_fetch_from_array($_COOKIE, $index, $xss_clean);
}
@@ -307,7 +286,7 @@ class CI_Input {
* @param bool $xss_clean Whether to apply XSS filtering
* @return mixed
*/
- public function server($index = '', $xss_clean = FALSE)
+ public function server($index, $xss_clean = NULL)
{
return $this->_fetch_from_array($_SERVER, $index, $xss_clean);
}
@@ -323,21 +302,14 @@ class CI_Input {
* @param bool $xss_clean Whether to apply XSS filtering
* @return mixed
*/
- public function input_stream($index = '', $xss_clean = FALSE)
+ public function input_stream($index = NULL, $xss_clean = NULL)
{
// 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))
- {
- return $this->_fetch_from_array($this->_input_stream, $index, $xss_clean);
- }
-
- // Parse the input stream in our cache var
- parse_str(file_get_contents('php://input'), $this->_input_stream);
if ( ! is_array($this->_input_stream))
{
- $this->_input_stream = array();
- return NULL;
+ parse_str(file_get_contents('php://input'), $this->_input_stream);
+ is_array($this->_input_stream) OR $this->_input_stream = array();
}
return $this->_fetch_from_array($this->_input_stream, $index, $xss_clean);
@@ -361,7 +333,7 @@ class CI_Input {
* @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 = FALSE, $httponly = FALSE)
+ public function set_cookie($name, $value = '', $expire = '', $domain = '', $path = '/', $prefix = '', $secure = FALSE, $httponly = FALSE)
{
if (is_array($name))
{
@@ -687,13 +659,22 @@ class CI_Input {
// 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]);
+ }
}
}
@@ -701,12 +682,12 @@ class CI_Input {
$_SERVER['PHP_SELF'] = strip_tags($_SERVER['PHP_SELF']);
// CSRF Protection check
- if ($this->_enable_csrf === TRUE && ! $this->is_cli_request())
+ if ($this->_enable_csrf === TRUE && ! is_cli())
{
$this->security->csrf_verify();
}
- log_message('debug', 'Global POST and COOKIE data sanitized');
+ log_message('debug', 'Global POST, GET and COOKIE data sanitized');
}
// --------------------------------------------------------------------
@@ -749,13 +730,7 @@ class CI_Input {
}
// Remove control characters
- $str = remove_invisible_characters($str);
-
- // 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)
@@ -776,15 +751,25 @@ class CI_Input {
* only named with alpha-numeric text and a few other items.
*
* @param string $str Input string
- * @return string
+ * @param string $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)
+ protected function _clean_input_keys($str, $fatal = TRUE)
{
if ( ! preg_match('/^[a-z0-9:_\/|-]+$/i', $str))
{
- set_status_header(503);
- echo 'Disallowed Key Characters.';
- exit(EXIT_USER_INPUT);
+ if ($fatal === TRUE)
+ {
+ return FALSE;
+ }
+ else
+ {
+ set_status_header(503);
+ echo 'Disallowed Key Characters.';
+ exit(EXIT_USER_INPUT);
+ }
}
// Clean UTF-8 if supported
@@ -884,11 +869,12 @@ class CI_Input {
*
* 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 === 'cli' OR defined('STDIN'));
+ return is_cli();
}
// --------------------------------------------------------------------
diff --git a/system/core/Lang.php b/system/core/Lang.php
index 3236709f2..94342133a 100644
--- a/system/core/Lang.php
+++ b/system/core/Lang.php
@@ -18,7 +18,7 @@
*
* @package CodeIgniter
* @author EllisLab Dev Team
- * @copyright Copyright (c) 2008 - 2013, EllisLab, Inc. (http://ellislab.com/)
+ * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (http://ellislab.com/)
* @license http://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
* @link http://codeigniter.com
* @since Version 1.0
@@ -166,7 +166,7 @@ class CI_Lang {
* @param bool $log_errors Whether to log an error message if the line is not found
* @return string Translation
*/
- public function line($line = '', $log_errors = TRUE)
+ public function line($line, $log_errors = TRUE)
{
$value = ($line === '' OR ! isset($this->language[$line])) ? FALSE : $this->language[$line];
diff --git a/system/core/Loader.php b/system/core/Loader.php
index 1709c2db1..2d40ab5b8 100644
--- a/system/core/Loader.php
+++ b/system/core/Loader.php
@@ -18,7 +18,7 @@
*
* @package CodeIgniter
* @author EllisLab Dev Team
- * @copyright Copyright (c) 2008 - 2013, EllisLab, Inc. (http://ellislab.com/)
+ * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (http://ellislab.com/)
* @license http://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
* @link http://codeigniter.com
* @since Version 1.0
@@ -174,22 +174,29 @@ class CI_Loader {
* @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 void
+ * @return object
*/
- public function library($library = '', $params = NULL, $object_name = NULL)
+ public function library($library, $params = NULL, $object_name = NULL)
{
if (empty($library))
{
- return;
+ 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;
+ return $this;
}
if ($params !== NULL && ! is_array($params))
@@ -198,6 +205,7 @@ class CI_Loader {
}
$this->_ci_load_class($library, $params, $object_name);
+ return $this;
}
// --------------------------------------------------------------------
@@ -210,21 +218,22 @@ class CI_Loader {
* @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 void
+ * @return object
*/
public function model($model, $name = '', $db_conn = FALSE)
{
if (empty($model))
{
- return;
+ return $this;
}
elseif (is_array($model))
{
foreach ($model as $key => $value)
{
- $this->model(is_int($key) ? $value : $key, $value);
+ is_int($key) ? $this->model($value, '', $db_conn) : $this->model($key, $value, $db_conn);
}
- return;
+
+ return $this;
}
$path = '';
@@ -246,7 +255,7 @@ class CI_Loader {
if (in_array($name, $this->_ci_models, TRUE))
{
- return;
+ return $this;
}
$CI =& get_instance();
@@ -283,7 +292,7 @@ class CI_Loader {
$CI->$name = new $model();
$this->_ci_models[] = $name;
- return;
+ return $this;
}
// couldn't find the model
@@ -300,8 +309,8 @@ class CI_Loader {
* @param bool $query_builder Whether to enable Query Builder
* (overrides the configuration setting)
*
- * @return void|object|bool Database object if $return is set to TRUE,
- * FALSE on failure, void in any other case
+ * @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, $query_builder = NULL)
{
@@ -327,6 +336,7 @@ class CI_Loader {
// Load the DB class
$CI->db =& DB($params, $query_builder);
+ return $this;
}
// --------------------------------------------------------------------
@@ -335,8 +345,8 @@ class CI_Loader {
* Load the Database Utilities Class
*
* @param object $db Database object
- * @param bool $return Whether to return the DB Forge class object or not
- * @return void|object
+ * @param bool $return Whether to return the DB Utilities class object or not
+ * @return object
*/
public function dbutil($db = NULL, $return = FALSE)
{
@@ -358,6 +368,7 @@ class CI_Loader {
}
$CI->dbutil = new $class($db);
+ return $this;
}
// --------------------------------------------------------------------
@@ -367,7 +378,7 @@ class CI_Loader {
*
* @param object $db Database object
* @param bool $return Whether to return the DB Forge class object or not
- * @return void|object
+ * @return object
*/
public function dbforge($db = NULL, $return = FALSE)
{
@@ -401,6 +412,7 @@ class CI_Loader {
}
$CI->dbforge = new $class($db);
+ return $this;
}
// --------------------------------------------------------------------
@@ -415,7 +427,7 @@ class CI_Loader {
* 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 void
+ * @return object|string
*/
public function view($view, $vars = array(), $return = FALSE)
{
@@ -429,7 +441,7 @@ class CI_Loader {
*
* @param string $path File path
* @param bool $return Whether to return the file output
- * @return void|string
+ * @return object|string
*/
public function file($path, $return = FALSE)
{
@@ -448,9 +460,9 @@ class CI_Loader {
* 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 void
+ * @return object
*/
- public function vars($vars = array(), $val = '')
+ public function vars($vars, $val = '')
{
if (is_string($vars))
{
@@ -466,6 +478,23 @@ class CI_Loader {
$this->_ci_cached_vars[$key] = $val;
}
}
+
+ return $this;
+ }
+
+ // --------------------------------------------------------------------
+
+ /**
+ * Clear Cached Variables
+ *
+ * Clears the cached variables.
+ *
+ * @return object
+ */
+ public function clear_vars()
+ {
+ $this->_ci_cached_vars = array();
+ return $this;
}
// --------------------------------------------------------------------
@@ -503,7 +532,7 @@ class CI_Loader {
* Helper Loader
*
* @param string|string[] $helpers Helper name(s)
- * @return void
+ * @return object
*/
public function helper($helpers = array())
{
@@ -560,6 +589,8 @@ class CI_Loader {
show_error('Unable to load the requested file: helpers/'.$helper.'.php');
}
}
+
+ return $this;
}
// --------------------------------------------------------------------
@@ -572,11 +603,11 @@ class CI_Loader {
*
* @uses CI_Loader::helper()
* @param string|string[] $helpers Helper name(s)
- * @return void
+ * @return object
*/
public function helpers($helpers = array())
{
- $this->helper($helpers);
+ return $this->helper($helpers);
}
// --------------------------------------------------------------------
@@ -588,18 +619,19 @@ class CI_Loader {
*
* @param string|string[] $files List of language file names to load
* @param string Language name
- * @return void
+ * @return object
*/
- public function language($files = array(), $lang = '')
+ public function language($files, $lang = '')
{
$CI =& get_instance();
-
is_array($files) OR $files = array($files);
foreach ($files as $langfile)
{
$CI->lang->load($langfile, $lang);
}
+
+ return $this;
}
// --------------------------------------------------------------------
@@ -615,10 +647,9 @@ class CI_Loader {
* @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();
- return $CI->config->load($file, $use_sections, $fail_gracefully);
+ return get_instance()->config->load($file, $use_sections, $fail_gracefully);
}
// --------------------------------------------------------------------
@@ -632,10 +663,10 @@ class CI_Loader {
* @param array $params Optional parameters to pass to the driver
* @param string $object_name An optional object name to assign to
*
- * @return void|object|bool Object or FALSE on failure if $library is a string
- * and $object_name is set. void otherwise.
+ * @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 (is_array($library))
{
@@ -643,10 +674,10 @@ class CI_Loader {
{
$this->driver($driver);
}
- return;
- }
- if ($library === '')
+ return $this;
+ }
+ elseif (empty($library))
{
return FALSE;
}
@@ -682,7 +713,7 @@ class CI_Loader {
*
* @param string $path Path to add
* @param bool $view_cascade (default: TRUE)
- * @return void
+ * @return object
*/
public function add_package_path($path, $view_cascade = TRUE)
{
@@ -697,6 +728,8 @@ class CI_Loader {
// Add config file path
$config =& $this->_ci_get_component('config');
$config->_config_paths[] = $path;
+
+ return $this;
}
// --------------------------------------------------------------------
@@ -724,7 +757,7 @@ class CI_Loader {
* added path will be removed removed.
*
* @param string $path Path to remove
- * @return void
+ * @return object
*/
public function remove_package_path($path = '')
{
@@ -766,6 +799,8 @@ 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;
}
// --------------------------------------------------------------------
@@ -781,7 +816,7 @@ class CI_Loader {
* @used-by CI_Loader::view()
* @used-by CI_Loader::file()
* @param array $_ci_data Data to load
- * @return void
+ * @return object
*/
protected function _ci_load($_ci_data)
{
@@ -905,6 +940,8 @@ class CI_Loader {
$_ci_CI->output->append_output(ob_get_contents());
@ob_end_clean();
}
+
+ return $this;
}
// --------------------------------------------------------------------
diff --git a/system/core/Log.php b/system/core/Log.php
index e4d72b544..707964ccc 100644
--- a/system/core/Log.php
+++ b/system/core/Log.php
@@ -18,7 +18,7 @@
*
* @package CodeIgniter
* @author EllisLab Dev Team
- * @copyright Copyright (c) 2008 - 2013, EllisLab, Inc. (http://ellislab.com/)
+ * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (http://ellislab.com/)
* @license http://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
* @link http://codeigniter.com
* @since Version 1.0
@@ -140,10 +140,9 @@ class CI_Log {
*
* @param string the error level: 'error', 'debug' or 'info'
* @param string the error message
- * @param bool whether the error is a native PHP error
* @return bool
*/
- public function write_log($level, $msg, $php_error = FALSE)
+ public function write_log($level, $msg)
{
if ($this->_enabled === FALSE)
{
@@ -176,10 +175,18 @@ class CI_Log {
return FALSE;
}
- $message .= $level.' '.($level === 'INFO' ? ' -' : '-').' '.date($this->_date_fmt).' --> '.$msg."\n";
+ $message .= $level.' - '.date($this->_date_fmt).' --> '.$msg."\n";
flock($fp, LOCK_EX);
- fwrite($fp, $message);
+
+ for ($written = 0, $length = strlen($message); $written < $length; $written += $result)
+ {
+ if (($result = fwrite($fp, substr($message, $written))) === FALSE)
+ {
+ break;
+ }
+ }
+
flock($fp, LOCK_UN);
fclose($fp);
@@ -188,7 +195,7 @@ class CI_Log {
@chmod($filepath, FILE_WRITE_MODE);
}
- return TRUE;
+ return is_int($result);
}
}
diff --git a/system/core/Model.php b/system/core/Model.php
index 1eb6f909b..9485ec2c9 100644
--- a/system/core/Model.php
+++ b/system/core/Model.php
@@ -18,7 +18,7 @@
*
* @package CodeIgniter
* @author EllisLab Dev Team
- * @copyright Copyright (c) 2008 - 2013, EllisLab, Inc. (http://ellislab.com/)
+ * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (http://ellislab.com/)
* @license http://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
* @link http://codeigniter.com
* @since Version 1.0
@@ -59,8 +59,7 @@ class CI_Model {
*/
public function __get($key)
{
- $CI =& get_instance();
- return $CI->$key;
+ return get_instance()->$key;
}
}
diff --git a/system/core/Output.php b/system/core/Output.php
index 06d7a866b..ef56a97bf 100644
--- a/system/core/Output.php
+++ b/system/core/Output.php
@@ -18,7 +18,7 @@
*
* @package CodeIgniter
* @author EllisLab Dev Team
- * @copyright Copyright (c) 2008 - 2013, EllisLab, Inc. (http://ellislab.com/)
+ * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (http://ellislab.com/)
* @license http://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
* @link http://codeigniter.com
* @since Version 1.0
@@ -58,21 +58,21 @@ class CI_Output {
*
* @var array
*/
- public $headers = array();
+ public $headers = array();
/**
* List of mime types
*
* @var array
*/
- public $mimes = array();
+ public $mimes = array();
/**
* Mime-type for the current page
*
* @var string
*/
- protected $mime_type = 'text/html';
+ protected $mime_type = 'text/html';
/**
* Enable Profiler flag
@@ -82,11 +82,18 @@ class CI_Output {
public $enable_profiler = FALSE;
/**
- * zLib output compression flag
+ * php.ini zlib.output_compression flag
*
* @var bool
*/
- protected $_zlib_oc = FALSE;
+ protected $_zlib_oc = FALSE;
+
+ /**
+ * CI output compression flag
+ *
+ * @var bool
+ */
+ protected $_compress_output = FALSE;
/**
* List of profiler sections
@@ -102,7 +109,7 @@ class CI_Output {
*
* @var bool
*/
- public $parse_exec_vars = TRUE;
+ public $parse_exec_vars = TRUE;
/**
* Class constructor
@@ -114,6 +121,11 @@ class CI_Output {
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')
+ );
// Get mime types for later
$this->mimes =& get_mimes();
@@ -436,15 +448,14 @@ class CI_Output {
if ($this->parse_exec_vars === TRUE)
{
$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
- && extension_loaded('zlib')
+ 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)
{
ob_start('ob_gzhandler');
@@ -468,6 +479,21 @@ 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: '.strlen($output));
+ }
+ else
+ {
+ // User agent doesn't support gzip compression,
+ // so we'll have to decompress our cache
+ $output = gzinflate(substr($output, 10, -8));
+ }
+ }
+
echo $output;
log_message('debug', 'Final output sent to browser');
log_message('debug', 'Total execution time: '.$elapsed);
@@ -530,9 +556,9 @@ class CI_Output {
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();
$cache_path .= md5($uri);
@@ -542,17 +568,39 @@ class CI_Output {
return;
}
- $expire = time() + ($this->cache_expiration * 60);
-
- // Put together our serialized info.
- $cache_info = serialize(array(
- 'expire' => $expire,
- 'headers' => $this->headers
- ));
-
if (flock($fp, LOCK_EX))
{
- fwrite($fp, $cache_info.'ENDCI--->'.$output);
+ // 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);
+
+ // Put together our serialized info.
+ $cache_info = serialize(array(
+ 'expire' => $expire,
+ 'headers' => $this->headers
+ ));
+
+ $output = $cache_info.'ENDCI--->'.$output;
+
+ for ($written = 0, $length = strlen($output); $written < $length; $written += $result)
+ {
+ if (($result = fwrite($fp, substr($output, $written))) === FALSE)
+ {
+ break;
+ }
+ }
+
flock($fp, LOCK_UN);
}
else
@@ -560,13 +608,22 @@ class CI_Output {
log_message('error', 'Unable to secure a file lock for file at: '.$cache_path);
return;
}
+
fclose($fp);
- @chmod($cache_path, FILE_WRITE_MODE);
- log_message('debug', 'Cache file written: '.$cache_path);
+ if (is_int($result))
+ {
+ @chmod($cache_path, FILE_WRITE_MODE);
+ 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);
+ // Send HTTP cache-control headers to browser to match file cache settings.
+ $this->set_cache_header($_SERVER['REQUEST_TIME'], $expire);
+ }
+ else
+ {
+ @unlink($cache_path);
+ log_message('error', 'Unable to write the complete cache content at: '.$cache_path);
+ }
}
// --------------------------------------------------------------------
@@ -701,7 +758,7 @@ class CI_Output {
else
{
header('Pragma: public');
- header('Cache-Control: max-age=' . $max_age . ', 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');
}
@@ -740,13 +797,13 @@ class CI_Output {
preg_match_all('{<style.+</style>}msU', $output, $style_clean);
foreach ($style_clean[0] as $s)
{
- $output = str_replace($s, $this->_minify_script_style($s, TRUE), $output);
+ $output = str_replace($s, $this->_minify_js_css($s, 'css', TRUE), $output);
}
// Minify the javascript in <script> tags.
foreach ($javascript_clean[0] as $s)
{
- $javascript_mini[] = $this->_minify_script_style($s, TRUE);
+ $javascript_mini[] = $this->_minify_js_css($s, 'js', TRUE);
}
// Replace multiple spaces with a single space.
@@ -792,13 +849,14 @@ class CI_Output {
break;
case 'text/css':
+
+ return $this->_minify_js_css($output, 'css');
+
case 'text/javascript':
case 'application/javascript':
case 'application/x-javascript':
- $output = $this->_minify_script_style($output);
-
- break;
+ return $this->_minify_js_css($output, 'js');
default: break;
}
@@ -809,134 +867,100 @@ class CI_Output {
// --------------------------------------------------------------------
/**
- * Minify Style and Script
- *
- * Reduce excessive size of CSS/JavaScript content. To remove spaces this
- * script walks the string as an array and determines if the pointer is inside
- * a string created by single quotes or double quotes. spaces inside those
- * strings are not stripped. Opening and closing tags are severed from
- * the string initially and saved without stripping whitespace to preserve
- * the tags and any associated properties if tags are present
+ * Minify JavaScript and CSS code
*
- * Minification logic/workflow is similar to methods used by Douglas Crockford
- * in JSMIN. http://www.crockford.com/javascript/jsmin.html
+ * Strips comments and excessive whitespace characters
*
- * KNOWN ISSUE: ending a line with a closing parenthesis ')' and no semicolon
- * where there should be one will break the Javascript. New lines after a
- * closing parenthesis are not recognized by the script. For best results
- * be sure to terminate lines with a semicolon when appropriate.
- *
- * @param string $output Output to minify
- * @param bool $has_tags Specify if the output has style or script tags
- * @return string Minified output
+ * @param string $output
+ * @param string $type 'js' or 'css'
+ * @param bool $tags Whether $output contains the 'script' or 'style' tag
+ * @return string
*/
- protected function _minify_script_style($output, $has_tags = FALSE)
+ protected function _minify_js_css($output, $type, $tags = FALSE)
{
- // We only need this if there are tags in the file
- if ($has_tags === TRUE)
+ if ($tags === TRUE)
{
- // Remove opening tag and save for later
- $pos = strpos($output, '>') + 1;
- $open_tag = substr($output, 0, $pos);
- $output = substr_replace($output, '', 0, $pos);
+ $tags = array('close' => strrchr($output, '<'));
- // Remove closing tag and save it for later
- $pos = strpos($output, '</');
- $closing_tag = substr($output, $pos, strlen($output));
- $output = substr_replace($output, '', $pos);
- }
+ $open_length = strpos($output, '>') + 1;
+ $tags['open'] = substr($output, 0, $open_length);
- // Remove CSS comments
- $output = preg_replace('!/\*[^*]*\*+([^/][^*]*\*+)*/!i', '', $output);
+ $output = substr($output, $open_length, -strlen($tags['close']));
- // Remove spaces around curly brackets, colons,
- // semi-colons, parenthesis, commas
- $chunks = preg_split('/([\'|"]).+(?![^\\\]\\1)\\1/iU', $output, -1, PREG_SPLIT_OFFSET_CAPTURE);
- for ($i = count($chunks) - 1; $i >= 0; $i--)
- {
- $output = substr_replace(
- $output,
- preg_replace('/\s*(:|;|,|}|{|\(|\))\s*/i', '$1', $chunks[$i][0]),
- $chunks[$i][1],
- strlen($chunks[$i][0])
- );
+ // Strip spaces from the tags
+ $tags = preg_replace('#\s{2,}#', ' ', $tags);
}
- // Replace tabs with spaces
- // Replace carriage returns & multiple new lines with single new line
- // and trim any leading or trailing whitespace
- $output = trim(preg_replace(array('/\t+/', '/\r/', '/\n+/'), array(' ', "\n", "\n"), $output));
+ $output = trim($output);
- // Remove spaces when safe to do so.
- $in_string = $in_dstring = $prev = FALSE;
- $array_output = str_split($output);
- foreach ($array_output as $key => $value)
+ if ($type === 'js')
{
- if ($in_string === FALSE && $in_dstring === FALSE)
+ // Catch all string literals and comment blocks
+ if (preg_match_all('#((?:((?<!\\\)\'|")|(/\*)|(//)).*(?(2)(?<!\\\)\2|(?(3)\*/|\n)))#msuUS', $output, $match, PREG_OFFSET_CAPTURE))
{
- if ($value === ' ')
+ $js_literals = $js_code = array();
+ for ($match = $match[0], $c = count($match), $i = $pos = $offset = 0; $i < $c; $i++)
{
- // Get the next element in the array for comparisons
- $next = $array_output[$key + 1];
-
- // Strip spaces preceded/followed by a non-ASCII character
- // or not preceded/followed by an alphanumeric
- // or not preceded/followed \ $ and _
- if ((preg_match('/^[\x20-\x7f]*$/D', $next) OR preg_match('/^[\x20-\x7f]*$/D', $prev))
- && ( ! ctype_alnum($next) OR ! ctype_alnum($prev))
- && ! in_array($next, array('\\', '_', '$'), TRUE)
- && ! in_array($prev, array('\\', '_', '$'), TRUE)
- )
+ $js_code[$pos++] = trim(substr($output, $offset, $match[$i][1] - $offset));
+ $offset = $match[$i][1] + strlen($match[$i][0]);
+
+ // Save only if we haven't matched a comment block
+ if ($match[$i][0][0] !== '/')
{
- unset($array_output[$key]);
+ $js_literals[$pos++] = array_shift($match[$i]);
}
}
- else
- {
- // Save this value as previous for the next iteration
- // if it is not a blank space
- $prev = $value;
- }
- }
+ $js_code[$pos] = substr($output, $offset);
- if ($value === "'")
- {
- $in_string = ! $in_string;
+ // $match might be quite large, so free it up together with other vars that we no longer need
+ unset($match, $offset, $pos);
}
- elseif ($value === '"')
+ else
{
- $in_dstring = ! $in_dstring;
+ $js_code = array($output);
+ $js_literals = array();
}
+
+ $varname = 'js_code';
+ }
+ else
+ {
+ $varname = 'output';
}
- // Put the string back together after spaces have been stripped
- $output = implode($array_output);
+ // Standartize new lines
+ $$varname = str_replace(array("\r\n", "\r"), "\n", $$varname);
- // Remove new line characters unless previous or next character is
- // printable or Non-ASCII
- preg_match_all('/[\n]/', $output, $lf, PREG_OFFSET_CAPTURE);
- $removed_lf = 0;
- foreach ($lf as $feed_position)
+ if ($type === 'js')
{
- foreach ($feed_position as $position)
- {
- $position = $position[1] - $removed_lf;
- $next = $output[$position + 1];
- $prev = $output[$position - 1];
- if ( ! ctype_print($next) && ! ctype_print($prev)
- && ! preg_match('/^[\x20-\x7f]*$/D', $next)
- && ! preg_match('/^[\x20-\x7f]*$/D', $prev)
- )
- {
- $output = substr_replace($output, '', $position, 1);
- $removed_lf++;
- }
- }
+ $patterns = array(
+ '#\s*([!\#%&()*+,\-./:;<=>?@\[\]^`{|}~])\s*#' => '$1', // Remove spaces following and preceeding JS-wise non-special & non-word characters
+ '#\s{2,}#' => ' ' // Reduce the remaining multiple whitespace characters to a single space
+ );
+ }
+ else
+ {
+ $patterns = array(
+ '#/\*.*(?=\*/)\*/#s' => '', // Remove /* block comments */
+ '#\n?//[^\n]*#' => '', // Remove // line comments
+ '#\s*([^\w.\#%])\s*#U' => '$1', // Remove spaces following and preceeding non-word characters, excluding dots, hashes and the percent sign
+ '#\s{2,}#' => ' ' // Reduce the remaining multiple space characters to a single space
+ );
+ }
+
+ $$varname = preg_replace(array_keys($patterns), array_values($patterns), $$varname);
+
+ // Glue back JS quoted strings
+ if ($type === 'js')
+ {
+ $js_code += $js_literals;
+ ksort($js_code);
+ $output = implode($js_code);
+ unset($js_code, $js_literals, $varname, $patterns);
}
- // Put the opening and closing tags back if applicable
- return isset($open_tag)
- ? $open_tag.$output.$closing_tag
+ return is_array($tags)
+ ? $tags['open'].$output.$tags['close']
: $output;
}
diff --git a/system/core/Router.php b/system/core/Router.php
index 0f7278ae6..05263b153 100644
--- a/system/core/Router.php
+++ b/system/core/Router.php
@@ -18,7 +18,7 @@
*
* @package CodeIgniter
* @author EllisLab Dev Team
- * @copyright Copyright (c) 2008 - 2013, EllisLab, Inc. (http://ellislab.com/)
+ * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (http://ellislab.com/)
* @license http://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
* @link http://codeigniter.com
* @since Version 1.0
@@ -91,6 +91,15 @@ class CI_Router {
*/
public $translate_uri_dashes = FALSE;
+ /**
+ * Enable query strings flag
+ *
+ * Determines wether to use GET parameters or segment URIs
+ *
+ * @var bool
+ */
+ public $enable_query_strings = FALSE;
+
// --------------------------------------------------------------------
/**
@@ -102,9 +111,34 @@ class CI_Router {
*/
public function __construct()
{
+ global $routing;
+
$this->config =& load_class('Config', 'core');
$this->uri =& load_class('URI', 'core');
+
+ $this->enable_query_strings = ( ! is_cli() && $this->config->item('enable_query_strings') === TRUE);
$this->_set_routing();
+
+ // Set any routing overrides that may exist in the main index file
+ if (isset($routing) && is_array($routing))
+ {
+ if (isset($routing['directory']))
+ {
+ $this->set_directory($routing['directory']);
+ }
+
+ if ( ! empty($routing['controller']))
+ {
+ $this->set_class($routing['controller']);
+ }
+
+ if (isset($routing['function']))
+ {
+ $routing['function'] = empty($routing['function']) ? 'index' : $routing['function'];
+ $this->set_method($routing['function']);
+ }
+ }
+
log_message('debug', 'Router Class Initialized');
}
@@ -123,26 +157,39 @@ class CI_Router {
// 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
- && ! empty($_GET[$this->config->item('controller_trigger')])
- && is_string($_GET[$this->config->item('controller_trigger')])
- )
+ if ($this->enable_query_strings)
{
- if (isset($_GET[$this->config->item('directory_trigger')]) && is_string($_GET[$this->config->item('directory_trigger')]))
+ $_d = $this->config->item('directory_trigger');
+ $_d = isset($_GET[$_d]) ? trim($_GET[$_d], " \t\n\r\0\x0B/") : '';
+ if ($_d !== '')
{
- $this->set_directory(trim($this->uri->_filter_uri($_GET[$this->config->item('directory_trigger')])));
- $segments[] = $this->directory;
+ $this->set_directory($this->uri->filter_uri($_d));
}
- $this->set_class(trim($this->uri->_filter_uri($_GET[$this->config->item('controller_trigger')])));
- $segments[] = $this->class;
+ $_c = $this->config->item('controller_trigger');
+ if ( ! empty($_GET[$_c]))
+ {
+ $this->set_class(trim($this->uri->filter_uri(trim($_GET[$_c]))));
+
+ $_f = $this->config->item('function_trigger');
+ if ( ! empty($_GET[$_f]))
+ {
+ $this->set_method(trim($this->uri->filter_uri($_GET[$_f])));
+ }
- if ( ! empty($_GET[$this->config->item('function_trigger')]) && is_string($_GET[$this->config->item('function_trigger')]))
+ $this->uri->rsegments = array(
+ 1 => $this->class,
+ 2 => $this->method
+ );
+ }
+ else
{
- $this->set_method(trim($this->uri->_filter_uri($_GET[$this->config->item('function_trigger')])));
- $segments[] = $this->method;
+ $this->_set_default_controller();
}
+
+ // Routing rules don't apply to query strings and we don't need to detect
+ // directories, so we're done here
+ return;
}
// Load the routes.php file.
@@ -165,53 +212,15 @@ class CI_Router {
$this->routes = $route;
}
- // Were there any query string segments? If so, we'll validate them and bail out since we're done.
- if (count($segments) > 0)
+ // Is there anything to parse?
+ if ($this->uri->uri_string !== '')
{
- return $this->_validate_request($segments);
+ $this->_parse_routes();
}
-
- // 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 == '')
+ else
{
- return $this->_set_default_controller();
+ $this->_set_default_controller();
}
-
- $this->uri->_remove_url_suffix(); // Remove the URL suffix
- $this->uri->_explode_segments(); // Compile the segments into an array
- $this->_parse_routes(); // Parse any custom routing that may exist
- $this->uri->_reindex_segments(); // Re-index the segment array so that it starts with 1 rather than 0
- }
-
- // --------------------------------------------------------------------
-
- /**
- * Set default controller
- *
- * @return void
- */
- protected function _set_default_controller()
- {
- if (empty($this->default_controller))
- {
- show_error('Unable to determine what should be displayed. A default route has not been specified in the routing file.');
- }
-
- // Is the method being specified?
- if (sscanf($this->default_controller, '%[^/]/%s', $class, $method) !== 2)
- {
- $method = 'index';
- }
-
- $this->_set_request(array($class, $method));
-
- // 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.');
}
// --------------------------------------------------------------------
@@ -222,16 +231,19 @@ class CI_Router {
* Takes an array of URI segments as input and sets the class/method
* to be called.
*
+ * @used-by CI_Router::_parse_routes()
* @param array $segments URI segments
* @return void
*/
protected function _set_request($segments = array())
{
$segments = $this->_validate_request($segments);
-
- if (count($segments) === 0)
+ // If we don't have any segments left - try the default controller;
+ // WARNING: Directories get shifted out of the segments array!
+ if (empty($segments))
{
- return $this->_set_default_controller();
+ $this->_set_default_controller();
+ return;
}
if ($this->translate_uri_dashes === TRUE)
@@ -244,90 +256,86 @@ class CI_Router {
}
$this->set_class($segments[0]);
- isset($segments[1]) OR $segments[1] = 'index';
- $this->set_method($segments[1]);
+ if (isset($segments[1]))
+ {
+ $this->set_method($segments[1]);
+ }
- // 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;
}
// --------------------------------------------------------------------
/**
- * Validate request
- *
- * Attempts validate the URI request and determine the controller path.
+ * Set default controller
*
- * @param array $segments URI segments
- * @return array URI segments
+ * @return void
*/
- protected 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.');
}
- $test = ucfirst($this->translate_uri_dashes === TRUE ? str_replace('-', '_', $segments[0]) : $segments[0]);
-
- // Does the requested controller exist in the root folder?
- if (file_exists(APPPATH.'controllers/'.$test.'.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(array_shift($segments));
- if (count($segments) > 0)
- {
- $test = ucfirst($this->translate_uri_dashes === TRUE ? str_replace('-', '_', $segments[0]) : $segments[0]);
+ // This will trigger 404 later
+ return;
+ }
- // Does the requested controller exist in the sub-directory?
- if ( ! file_exists(APPPATH.'controllers/'.$this->directory.$test.'.php'))
- {
- if ( ! empty($this->routes['404_override']))
- {
- $this->directory = '';
- return explode('/', $this->routes['404_override'], 2);
- }
- else
- {
- show_404($this->directory.$segments[0]);
- }
- }
- }
- else
- {
- // Is the method being specified in the route?
- $segments = explode('/', $this->default_controller);
- if ( ! file_exists(APPPATH.'controllers/'.$this->directory.ucfirst($segments[0]).'.php'))
- {
- $this->directory = '';
- }
- }
+ $this->set_class($class);
+ $this->set_method($method);
- return $segments;
- }
+ // Assign routed segments, index starting from 1
+ $this->uri->rsegments = array(
+ 1 => $class,
+ 2 => $method
+ );
- // 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']))
+ log_message('debug', 'No URI present. Default controller set.');
+ }
+
+ // --------------------------------------------------------------------
+
+ /**
+ * 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);
+ // Loop through our segments and return as soon as a controller
+ // is found or when such a directory doesn't exist
+ while ($c-- > 0)
{
- if (sscanf($this->routes['404_override'], '%[^/]/%s', $class, $method) !== 2)
+ $test = $this->directory
+ .ucfirst($this->translate_uri_dashes === TRUE ? str_replace('-', '_', $segments[0]) : $segments[0]);
+
+ if ( ! file_exists(APPPATH.'controllers/'.$test.'.php') && is_dir(APPPATH.'controllers/'.$this->directory.$segments[0]))
{
- $method = 'index';
+ $this->set_directory(array_shift($segments), TRUE);
+ continue;
}
- return array($class, $method);
+ 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;
}
// --------------------------------------------------------------------
@@ -345,15 +353,42 @@ class CI_Router {
// Turn the segment array into a URI string
$uri = implode('/', $this->uri->segments);
+ // Get HTTP verb
+ $http_verb = isset($_SERVER['REQUEST_METHOD']) ? strtolower($_SERVER['REQUEST_METHOD']) : 'cli';
+
// Is there a literal match? If so we're done
- if (isset($this->routes[$uri]) && is_string($this->routes[$uri]))
+ if (isset($this->routes[$uri]))
{
- return $this->_set_request(explode('/', $this->routes[$uri]));
+ // Check default routes format
+ if (is_string($this->routes[$uri]))
+ {
+ $this->_set_request(explode('/', $this->routes[$uri]));
+ return;
+ }
+ // Is there a matching http verb?
+ elseif (is_array($this->routes[$uri]) && isset($this->routes[$uri][$http_verb]))
+ {
+ $this->_set_request(explode('/', $this->routes[$uri][$http_verb]));
+ return;
+ }
}
// Loop through the route array looking for wildcards
foreach ($this->routes as $key => $val)
{
+ // Check if route format is using http verb
+ if (is_array($val))
+ {
+ 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);
@@ -404,13 +439,14 @@ class CI_Router {
$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));
}
// --------------------------------------------------------------------
@@ -471,11 +507,19 @@ class CI_Router {
* Set directory name
*
* @param string $dir Directory name
+ * @param bool $appent Whether we're appending rather then setting the full value
* @return void
*/
- public 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, '/')).'/';
+ }
}
// --------------------------------------------------------------------
@@ -494,38 +538,6 @@ class CI_Router {
return $this->directory;
}
- // --------------------------------------------------------------------
-
- /**
- * Set controller overrides
- *
- * @param array $routing Route overrides
- * @return void
- */
- public function _set_overrides($routing)
- {
- if ( ! is_array($routing))
- {
- return;
- }
-
- if (isset($routing['directory']))
- {
- $this->set_directory($routing['directory']);
- }
-
- if ( ! empty($routing['controller']))
- {
- $this->set_class($routing['controller']);
- }
-
- if (isset($routing['function']))
- {
- $routing['function'] = empty($routing['function']) ? 'index' : $routing['function'];
- $this->set_method($routing['function']);
- }
- }
-
}
/* End of file Router.php */
diff --git a/system/core/Security.php b/system/core/Security.php
index 70cf3e013..faa52d746 100644
--- a/system/core/Security.php
+++ b/system/core/Security.php
@@ -18,7 +18,7 @@
*
* @package CodeIgniter
* @author EllisLab Dev Team
- * @copyright Copyright (c) 2008 - 2013, EllisLab, Inc. (http://ellislab.com/)
+ * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (http://ellislab.com/)
* @license http://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
* @link http://codeigniter.com
* @since Version 1.0
@@ -62,6 +62,19 @@ class CI_Security {
);
/**
+ * HTML5 entities
+ *
+ * @var array
+ */
+ public $html5_entities = array(
+ '&colon;' => ':',
+ '&lpar;' => '(',
+ '&rpar;' => ')',
+ '&newline;' => "\n",
+ '&tab;' => "\t"
+ );
+
+ /**
* XSS Hash
*
* Random Hash for protecting URLs.
@@ -117,7 +130,6 @@ class CI_Security {
'document.write' => '[removed]',
'.parentNode' => '[removed]',
'.innerHTML' => '[removed]',
- 'window.location' => '[removed]',
'-moz-binding' => '[removed]',
'<!--' => '&lt;!--',
'-->' => '--&gt;',
@@ -132,9 +144,13 @@ class CI_Security {
*/
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?"
);
@@ -343,7 +359,11 @@ class CI_Security {
*
* Note: Use rawurldecode() so it does not remove plus signs
*/
- $str = rawurldecode($str);
+ do
+ {
+ $str = rawurldecode($str);
+ }
+ while (preg_match('/%[0-9a-f]{2,}/i', $str));
/*
* Convert character entities to ASCII
@@ -402,8 +422,9 @@ 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'
);
foreach ($words as $word)
@@ -420,6 +441,12 @@ class CI_Security {
* We used to do some version comparisons and use of stripos for PHP5,
* 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
{
@@ -427,12 +454,12 @@ class CI_Security {
if (preg_match('/<a/i', $str))
{
- $str = preg_replace_callback('#<a\s+([^>]*?)(?:>|$)#si', array($this, '_js_link_removal'), $str);
+ $str = preg_replace_callback('#<a[\s\d"\'`;/=,\(\\\\]+([^>]*?)(?:>|$)#si', array($this, '_js_link_removal'), $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[\s\d"\'`;/=,\(\\\\]+([^>]*?)(?:\s?/?>|$)#si', array($this, '_js_img_removal'), $str);
}
if (preg_match('/script|xss/i', $str))
@@ -456,7 +483,7 @@ class CI_Security {
* So this: <blink>
* Becomes: &lt;blink&gt;
*/
- $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';
+ $naughty = 'alert|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';
$str = preg_replace_callback('#<(/*\s*)('.$naughty.')([^><]*)([><]*)#is', array($this, '_sanitize_naughty_html'), $str);
/*
@@ -471,7 +498,7 @@ class CI_Security {
* For example: eval('some code')
* Becomes: eval&#40;'some code'&#41;
*/
- $str = preg_replace('#(alert|cmd|passthru|eval|exec|expression|system|fopen|fsockopen|file|file_get_contents|readfile|unlink)(\s*)\((.*?)\)#si',
+ $str = preg_replace('#(alert|prompt|confirm|cmd|passthru|eval|exec|expression|system|fopen|fsockopen|file|file_get_contents|readfile|unlink)(\s*)\((.*?)\)#si',
'\\1\\2&#40;\\3&#41;',
$str);
@@ -551,13 +578,13 @@ class CI_Security {
do
{
- $matches = $matches1 = 0;
+ $m1 = $m2 = 0;
+ $str = preg_replace('/(&#x0*[0-9a-f]{2,5})(?![0-9a-f;])/iS', '$1;', $str, -1, $m1);
+ $str = preg_replace('/(&#\d{2,4})(?![0-9;])/S', '$1;', $str, -1, $m2);
$str = html_entity_decode($str, ENT_COMPAT, $charset);
- $str = preg_replace('~&#x(0*[0-9a-f]{2,5})~ei', 'chr(hexdec("\\1"))', $str, -1, $matches);
- $str = preg_replace('~&#([0-9]{2,4})~e', 'chr(\\1)', $str, -1, $matches1);
}
- while ($matches OR $matches1);
+ while ($m1 OR $m2);
return $str;
}
@@ -603,7 +630,7 @@ class CI_Security {
*/
public function strip_image_tags($str)
{
- return preg_replace(array('#<img\s+.*?src\s*=\s*["\'](.+?)["\'].*?\>#', '#<img\s+.*?src\s*=\s*(.+?).*?\>#'), '\\1', $str);
+ return preg_replace(array('#<img[\s/]+.*?src\s*=\s*["\'](.+?)["\'].*?\>#', '#<img[\s/]+.*?src\s*=\s*(.+?).*?\>#'), '\\1', $str);
}
// ----------------------------------------------------------------
@@ -648,8 +675,7 @@ class CI_Security {
*/
protected function _remove_evil_attributes($str, $is_image)
{
- // All javascript event handlers (e.g. onload, onclick, onmouseover), style, and xmlns
- $evil_attributes = array('on\w*', 'style', 'xmlns', 'formaction');
+ $evil_attributes = array('on\w*', 'style', 'xmlns', 'formaction', 'form', 'xlink:href');
if ($is_image === TRUE)
{
@@ -665,7 +691,7 @@ class CI_Security {
$attribs = array();
// 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);
+ preg_match_all('/(?<!\w)('.implode('|', $evil_attributes).')\s*=\s*(\042|\047)([^\\2]*?)(\\2)/is', $str, $matches, PREG_SET_ORDER);
foreach ($matches as $attr)
{
@@ -673,7 +699,7 @@ class CI_Security {
}
// find occurrences of illegal attribute strings without quotes
- preg_match_all('/('.implode('|', $evil_attributes).')\s*=\s*([^\s>]*)/is', $str, $matches, PREG_SET_ORDER);
+ preg_match_all('/(?<!\w)('.implode('|', $evil_attributes).')\s*=\s*([^\s>]*)/is', $str, $matches, PREG_SET_ORDER);
foreach ($matches as $attr)
{
@@ -727,7 +753,7 @@ class CI_Security {
protected function _js_link_removal($match)
{
return str_replace($match[1],
- preg_replace('#href=.*?(?:alert\(|alert&\#40;|javascript:|livescript:|mocha:|charset=|window\.|document\.|\.cookie|<script|<xss|data\s*:)#si',
+ preg_replace('#href=.*?(?:(?:alert|prompt|confirm)(?:\(|&\#40;)|javascript:|livescript:|mocha:|charset=|window\.|document\.|\.cookie|<script|<xss|data\s*:)#si',
'',
$this->_filter_attributes(str_replace(array('<', '>'), '', $match[1]))
),
@@ -752,7 +778,7 @@ class CI_Security {
protected function _js_img_removal($match)
{
return str_replace($match[1],
- preg_replace('#src=.*?(?:alert\(|alert&\#40;|javascript:|livescript:|mocha:|charset=|window\.|document\.|\.cookie|<script|<xss|base64\s*,)#si',
+ preg_replace('#src=.*?(?:(?:alert|prompt|confirm)(?:\(|&\#40;)|javascript:|livescript:|mocha:|charset=|window\.|document\.|\.cookie|<script|<xss|base64\s*,)#si',
'',
$this->_filter_attributes(str_replace(array('<', '>'), '', $match[1]))
),
@@ -810,7 +836,14 @@ class CI_Security {
*/
protected function _decode_entity($match)
{
- return $this->entity_decode($match[0], strtoupper(config_item('charset')));
+ // entity_decode() won't convert dangerous HTML5 entities
+ // (it could, but ENT_HTML5 is only available since PHP 5.4),
+ // so we'll do that here
+ return str_ireplace(
+ array_keys($this->html5_entities),
+ array_values($this->html5_entities),
+ $this->entity_decode($match[0], strtoupper(config_item('charset')))
+ );
}
// --------------------------------------------------------------------
@@ -837,14 +870,15 @@ class CI_Security {
* 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);
+ $str = preg_replace('/(&#\d{2,4})(?![0-9;])/', '$1;', $str);
+ $str = preg_replace('/(&[a-z]{2,})(?![a-z;])/i', '$1;', $str);
/*
* 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);
+ $str = preg_replace('/(&#x0*[0-9a-f]{2,5})(?![0-9a-f;])/i', '$1;', $str);
/*
* Un-Protect GET variables in URLs
@@ -884,7 +918,7 @@ class CI_Security {
{
if ($this->_csrf_hash === '')
{
- // 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
@@ -894,7 +928,7 @@ class CI_Security {
return $this->_csrf_hash = $_COOKIE[$this->_csrf_cookie_name];
}
- $this->_csrf_hash = md5(uniqid(rand(), TRUE));
+ $this->_csrf_hash = md5(uniqid(mt_rand(), TRUE));
$this->csrf_set_cookie();
}
diff --git a/system/core/URI.php b/system/core/URI.php
index bc086d223..15d6263be 100644
--- a/system/core/URI.php
+++ b/system/core/URI.php
@@ -18,7 +18,7 @@
*
* @package CodeIgniter
* @author EllisLab Dev Team
- * @copyright Copyright (c) 2008 - 2013, EllisLab, Inc. (http://ellislab.com/)
+ * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (http://ellislab.com/)
* @license http://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
* @link http://codeigniter.com
* @since Version 1.0
@@ -44,21 +44,21 @@ class CI_URI {
*
* @var array
*/
- public $keyval = array();
+ public $keyval = array();
/**
* Current URI string
*
* @var string
*/
- public $uri_string;
+ public $uri_string = '';
/**
* List of URI segments
*
* @var array
*/
- public $segments = array();
+ public $segments = array();
/**
* Re-indexed list of URI segments
@@ -67,90 +67,67 @@ class CI_URI {
*
* @var array
*/
- public $rsegments = array();
+ public $rsegments = array();
/**
- * Class 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
*
- * @return void
+ * @var string
*/
- public function __construct()
- {
- $this->config =& load_class('Config', 'core');
- log_message('debug', 'URI Class Initialized');
- }
-
- // --------------------------------------------------------------------
+ protected $_permitted_uri_chars;
/**
- * Fetch URI String
+ * Class constructor
*
- * @used-by CI_Router
* @return void
*/
- public function _fetch_uri_string()
+ public function __construct()
{
- $protocol = strtoupper($this->config->item('uri_protocol'));
+ $this->config =& load_class('Config', 'core');
- if ($protocol === 'AUTO')
+ // 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)
{
- // Is the request coming from the command line?
- if ($this->_is_cli_request())
+ $this->_permitted_uri_chars = $this->config->item('permitted_uri_chars');
+
+ // If it's a CLI request, ignore the configuration
+ if (is_cli() OR ($protocol = strtoupper($this->config->item('uri_protocol'))) === 'CLI')
{
$this->_set_uri_string($this->_parse_argv());
- return;
}
-
- // Is there a PATH_INFO variable? This should be the easiest solution.
- if (isset($_SERVER['PATH_INFO']))
+ elseif ($protocol === 'AUTO')
{
- $this->_set_uri_string($_SERVER['PATH_INFO']);
- return;
+ // Is there a PATH_INFO variable? This should be the easiest solution.
+ if (isset($_SERVER['PATH_INFO']))
+ {
+ $this->_set_uri_string($_SERVER['PATH_INFO']);
+ }
+ // No PATH_INFO? Let's try REQUST_URI or QUERY_STRING then
+ elseif (($uri = $this->_parse_request_uri()) !== '' OR ($uri = $this->_parse_query_string()) !== '')
+ {
+ $this->_set_uri_string($uri);
+ }
+ // As a last ditch effor, let's try using the $_GET array
+ elseif (is_array($_GET) && count($_GET) === 1 && trim(key($_GET), '/') !== '')
+ {
+ $this->_set_uri_string(key($_GET));
+ }
}
-
- // Let's try REQUEST_URI then, this will work in most situations
- if (($uri = $this->_parse_request_uri()) !== '')
+ elseif (method_exists($this, ($method = '_parse_'.strtolower($protocol))))
{
- $this->_set_uri_string($uri);
- return;
+ $this->_set_uri_string($this->$method());
}
-
- // No REQUEST_URI either?... What about QUERY_STRING?
- if (($uri = $this->_parse_query_string()) !== '')
+ else
{
+ $uri = isset($_SERVER[$protocol]) ? $_SERVER[$protocol] : @getenv($protocol);
$this->_set_uri_string($uri);
- return;
- }
-
- // As a last ditch effort let's try using the $_GET array
- if (is_array($_GET) && count($_GET) === 1 && trim(key($_GET), '/') !== '')
- {
- $this->_set_uri_string(key($_GET));
- return;
}
-
- // We've exhausted all our options...
- $this->uri_string = '';
- return;
}
- if ($protocol === 'CLI')
- {
- $this->_set_uri_string($this->_parse_argv());
- return;
- }
- elseif (method_exists($this, ($method = '_parse_'.strtolower($protocol))))
- {
- $this->_set_uri_string($this->$method());
- return;
- }
-
- $uri = isset($_SERVER[$protocol]) ? $_SERVER[$protocol] : @getenv($protocol);
- $this->_set_uri_string($uri);
+ log_message('debug', 'URI Class Initialized');
}
// --------------------------------------------------------------------
@@ -165,6 +142,35 @@ class CI_URI {
{
// 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);
+ }
+ }
+
+ $this->segments[0] = NULL;
+ // Populate the segments array
+ foreach (explode('/', preg_replace('|/*(.+?)/*$|', '\\1', $this->uri_string)) as $val)
+ {
+ // Filter segments for security
+ $val = trim($this->filter_uri($val));
+
+ if ($val !== '')
+ {
+ $this->segments[] = $val;
+ }
+ }
+
+ unset($this->segments[0]);
+ }
}
// --------------------------------------------------------------------
@@ -225,36 +231,10 @@ class CI_URI {
// --------------------------------------------------------------------
/**
- * Remove relative directory (../) and multi slashes (///)
- *
- * Do some final cleaning of the URI and return it, currently only used in self::_parse_request_uri()
- *
- * @param string $url
- * @return string
- */
- protected function _remove_relative_directory($uri)
- {
- $uris = array();
- $tok = strtok($uri, '/');
- while ($tok !== FALSE)
- {
- if (( ! empty($tok) OR $tok === '0') && $tok !== '..')
- {
- $uris[] = $tok;
- }
- $tok = strtok('/');
- }
- return implode('/', $uris);
- }
-
- // --------------------------------------------------------------------
-
- /**
* Parse QUERY_STRING
*
* Will parse QUERY_STRING and automatically detect the URI from it.
*
- * @used-by CI_URI::_fetch_uri_string()
* @return string
*/
protected function _parse_query_string()
@@ -280,23 +260,6 @@ class CI_URI {
// --------------------------------------------------------------------
/**
- * Is CLI Request?
- *
- * Duplicate of method from the Input class to test to see if
- * a request was made from the command line.
- *
- * @see CI_Input::is_cli_request()
- * @used-by CI_URI::_fetch_uri_string()
- * @return bool
- */
- protected function _is_cli_request()
- {
- return (PHP_SAPI === 'cli') OR defined('STDIN');
- }
-
- // --------------------------------------------------------------------
-
- /**
* Parse CLI arguments
*
* Take each command line argument and assume it is a URI segment.
@@ -312,104 +275,52 @@ class CI_URI {
// --------------------------------------------------------------------
/**
- * Filter URI
+ * Remove relative directory (../) and multi slashes (///)
*
- * Filters segments for malicious characters.
+ * Do some final cleaning of the URI and return it, currently only used in self::_parse_request_uri()
*
- * @used-by CI_Router
- * @param string $str
+ * @param string $url
* @return string
*/
- public function _filter_uri($str)
+ protected function _remove_relative_directory($uri)
{
- if ($str !== '' && $this->config->item('permitted_uri_chars') != '' && $this->config->item('enable_query_strings') === FALSE)
+ $uris = array();
+ $tok = strtok($uri, '/');
+ while ($tok !== FALSE)
{
- // 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))
+ if (( ! empty($tok) OR $tok === '0') && $tok !== '..')
{
- show_error('The URI you submitted has disallowed characters.', 400);
+ $uris[] = $tok;
}
+ $tok = strtok('/');
}
- // Convert programatic characters to entities and return
- return str_replace(
- array('$', '(', ')', '%28', '%29'), // Bad
- array('&#36;', '&#40;', '&#41;', '&#40;', '&#41;'), // Good
- $str);
- }
-
- // --------------------------------------------------------------------
-
- /**
- * Remove URL suffix
- *
- * Removes the suffix from the URL if needed.
- *
- * @used-by CI_Router
- * @return void
- */
- public function _remove_url_suffix()
- {
- $suffix = (string) $this->config->item('url_suffix');
-
- if ($suffix === '')
- {
- return;
- }
-
- $slen = strlen($suffix);
-
- if (substr($this->uri_string, -$slen) === $suffix)
- {
- $this->uri_string = substr($this->uri_string, 0, -$slen);
- }
+ return implode('/', $uris);
}
// --------------------------------------------------------------------
/**
- * Explode URI segments
+ * Filter URI
*
- * The individual segments will be stored in the $this->segments array.
+ * Filters segments for malicious characters.
*
- * @see CI_URI::$segments
- * @used-by CI_Router
- * @return void
+ * @param string $str
+ * @return string
*/
- public function _explode_segments()
+ public function filter_uri($str)
{
- foreach (explode('/', preg_replace('|/*(.+?)/*$|', '\\1', $this->uri_string)) as $val)
+ if ( ! empty($str) && ! empty($this->_permitted_uri_chars) && ! preg_match('/^['.$this->_permitted_uri_chars.']+$/i'.(UTF8_ENABLED ? 'u' : ''), $str))
{
- // Filter segments for security
- $val = trim($this->_filter_uri($val));
-
- if ($val !== '')
- {
- $this->segments[] = $val;
- }
+ show_error('The URI you submitted has disallowed characters.', 400);
}
- }
- // --------------------------------------------------------------------
-
- /**
- * Re-index Segments
- *
- * Re-indexes the CI_URI::$segment array so that it starts at 1 rather
- * than 0. Doing so makes it simpler to use methods like
- * CI_URI::segment(n) since there is a 1:1 relationship between the
- * segment array and the actual segments.
- *
- * @used-by CI_Router
- * @return void
- */
- public function _reindex_segments()
- {
- array_unshift($this->segments, NULL);
- array_unshift($this->rsegments, NULL);
- unset($this->segments[0]);
- unset($this->rsegments[0]);
+ // Convert programatic characters to entities and return
+ return str_replace(
+ array('$', '(', ')', '%28', '%29'), // Bad
+ array('&#36;', '&#40;', '&#41;', '&#40;', '&#41;'), // Good
+ $str
+ );
}
// --------------------------------------------------------------------
@@ -720,12 +631,7 @@ class CI_URI {
{
global $RTR;
- if (($dir = $RTR->directory) === '/')
- {
- $dir = '';
- }
-
- return $dir.implode('/', $this->rsegment_array());
+ return ltrim($RTR->directory, '/').implode('/', $this->rsegments);
}
}
diff --git a/system/core/Utf8.php b/system/core/Utf8.php
index a78616d40..6ca1a02ca 100644
--- a/system/core/Utf8.php
+++ b/system/core/Utf8.php
@@ -18,7 +18,7 @@
*
* @package CodeIgniter
* @author EllisLab Dev Team
- * @copyright Copyright (c) 2008 - 2013, EllisLab, Inc. (http://ellislab.com/)
+ * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (http://ellislab.com/)
* @license http://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
* @link http://codeigniter.com
* @since Version 2.0
@@ -48,28 +48,10 @@ class CI_Utf8 {
*/
public function __construct()
{
- log_message('debug', 'Utf8 Class Initialized');
-
- $charset = strtoupper(config_item('charset'));
-
- // 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($charset);
- }
- else
- {
- define('MB_ENABLED', FALSE);
- }
-
if (
- @preg_match('/./u', 'é') === 1 // PCRE must support UTF-8
- && function_exists('iconv') // iconv must be installed
- && MB_ENABLED === TRUE // mbstring must be enabled
- && $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
+ && strnatcasecmp(config_item('charset'), 'UTF-8') === 0 // Application charset must be UTF-8
)
{
define('UTF8_ENABLED', TRUE);
@@ -80,6 +62,8 @@ class CI_Utf8 {
define('UTF8_ENABLED', FALSE);
log_message('debug', 'UTF-8 Support Disabled');
}
+
+ log_message('debug', 'Utf8 Class Initialized');
}
// --------------------------------------------------------------------
@@ -98,7 +82,14 @@ class CI_Utf8 {
{
if ($this->_is_ascii($str) === FALSE)
{
- $str = @iconv('UTF-8', 'UTF-8//IGNORE', $str);
+ if (ICONV_ENABLED)
+ {
+ $str = @iconv('UTF-8', 'UTF-8//IGNORE', $str);
+ }
+ elseif (MB_ENABLED)
+ {
+ $str = mb_convert_encoding($str, 'UTF-8', 'UTF-8');
+ }
}
return $str;
@@ -134,7 +125,7 @@ class CI_Utf8 {
*/
public function convert_to_utf8($str, $encoding)
{
- if (function_exists('iconv'))
+ if (ICONV_ENABLED)
{
return @iconv($encoding, 'UTF-8', $str);
}
diff --git a/system/core/compat/mbstring.php b/system/core/compat/mbstring.php
new file mode 100644
index 000000000..91ea8017c
--- /dev/null
+++ b/system/core/compat/mbstring.php
@@ -0,0 +1,141 @@
+<?php
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP 5.2.4 or newer
+ *
+ * NOTICE OF LICENSE
+ *
+ * Licensed under the Open Software License version 3.0
+ *
+ * This source file is subject to the Open Software License (OSL 3.0) that is
+ * bundled with this package in the files license.txt / license.rst. It is
+ * also available through the world wide web at this URL:
+ * http://opensource.org/licenses/OSL-3.0
+ * If you did not receive a copy of the license and are unable to obtain it
+ * through the world wide web, please send an email to
+ * licensing@ellislab.com so we can send you a copy immediately.
+ *
+ * @package CodeIgniter
+ * @author EllisLab Dev Team
+ * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (http://ellislab.com/)
+ * @license http://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
+ * @link http://codeigniter.com
+ * @since Version 3.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 http://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 string
+ */
+ function mb_strlen($str, $encoding = NULL)
+ {
+ if (ICONV_ENABLED === TRUE)
+ {
+ return iconv_strlen($str, isset($charset) ? $charset : 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);
+ }
+}
+
+/* End of file mbstring.php */
+/* Location: ./system/core/compat/mbstring.php */ \ No newline at end of file
diff --git a/system/core/compat/password.php b/system/core/compat/password.php
new file mode 100644
index 000000000..92fdedb99
--- /dev/null
+++ b/system/core/compat/password.php
@@ -0,0 +1,216 @@
+<?php
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP 5.2.4 or newer
+ *
+ * NOTICE OF LICENSE
+ *
+ * Licensed under the Open Software License version 3.0
+ *
+ * This source file is subject to the Open Software License (OSL 3.0) that is
+ * bundled with this package in the files license.txt / license.rst. It is
+ * also available through the world wide web at this URL:
+ * http://opensource.org/licenses/OSL-3.0
+ * If you did not receive a copy of the license and are unable to obtain it
+ * through the world wide web, please send an email to
+ * licensing@ellislab.com so we can send you a copy immediately.
+ *
+ * @package CodeIgniter
+ * @author EllisLab Dev Team
+ * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (http://ellislab.com/)
+ * @license http://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
+ * @link http://codeigniter.com
+ * @since Version 3.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 http://codeigniter.com/user_guide/
+ * @link http://php.net/password
+ */
+
+// ------------------------------------------------------------------------
+
+if (is_php('5.5') OR ! is_php('5.3.7') OR ! defined('CRYPT_BLOWFISH') OR CRYPT_BLOWFISH !== 1)
+{
+ 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())
+ {
+ 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']) && strlen($options['salt']) < 22)
+ {
+ trigger_error('password_hash(): Provided salt is too short: '.strlen($options['salt']).' expecting 22', E_USER_WARNING);
+ return NULL;
+ }
+ elseif ( ! isset($options['salt']))
+ {
+ if (defined('MCRYPT_DEV_URANDOM'))
+ {
+ $options['salt'] = mcrypt_create_iv(16, MCRYPT_DEV_URANDOM);
+ }
+ elseif (function_exists('openssl_random_pseudo_bytes'))
+ {
+ $options['salt'] = openssl_random_pseudo_bytes(16);
+ }
+ 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;
+ }
+
+ $options['salt'] = '';
+ for ($read = 0; $read < 16; $read = 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);
+ }
+ 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 crypt($password, sprintf('$2y$%02d$%s', $options['cost'], $options['salt']));
+ }
+}
+
+// ------------------------------------------------------------------------
+
+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);
+ }
+}
+
+/* End of file password.php */
+/* Location: ./system/core/compat/password.php */ \ No newline at end of file