summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.travis.yml1
-rw-r--r--application/config/config.php3
-rw-r--r--system/core/Config.php20
-rw-r--r--system/core/Exceptions.php2
-rw-r--r--system/core/Input.php20
-rw-r--r--system/core/Loader.php2
-rw-r--r--system/core/Router.php42
-rw-r--r--system/core/Security.php188
-rw-r--r--system/database/DB_driver.php39
-rw-r--r--system/database/DB_query_builder.php2
-rw-r--r--system/database/drivers/mssql/mssql_driver.php16
-rw-r--r--system/database/drivers/sqlsrv/sqlsrv_driver.php15
-rw-r--r--system/helpers/file_helper.php2
-rw-r--r--system/libraries/Cache/drivers/Cache_file.php2
-rw-r--r--system/libraries/Cache/drivers/Cache_memcached.php10
-rw-r--r--system/libraries/Cache/drivers/Cache_redis.php22
-rw-r--r--system/libraries/Email.php23
-rw-r--r--system/libraries/Pagination.php12
-rw-r--r--tests/codeigniter/core/Security_test.php111
-rw-r--r--tests/codeigniter/database/query_builder/where_test.php8
-rw-r--r--user_guide_src/source/changelog.rst37
-rw-r--r--user_guide_src/source/contributing/index.rst20
-rw-r--r--user_guide_src/source/database/db_driver_reference.rst4
-rw-r--r--user_guide_src/source/database/examples.rst17
-rw-r--r--user_guide_src/source/general/controllers.rst22
-rw-r--r--user_guide_src/source/general/routing.rst21
-rw-r--r--user_guide_src/source/general/security.rst3
-rw-r--r--user_guide_src/source/installation/downloads.rst2
-rw-r--r--user_guide_src/source/installation/upgrade_300.rst3
-rw-r--r--user_guide_src/source/installation/upgrade_303.rst14
-rw-r--r--user_guide_src/source/installation/upgrading.rst1
-rw-r--r--user_guide_src/source/libraries/encryption.rst2
-rw-r--r--user_guide_src/source/libraries/form_validation.rst5
-rw-r--r--user_guide_src/source/libraries/security.rst19
-rw-r--r--user_guide_src/source/libraries/sessions.rst19
35 files changed, 474 insertions, 255 deletions
diff --git a/.travis.yml b/.travis.yml
index 258ad76f1..adc60d759 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -48,4 +48,5 @@ matrix:
branches:
only:
- develop
+ - 3.0-stable
- /^feature\/.+$/
diff --git a/application/config/config.php b/application/config/config.php
index a4d883fab..479d591a4 100644
--- a/application/config/config.php
+++ b/application/config/config.php
@@ -344,6 +344,9 @@ $config['encryption_key'] = '';
|
| Whether to match the user's IP address when reading the session data.
|
+| WARNING: If you're using the database driver, don't forget to update
+| your session table's PRIMARY KEY when changing this setting.
+|
| 'sess_time_to_update'
|
| How many seconds between CI regenerating the session ID.
diff --git a/system/core/Config.php b/system/core/Config.php
index d07000ac9..feea7c85a 100644
--- a/system/core/Config.php
+++ b/system/core/Config.php
@@ -238,7 +238,15 @@ class CI_Config {
if (isset($protocol))
{
- $base_url = $protocol.substr($base_url, strpos($base_url, '://'));
+ // For protocol-relative links
+ if ($protocol === '')
+ {
+ $base_url = substr($base_url, strpos($base_url, '//'));
+ }
+ else
+ {
+ $base_url = $protocol.substr($base_url, strpos($base_url, '://'));
+ }
}
if (empty($uri))
@@ -293,7 +301,15 @@ class CI_Config {
if (isset($protocol))
{
- $base_url = $protocol.substr($base_url, strpos($base_url, '://'));
+ // For protocol-relative links
+ if ($protocol === '')
+ {
+ $base_url = substr($base_url, strpos($base_url, '//'));
+ }
+ else
+ {
+ $base_url = $protocol.substr($base_url, strpos($base_url, '://'));
+ }
}
return $base_url.ltrim($this->_uri_string($uri), '/');
diff --git a/system/core/Exceptions.php b/system/core/Exceptions.php
index fc25f57e6..d8f62c0fe 100644
--- a/system/core/Exceptions.php
+++ b/system/core/Exceptions.php
@@ -187,7 +187,7 @@ class CI_Exceptions {
// --------------------------------------------------------------------
- public function show_exception(Exception $exception)
+ public function show_exception($exception)
{
$templates_path = config_item('error_views_path');
if (empty($templates_path))
diff --git a/system/core/Input.php b/system/core/Input.php
index e1319be8d..4e7a4e95e 100644
--- a/system/core/Input.php
+++ b/system/core/Input.php
@@ -153,6 +153,12 @@ class CI_Input {
// Sanitize global arrays
$this->_sanitize_globals();
+ // CSRF Protection check
+ if ($this->_enable_csrf === TRUE && ! is_cli())
+ {
+ $this->security->csrf_verify();
+ }
+
log_message('info', 'Input Class Initialized');
}
@@ -600,7 +606,7 @@ class CI_Input {
{
$_GET = array();
}
- elseif (is_array($_GET) && count($_GET) > 0)
+ elseif (is_array($_GET))
{
foreach ($_GET as $key => $val)
{
@@ -609,7 +615,7 @@ class CI_Input {
}
// Clean $_POST Data
- if (is_array($_POST) && count($_POST) > 0)
+ if (is_array($_POST))
{
foreach ($_POST as $key => $val)
{
@@ -618,7 +624,7 @@ class CI_Input {
}
// Clean $_COOKIE Data
- if (is_array($_COOKIE) && count($_COOKIE) > 0)
+ if (is_array($_COOKIE))
{
// Also get rid of specially treated cookies that might be set by a server
// or silly application, that are of no use to a CI application anyway
@@ -647,12 +653,6 @@ class CI_Input {
// Sanitize PHP_SELF
$_SERVER['PHP_SELF'] = strip_tags($_SERVER['PHP_SELF']);
- // CSRF Protection check
- if ($this->_enable_csrf === TRUE && ! is_cli())
- {
- $this->security->csrf_verify();
- }
-
log_message('debug', 'Global POST, GET and COOKIE data sanitized');
}
@@ -803,7 +803,7 @@ class CI_Input {
if ( ! isset($headers))
{
- empty($this->headers) OR $this->request_headers();
+ empty($this->headers) && $this->request_headers();
foreach ($this->headers as $key => $value)
{
$headers[strtolower($key)] = $value;
diff --git a/system/core/Loader.php b/system/core/Loader.php
index 5de7a9483..18e4c5287 100644
--- a/system/core/Loader.php
+++ b/system/core/Loader.php
@@ -290,7 +290,7 @@ class CI_Loader {
load_class('Model', 'core');
}
- $model = ucfirst(strtolower($model));
+ $model = ucfirst($model);
if ( ! class_exists($model))
{
foreach ($this->_ci_model_paths as $mod_path)
diff --git a/system/core/Router.php b/system/core/Router.php
index eb868cd5b..a84be1f1d 100644
--- a/system/core/Router.php
+++ b/system/core/Router.php
@@ -153,6 +153,28 @@ class CI_Router {
*/
protected function _set_routing()
{
+ // Load the routes.php file. It would be great if we could
+ // skip this for enable_query_strings = TRUE, but then
+ // default_controller would be empty ...
+ if (file_exists(APPPATH.'config/routes.php'))
+ {
+ include(APPPATH.'config/routes.php');
+ }
+
+ if (file_exists(APPPATH.'config/'.ENVIRONMENT.'/routes.php'))
+ {
+ include(APPPATH.'config/'.ENVIRONMENT.'/routes.php');
+ }
+
+ // Validate & get reserved routes
+ if (isset($route) && is_array($route))
+ {
+ isset($route['default_controller']) && $this->default_controller = $route['default_controller'];
+ isset($route['translate_uri_dashes']) && $this->translate_uri_dashes = $route['translate_uri_dashes'];
+ unset($route['default_controller'], $route['translate_uri_dashes']);
+ $this->routes = $route;
+ }
+
// 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
@@ -199,26 +221,6 @@ class CI_Router {
return;
}
- // Load the routes.php file.
- if (file_exists(APPPATH.'config/routes.php'))
- {
- include(APPPATH.'config/routes.php');
- }
-
- if (file_exists(APPPATH.'config/'.ENVIRONMENT.'/routes.php'))
- {
- include(APPPATH.'config/'.ENVIRONMENT.'/routes.php');
- }
-
- // Validate & get reserved routes
- if (isset($route) && is_array($route))
- {
- isset($route['default_controller']) && $this->default_controller = $route['default_controller'];
- isset($route['translate_uri_dashes']) && $this->translate_uri_dashes = $route['translate_uri_dashes'];
- unset($route['default_controller'], $route['translate_uri_dashes']);
- $this->routes = $route;
- }
-
// Is there anything to parse?
if ($this->uri->uri_string !== '')
{
diff --git a/system/core/Security.php b/system/core/Security.php
index 7c5199255..ab85e2239 100644
--- a/system/core/Security.php
+++ b/system/core/Security.php
@@ -436,7 +436,7 @@ class CI_Security {
$words = array(
'javascript', 'expression', 'vbscript', 'jscript', 'wscript',
'vbs', 'script', 'base64', 'applet', 'alert', 'document',
- 'write', 'cookie', 'window', 'confirm', 'prompt'
+ 'write', 'cookie', 'window', 'confirm', 'prompt', 'eval'
);
foreach ($words as $word)
@@ -480,12 +480,8 @@ class CI_Security {
}
}
while ($original !== $str);
-
unset($original);
- // Remove evil attributes such as style, onclick and xmlns
- $str = $this->_remove_evil_attributes($str, $is_image);
-
/*
* Sanitize naughty HTML elements
*
@@ -495,8 +491,29 @@ class CI_Security {
* So this: <blink>
* Becomes: &lt;blink&gt;
*/
- $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);
+ $pattern = '#'
+ .'<((?<slash>/*\s*)(?<tagName>[a-z0-9]+)(?=[^a-z0-9]|$)' // tag start and name, followed by a non-tag character
+ .'[^\s\042\047a-z0-9>/=]*' // a valid attribute character immediately after the tag would count as a separator
+ // optional attributes
+ .'(?<attributes>(?:[\s\042\047/=]*' // non-attribute characters, excluding > (tag close) for obvious reasons
+ .'[^\s\042\047>/=]+' // attribute characters
+ // optional attribute-value
+ .'(?:\s*=' // attribute-value separator
+ .'(?:[^\s\042\047=><`]+|\s*\042[^\042]*\042|\s*\047[^\047]*\047|\s*(?U:[^\s\042\047=><`]*))' // single, double or non-quoted value
+ .')?' // end optional attribute-value group
+ .')*)' // end optional attributes group
+ .'[^>]*)(?<closeTag>\>)?#isS';
+
+ // Note: It would be nice to optimize this for speed, BUT
+ // only matching the naughty elements here results in
+ // false positives and in turn - vulnerabilities!
+ do
+ {
+ $old_str = $str;
+ $str = preg_replace_callback($pattern, array($this, '_sanitize_naughty_html'), $str);
+ }
+ while ($old_str !== $str);
+ unset($old_str);
/*
* Sanitize naughty scripting elements
@@ -510,9 +527,11 @@ class CI_Security {
* For example: eval('some code')
* Becomes: eval&#40;'some code'&#41;
*/
- $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);
+ $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
+ );
// Final clean up
// This adds a bit of extra precaution in case
@@ -750,58 +769,6 @@ class CI_Security {
// --------------------------------------------------------------------
/**
- * Remove Evil HTML Attributes (like event handlers and style)
- *
- * It removes the evil attribute and either:
- *
- * - Everything up until a space. For example, everything between the pipes:
- *
- * <code>
- * <a |style=document.write('hello');alert('world');| class=link>
- * </code>
- *
- * - Everything inside the quotes. For example, everything between the pipes:
- *
- * <code>
- * <a |style="document.write('hello'); alert('world');"| class="link">
- * </code>
- *
- * @param string $str The string to check
- * @param bool $is_image Whether the input is an image
- * @return string The string with the evil attributes removed
- */
- protected function _remove_evil_attributes($str, $is_image)
- {
- $evil_attributes = array('on\w*', 'style', 'xmlns', 'formaction', 'form', 'xlink:href', 'FSCommand', 'seekSegmentTime');
-
- if ($is_image === TRUE)
- {
- /*
- * Adobe Photoshop puts XML metadata into JFIF images,
- * including namespacing, so we have to allow this for images.
- */
- unset($evil_attributes[array_search('xmlns', $evil_attributes)]);
- }
-
- do {
- $count = $temp_count = 0;
-
- // replace occurrences of illegal attribute strings with quotes (042 and 047 are octal quotes)
- $str = preg_replace('/(<[^>]+)(?<!\w)('.implode('|', $evil_attributes).')\s*=\s*(\042|\047)([^\\2]*?)(\\2)/is', '$1[removed]', $str, -1, $temp_count);
- $count += $temp_count;
-
- // find occurrences of illegal attribute strings without quotes
- $str = preg_replace('/(<[^>]+)(?<!\w)('.implode('|', $evil_attributes).')\s*=\s*([^\s>]*)/is', '$1[removed]', $str, -1, $temp_count);
- $count += $temp_count;
- }
- while ($count);
-
- return $str;
- }
-
- // --------------------------------------------------------------------
-
- /**
* Sanitize Naughty HTML
*
* Callback method for xss_clean() to remove naughty HTML elements.
@@ -812,9 +779,70 @@ class CI_Security {
*/
protected function _sanitize_naughty_html($matches)
{
- return '&lt;'.$matches[1].$matches[2].$matches[3] // encode opening brace
- // encode captured opening or closing brace to prevent recursive vectors:
- .str_replace(array('>', '<'), array('&gt;', '&lt;'), $matches[4]);
+ static $naughty_tags = array(
+ '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'
+ );
+
+ static $evil_attributes = array(
+ 'on\w+', 'style', 'xmlns', 'formaction', 'form', 'xlink:href', 'FSCommand', 'seekSegmentTime'
+ );
+
+ // First, escape unclosed tags
+ if (empty($matches['closeTag']))
+ {
+ return '&lt;'.$matches[1];
+ }
+ // Is the element that we caught naughty? If so, escape it
+ elseif (in_array(strtolower($matches['tagName']), $naughty_tags, TRUE))
+ {
+ return '&lt;'.$matches[1].'&gt;';
+ }
+ // For other tags, see if their attributes are "evil" and strip those
+ elseif (isset($matches['attributes']))
+ {
+ // We'll need to catch all attributes separately first
+ $pattern = '#'
+ .'([\s\042\047/=]*)' // non-attribute characters, excluding > (tag close) for obvious reasons
+ .'(?<name>[^\s\042\047>/=]+)' // attribute characters
+ // optional attribute-value
+ .'(?:\s*=(?<value>[^\s\042\047=><`]+|\s*\042[^\042]*\042|\s*\047[^\047]*\047|\s*(?U:[^\s\042\047=><`]*)))' // attribute-value separator
+ .'#i';
+
+ if ($count = preg_match_all($pattern, $matches['attributes'], $attributes, PREG_SET_ORDER | PREG_OFFSET_CAPTURE))
+ {
+ // Since we'll be using substr_replace() below, we
+ // need to handle the attributes in reverse order,
+ // so we don't damage the string.
+ for ($i = $count - 1; $i > -1; $i--)
+ {
+ if (
+ // Is it indeed an "evil" attribute?
+ preg_match('#^('.implode('|', $evil_attributes).')$#i', $attributes[$i]['name'][0])
+ // Or an attribute not starting with a letter? Some parsers get confused by that
+ OR ! ctype_alpha($attributes[$i]['name'][0][0])
+ // Does it have an equals sign, but no value and not quoted? Strip that too!
+ OR (trim($attributes[$i]['value'][0]) === '')
+ )
+ {
+ $matches['attributes'] = substr_replace(
+ $matches['attributes'],
+ ' [removed]',
+ $attributes[$i][0][1],
+ strlen($attributes[$i][0][0])
+ );
+ }
+ }
+
+ // Note: This will strip some non-space characters and/or
+ // reduce multiple spaces between attributes.
+ return '<'.$matches['slash'].$matches['tagName'].' '.trim($matches['attributes']).'>';
+ }
+ }
+
+ return $matches[0];
}
// --------------------------------------------------------------------
@@ -834,12 +862,15 @@ class CI_Security {
*/
protected function _js_link_removal($match)
{
- return str_replace($match[1],
- 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]))
- ),
- $match[0]);
+ return str_replace(
+ $match[1],
+ preg_replace(
+ '#href=.*?(?:(?:alert|prompt|confirm)(?:\(|&\#40;)|javascript:|livescript:|mocha:|charset=|window\.|document\.|\.cookie|<script|<xss|data\s*:)#si',
+ '',
+ $this->_filter_attributes($match[1])
+ ),
+ $match[0]
+ );
}
// --------------------------------------------------------------------
@@ -859,12 +890,15 @@ class CI_Security {
*/
protected function _js_img_removal($match)
{
- return str_replace($match[1],
- 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]))
- ),
- $match[0]);
+ return str_replace(
+ $match[1],
+ preg_replace(
+ '#src=.*?(?:(?:alert|prompt|confirm|eval)(?:\(|&\#40;)|javascript:|livescript:|mocha:|charset=|window\.|document\.|\.cookie|<script|<xss|base64\s*,)#si',
+ '',
+ $this->_filter_attributes($match[1])
+ ),
+ $match[0]
+ );
}
// --------------------------------------------------------------------
diff --git a/system/database/DB_driver.php b/system/database/DB_driver.php
index 34d3a5979..cc94edc16 100644
--- a/system/database/DB_driver.php
+++ b/system/database/DB_driver.php
@@ -791,10 +791,13 @@ abstract class CI_DB_driver {
/**
* Enable/disable Transaction Strict Mode
+ *
* When strict mode is enabled, if you are running multiple groups of
- * transactions, if one group fails all groups will be rolled back.
- * If strict mode is disabled, each group is treated autonomously, meaning
- * a failure of one group will not affect any others
+ * transactions, if one group fails all subsequent groups will be
+ * rolled back.
+ *
+ * If strict mode is disabled, each group is treated autonomously,
+ * meaning a failure of one group will not affect any others
*
* @param bool $mode = TRUE
* @return void
@@ -861,8 +864,8 @@ abstract class CI_DB_driver {
$this->trans_rollback();
// If we are NOT running in strict mode, we will reset
- // the _trans_status flag so that subsequent groups of transactions
- // will be permitted.
+ // the _trans_status flag so that subsequent groups of
+ // transactions will be permitted.
if ($this->trans_strict === FALSE)
{
$this->_trans_status = TRUE;
@@ -1480,18 +1483,18 @@ abstract class CI_DB_driver {
? '\s+'.preg_quote(trim(sprintf($this->_like_escape_str, $this->_like_escape_chr)), '/')
: '';
$_operators = array(
- '\s*(?:<|>|!)?=\s*', // =, <=, >=, !=
- '\s*<>?\s*', // <, <>
- '\s*>\s*', // >
- '\s+IS NULL', // IS NULL
- '\s+IS NOT NULL', // IS NOT NULL
- '\s+EXISTS\s*\([^\)]+\)', // EXISTS(sql)
- '\s+NOT EXISTS\s*\([^\)]+\)', // NOT EXISTS(sql)
- '\s+BETWEEN\s+\S+\s+AND\s+\S+', // BETWEEN value AND value
- '\s+IN\s*\([^\)]+\)', // IN(list)
- '\s+NOT IN\s*\([^\)]+\)', // NOT IN (list)
- '\s+LIKE\s+\S+'.$_les, // LIKE 'expr'[ ESCAPE '%s']
- '\s+NOT LIKE\s+\S+'.$_les // NOT LIKE 'expr'[ ESCAPE '%s']
+ '\s*(?:<|>|!)?=\s*', // =, <=, >=, !=
+ '\s*<>?\s*', // <, <>
+ '\s*>\s*', // >
+ '\s+IS NULL', // IS NULL
+ '\s+IS NOT NULL', // IS NOT NULL
+ '\s+EXISTS\s*\([^\)]+\)', // EXISTS(sql)
+ '\s+NOT EXISTS\s*\([^\)]+\)', // NOT EXISTS(sql)
+ '\s+BETWEEN\s+', // BETWEEN value AND value
+ '\s+IN\s*\([^\)]+\)', // IN(list)
+ '\s+NOT IN\s*\([^\)]+\)', // NOT IN (list)
+ '\s+LIKE\s+\S.*('.$_les.')?', // LIKE 'expr'[ ESCAPE '%s']
+ '\s+NOT LIKE\s+\S.*('.$_les.')?' // NOT LIKE 'expr'[ ESCAPE '%s']
);
}
@@ -1760,7 +1763,7 @@ abstract class CI_DB_driver {
}
// Convert tabs or multiple spaces into single spaces
- $item = preg_replace('/\s+/', ' ', $item);
+ $item = preg_replace('/\s+/', ' ', trim($item));
// If the item has an alias declaration we remove it and set it aside.
// Note: strripos() is used in order to support spaces in table names
diff --git a/system/database/DB_query_builder.php b/system/database/DB_query_builder.php
index 7f3334763..cf1100d27 100644
--- a/system/database/DB_query_builder.php
+++ b/system/database/DB_query_builder.php
@@ -2342,7 +2342,7 @@ abstract class CI_DB_query_builder extends CI_DB_driver {
// Split multiple conditions
$conditions = preg_split(
- '/((^|\s+)AND\s+|(^|\s+)OR\s+)/i',
+ '/((?:^|\s+)AND\s+|(?:^|\s+)OR\s+)/i',
$this->{$qb_key}[$i]['condition'],
-1,
PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY
diff --git a/system/database/drivers/mssql/mssql_driver.php b/system/database/drivers/mssql/mssql_driver.php
index 8f15d8d69..05e5418c3 100644
--- a/system/database/drivers/mssql/mssql_driver.php
+++ b/system/database/drivers/mssql/mssql_driver.php
@@ -381,9 +381,19 @@ class CI_DB_mssql_driver extends CI_DB {
*/
public function error()
{
- $query = $this->query('SELECT @@ERROR AS code');
- $query = $query->row();
- return array('code' => $query->code, 'message' => mssql_get_last_message());
+ // We need this because the error info is discarded by the
+ // server the first time you request it, and query() already
+ // calls error() once for logging purposes when a query fails.
+ static $error = array('code' => 0, 'message' => NULL);
+
+ $message = mssql_get_last_message();
+ if ( ! empty($message))
+ {
+ $error['code'] = $this->query('SELECT @@ERROR AS code')->row()->code;
+ $error['message'] = $message;
+ }
+
+ return $error;
}
// --------------------------------------------------------------------
diff --git a/system/database/drivers/sqlsrv/sqlsrv_driver.php b/system/database/drivers/sqlsrv/sqlsrv_driver.php
index 16f77fab2..8d383b274 100644
--- a/system/database/drivers/sqlsrv/sqlsrv_driver.php
+++ b/system/database/drivers/sqlsrv/sqlsrv_driver.php
@@ -141,13 +141,14 @@ class CI_DB_sqlsrv_driver extends CI_DB {
unset($connection['UID'], $connection['PWD']);
}
- $this->conn_id = sqlsrv_connect($this->hostname, $connection);
-
- // Determine how identifiers are escaped
- $query = $this->query('SELECT CASE WHEN (@@OPTIONS | 256) = @@OPTIONS THEN 1 ELSE 0 END AS qi');
- $query = $query->row_array();
- $this->_quoted_identifier = empty($query) ? FALSE : (bool) $query['qi'];
- $this->_escape_char = ($this->_quoted_identifier) ? '"' : array('[', ']');
+ if (FALSE !== ($this->conn_id = sqlsrv_connect($this->hostname, $connection)))
+ {
+ // Determine how identifiers are escaped
+ $query = $this->query('SELECT CASE WHEN (@@OPTIONS | 256) = @@OPTIONS THEN 1 ELSE 0 END AS qi');
+ $query = $query->row_array();
+ $this->_quoted_identifier = empty($query) ? FALSE : (bool) $query['qi'];
+ $this->_escape_char = ($this->_quoted_identifier) ? '"' : array('[', ']');
+ }
return $this->conn_id;
}
diff --git a/system/helpers/file_helper.php b/system/helpers/file_helper.php
index cd1c641ec..f6cb1629a 100644
--- a/system/helpers/file_helper.php
+++ b/system/helpers/file_helper.php
@@ -343,7 +343,7 @@ if ( ! function_exists('get_mime_by_extension'))
if ( ! is_array($mimes))
{
- $mimes =& get_mimes();
+ $mimes = get_mimes();
if (empty($mimes))
{
diff --git a/system/libraries/Cache/drivers/Cache_file.php b/system/libraries/Cache/drivers/Cache_file.php
index 68bc1ec96..c046f3b7d 100644
--- a/system/libraries/Cache/drivers/Cache_file.php
+++ b/system/libraries/Cache/drivers/Cache_file.php
@@ -267,7 +267,7 @@ class CI_Cache_file extends CI_Driver {
*/
protected function _get($id)
{
- if ( ! file_exists($this->_cache_path.$id))
+ if ( ! is_file($this->_cache_path.$id))
{
return FALSE;
}
diff --git a/system/libraries/Cache/drivers/Cache_memcached.php b/system/libraries/Cache/drivers/Cache_memcached.php
index 111e2109d..59cf4685d 100644
--- a/system/libraries/Cache/drivers/Cache_memcached.php
+++ b/system/libraries/Cache/drivers/Cache_memcached.php
@@ -106,7 +106,7 @@ class CI_Cache_memcached extends CI_Driver {
}
else
{
- throw new RuntimeException('Cache: Failed to create Memcache(d) object; extension not loaded?');
+ log_message('error', 'Cache: Failed to create Memcache(d) object; extension not loaded?');
}
foreach ($this->_memcache_conf as $cache_server)
@@ -284,12 +284,6 @@ class CI_Cache_memcached extends CI_Driver {
*/
public function is_supported()
{
- if ( ! extension_loaded('memcached') && ! extension_loaded('memcache'))
- {
- log_message('debug', 'The Memcached Extension must be loaded to use Memcached Cache.');
- return FALSE;
- }
-
- return TRUE;
+ return (extension_loaded('memcached') OR extension_loaded('memcache'));
}
}
diff --git a/system/libraries/Cache/drivers/Cache_redis.php b/system/libraries/Cache/drivers/Cache_redis.php
index d7dca1973..ea0059ff7 100644
--- a/system/libraries/Cache/drivers/Cache_redis.php
+++ b/system/libraries/Cache/drivers/Cache_redis.php
@@ -115,17 +115,17 @@ class CI_Cache_redis extends CI_Driver
if ( ! $success)
{
- throw new RuntimeException('Cache: Redis connection failed. Check your configuration.');
+ log_message('error', 'Cache: Redis connection failed. Check your configuration.');
+ }
+
+ if (isset($config['password']) && ! $this->_redis->auth($config['password']))
+ {
+ log_message('error', 'Cache: Redis authentication failed.');
}
}
catch (RedisException $e)
{
- throw new RuntimeException('Cache: Redis connection refused ('.$e->getMessage().')');
- }
-
- if (isset($config['password']) && ! $this->_redis->auth($config['password']))
- {
- throw new RuntimeException('Cache: Redis authentication failed.');
+ log_message('error', 'Cache: Redis connection refused ('.$e->getMessage().')');
}
// Initialize the index of serialized values.
@@ -298,13 +298,7 @@ class CI_Cache_redis extends CI_Driver
*/
public function is_supported()
{
- if ( ! extension_loaded('redis'))
- {
- log_message('debug', 'The Redis extension must be loaded to use Redis cache.');
- return FALSE;
- }
-
- return TRUE;
+ return extension_loaded('redis');
}
// ------------------------------------------------------------------------
diff --git a/system/libraries/Email.php b/system/libraries/Email.php
index 459c8f590..acf3629c3 100644
--- a/system/libraries/Email.php
+++ b/system/libraries/Email.php
@@ -1869,20 +1869,26 @@ class CI_Email {
return FALSE;
}
- $this->_send_command('from', $this->clean_email($this->_headers['From']));
+ if ( ! $this->_send_command('from', $this->clean_email($this->_headers['From'])))
+ {
+ return FALSE;
+ }
foreach ($this->_recipients as $val)
{
- $this->_send_command('to', $val);
+ if ( ! $this->_send_command('to', $val))
+ {
+ return FALSE;
+ }
}
if (count($this->_cc_array) > 0)
{
foreach ($this->_cc_array as $val)
{
- if ($val !== '')
+ if ($val !== '' && ! $this->_send_command('to', $val))
{
- $this->_send_command('to', $val);
+ return FALSE;
}
}
}
@@ -1891,14 +1897,17 @@ class CI_Email {
{
foreach ($this->_bcc_array as $val)
{
- if ($val !== '')
+ if ($val !== '' && ! $this->_send_command('to', $val))
{
- $this->_send_command('to', $val);
+ return FALSE;
}
}
}
- $this->_send_command('data');
+ if ( ! $this->_send_command('data'))
+ {
+ return FALSE;
+ }
// perform dot transformation on any lines that begin with a dot
$this->_send_data($this->_header_str.preg_replace('/^\./m', '..$1', $this->_finalbody));
diff --git a/system/libraries/Pagination.php b/system/libraries/Pagination.php
index 5b3aa01f4..4d18998b9 100644
--- a/system/libraries/Pagination.php
+++ b/system/libraries/Pagination.php
@@ -571,7 +571,7 @@ class CI_Pagination {
{
$i = ($this->use_page_numbers) ? $uri_page_number - 1 : $uri_page_number - $this->per_page;
- $attributes = sprintf('%s %s="%d"', $this->_attributes, $this->data_page_attr, (int) $i);
+ $attributes = sprintf('%s %s="%d"', $this->_attributes, $this->data_page_attr, ($this->cur_page - 1));
if ($i === $base_page)
{
@@ -592,11 +592,11 @@ class CI_Pagination {
if ($this->display_pages !== FALSE)
{
// Write the digit links
- for ($loop = $start -1; $loop <= $end; $loop++)
+ for ($loop = $start - 1; $loop <= $end; $loop++)
{
$i = ($this->use_page_numbers) ? $loop : ($loop * $this->per_page) - $this->per_page;
- $attributes = sprintf('%s %s="%d"', $this->_attributes, $this->data_page_attr, (int) $i);
+ $attributes = sprintf('%s %s="%d"', $this->_attributes, $this->data_page_attr, $loop);
if ($i >= $base_page)
{
@@ -614,7 +614,7 @@ class CI_Pagination {
else
{
$append = $this->prefix.$i.$this->suffix;
- $output .= $this->num_tag_open.'<a href="'.$base_url.$append.'"'.$attributes.$this->_attr_rel('start').'>'
+ $output .= $this->num_tag_open.'<a href="'.$base_url.$append.'"'.$attributes.'>'
.$loop.'</a>'.$this->num_tag_close;
}
}
@@ -626,7 +626,7 @@ class CI_Pagination {
{
$i = ($this->use_page_numbers) ? $this->cur_page + 1 : $this->cur_page * $this->per_page;
- $attributes = sprintf('%s %s="%d"', $this->_attributes, $this->data_page_attr, (int) $i);
+ $attributes = sprintf('%s %s="%d"', $this->_attributes, $this->data_page_attr, $this->cur_page + 1);
$output .= $this->next_tag_open.'<a href="'.$base_url.$this->prefix.$i.$this->suffix.'"'.$attributes
.$this->_attr_rel('next').'>'.$this->next_link.'</a>'.$this->next_tag_close;
@@ -637,7 +637,7 @@ class CI_Pagination {
{
$i = ($this->use_page_numbers) ? $num_pages : ($num_pages * $this->per_page) - $this->per_page;
- $attributes = sprintf('%s %s="%d"', $this->_attributes, $this->data_page_attr, (int) $i);
+ $attributes = sprintf('%s %s="%d"', $this->_attributes, $this->data_page_attr, $num_pages);
$output .= $this->last_tag_open.'<a href="'.$base_url.$this->prefix.$i.$this->suffix.'"'.$attributes.'>'
.$this->last_link.'</a>'.$this->last_tag_close;
diff --git a/tests/codeigniter/core/Security_test.php b/tests/codeigniter/core/Security_test.php
index bab76dffb..52967dc2f 100644
--- a/tests/codeigniter/core/Security_test.php
+++ b/tests/codeigniter/core/Security_test.php
@@ -96,7 +96,7 @@ class Security_test extends CI_TestCase {
$xss_clean_return = $this->security->xss_clean($harm_string, TRUE);
- $this->assertTrue($xss_clean_return);
+// $this->assertTrue($xss_clean_return);
}
// --------------------------------------------------------------------
@@ -120,6 +120,17 @@ class Security_test extends CI_TestCase {
// --------------------------------------------------------------------
+ public function text_xss_clean_js_link_removal()
+ {
+ // This one is to prevent a false positive
+ $this->assertEquals(
+ "<a href=\"javascrip\n<t\n:alert\n&#40;1&#41;\"\n>",
+ $this->security->xss_clean("<a href=\"javascrip\n<t\n:alert\n(1)\"\n>")
+ );
+ }
+
+ // --------------------------------------------------------------------
+
public function test_xss_clean_js_img_removal()
{
$input = '<img src="&#38&#35&#49&#48&#54&#38&#35&#57&#55&#38&#35&#49&#49&#56&#38&#35&#57&#55&#38&#35&#49&#49&#53&#38&#35&#57&#57&#38&#35&#49&#49&#52&#38&#35&#49&#48&#53&#38&#35&#49&#49&#50&#38&#35&#49&#49&#54&#38&#35&#53&#56&#38&#35&#57&#57&#38&#35&#49&#49&#49&#38&#35&#49&#49&#48&#38&#35&#49&#48&#50&#38&#35&#49&#48&#53&#38&#35&#49&#49&#52&#38&#35&#49&#48&#57&#38&#35&#52&#48&#38&#35&#52&#57&#38&#35&#52&#49">Clickhere';
@@ -128,24 +139,98 @@ class Security_test extends CI_TestCase {
// --------------------------------------------------------------------
- public function test_xss_clean_sanitize_naughty_html()
+ public function test_xss_clean_sanitize_naughty_html_tags()
{
- $input = '<blink>';
- $this->assertEquals('&lt;blink&gt;', $this->security->xss_clean($input));
+ $this->assertEquals('&lt;unclosedTag', $this->security->xss_clean('<unclosedTag'));
+ $this->assertEquals('&lt;blink&gt;', $this->security->xss_clean('<blink>'));
+ $this->assertEquals('<fubar>', $this->security->xss_clean('<fubar>'));
+
+ $this->assertEquals(
+ '<img [removed]> src="x">',
+ $this->security->xss_clean('<img <svg=""> src="x">')
+ );
+
+ $this->assertEquals(
+ '<img src="b on=">on=">"x onerror="alert&#40;1&#41;">',
+ $this->security->xss_clean('<img src="b on="<x">on=">"x onerror="alert(1)">')
+ );
}
// --------------------------------------------------------------------
- public function test_remove_evil_attributes()
+ public function test_xss_clean_sanitize_naughty_html_attributes()
{
- $this->assertEquals('<foo [removed]>', $this->security->remove_evil_attributes('<foo onAttribute="bar">', FALSE));
- $this->assertEquals('<foo [removed]>', $this->security->remove_evil_attributes('<foo onAttributeNoQuotes=bar>', FALSE));
- $this->assertEquals('<foo [removed]>', $this->security->remove_evil_attributes('<foo onAttributeWithSpaces = bar>', FALSE));
- $this->assertEquals('<foo prefixOnAttribute="bar">', $this->security->remove_evil_attributes('<foo prefixOnAttribute="bar">', FALSE));
- $this->assertEquals('<foo>onOutsideOfTag=test</foo>', $this->security->remove_evil_attributes('<foo>onOutsideOfTag=test</foo>', FALSE));
- $this->assertEquals('onNoTagAtAll = true', $this->security->remove_evil_attributes('onNoTagAtAll = true', FALSE));
- $this->assertEquals('<foo [removed]>', $this->security->remove_evil_attributes('<foo fscommand=case-insensitive>', FALSE));
- $this->assertEquals('<foo [removed]>', $this->security->remove_evil_attributes('<foo seekSegmentTime=whatever>', FALSE));
+ $this->assertEquals('<foo [removed]>', $this->security->xss_clean('<foo onAttribute="bar">'));
+ $this->assertEquals('<foo [removed]>', $this->security->xss_clean('<foo onAttributeNoQuotes=bar>'));
+ $this->assertEquals('<foo [removed]bar>', $this->security->xss_clean('<foo onAttributeWithSpaces = bar>'));
+ $this->assertEquals('<foo prefixOnAttribute="bar">', $this->security->xss_clean('<foo prefixOnAttribute="bar">'));
+ $this->assertEquals('<foo>onOutsideOfTag=test</foo>', $this->security->xss_clean('<foo>onOutsideOfTag=test</foo>'));
+ $this->assertEquals('onNoTagAtAll = true', $this->security->xss_clean('onNoTagAtAll = true'));
+ $this->assertEquals('<foo [removed]>', $this->security->xss_clean('<foo fscommand=case-insensitive>'));
+ $this->assertEquals('<foo [removed]>', $this->security->xss_clean('<foo seekSegmentTime=whatever>'));
+
+ $this->assertEquals(
+ '<foo bar=">" baz=\'>\' [removed]>',
+ $this->security->xss_clean('<foo bar=">" baz=\'>\' onAfterGreaterThan="quotes">')
+ );
+ $this->assertEquals(
+ '<foo bar=">" baz=\'>\' [removed]>',
+ $this->security->xss_clean('<foo bar=">" baz=\'>\' onAfterGreaterThan=noQuotes>')
+ );
+
+ $this->assertEquals(
+ '<img src="x" on=""> on=&lt;svg&gt; onerror=alert&#40;1&#41;>',
+ $this->security->xss_clean('<img src="x" on=""> on=<svg> onerror=alert(1)>')
+ );
+
+ $this->assertEquals(
+ '<img src="on=\'">"&lt;svg&gt; onerror=alert&#40;1&#41; onmouseover=alert&#40;1&#41;>',
+ $this->security->xss_clean('<img src="on=\'">"<svg> onerror=alert(1) onmouseover=alert(1)>')
+ );
+
+ $this->assertEquals(
+ '<img src="x"> on=\'x\' onerror=``,alert&#40;1&#41;>',
+ $this->security->xss_clean('<img src="x"> on=\'x\' onerror=``,alert(1)>')
+ );
+
+ $this->assertEquals(
+ '<a [removed]>',
+ $this->security->xss_clean('<a< onmouseover="alert(1)">')
+ );
+
+ $this->assertEquals(
+ '<img src="x"> on=\'x\' onerror=,xssm()>',
+ $this->security->xss_clean('<img src="x"> on=\'x\' onerror=,xssm()>')
+ );
+
+ $this->assertEquals(
+ '<image src="<>" [removed]>',
+ $this->security->xss_clean('<image src="<>" onerror=\'alert(1)\'>')
+ );
+
+ $this->assertEquals(
+ '<b [removed] [removed]>',
+ $this->security->xss_clean('<b "=<= onmouseover=alert(1)>')
+ );
+
+ $this->assertEquals(
+ '<b [removed] [removed]alert&#40;1&#41;,1>1">',
+ $this->security->xss_clean('<b a=<=" onmouseover="alert(1),1>1">')
+ );
+ }
+
+ // --------------------------------------------------------------------
+
+ /**
+ * @depends test_xss_clean_sanitize_naughty_html_tags
+ * @depends test_xss_clean_sanitize_naughty_html_attributes
+ */
+ public function test_naughty_html_plus_evil_attributes()
+ {
+ $this->assertEquals(
+ '&lt;svg<img &gt; src="x" [removed]>',
+ $this->security->xss_clean('<svg<img > src="x" onerror="location=/javascript/.source+/:alert/.source+/(1)/.source">')
+ );
}
// --------------------------------------------------------------------
diff --git a/tests/codeigniter/database/query_builder/where_test.php b/tests/codeigniter/database/query_builder/where_test.php
index 20b7a567c..46a7fa2eb 100644
--- a/tests/codeigniter/database/query_builder/where_test.php
+++ b/tests/codeigniter/database/query_builder/where_test.php
@@ -123,4 +123,12 @@ class Where_test extends CI_TestCase {
$this->assertEquals('Musician', $jobs[1]['name']);
}
+ // ------------------------------------------------------------------------
+
+ public function test_issue4093()
+ {
+ $input = 'bar and baz or qux';
+ $sql = $this->db->where('foo', $input)->get_compiled_select('dummy');
+ $this->assertEquals("'".$input."'", substr($sql, -20));
+ }
} \ No newline at end of file
diff --git a/user_guide_src/source/changelog.rst b/user_guide_src/source/changelog.rst
index 64120df8e..03cbeb673 100644
--- a/user_guide_src/source/changelog.rst
+++ b/user_guide_src/source/changelog.rst
@@ -8,20 +8,55 @@ Version 3.1.0
Release Date: Not Released
-Version 3.0.2
+Version 3.0.3
=============
Release Date: Not Released
+- Database
+
+ - Optimized :doc:`Database Utility <database/utilities>` method ``csv_from_result()`` for speed with larger result sets.
+
+Bug fixes for 3.0.3
+-------------------
+
+Version 3.0.2
+=============
+
+Release Date: October 8, 2015
+
+- **Security**
+
+ - Fixed a number of XSS attack vectors in :doc:`Security Library <libraries/security>` method ``xss_clean()`` (thanks to Frans Rosén from `Detectify <https://detectify.com/>`_).
+
- General Changes
- Updated the *application/config/constants.php* file to check if constants aren't already defined before doing that.
+ - Changed :doc:`Loader Library <libraries/loader>` method ``model()`` to only apply ``ucfirst()`` and not ``strtolower()`` to the requested class name.
+ - Changed :doc:`Config Library <libraries/config>` methods ``base_url()``, ``site_url()`` to allow protocol-relative URLs by passing an empty string as the protocol.
Bug fixes for 3.0.2
-------------------
- Fixed a bug (#2284) - :doc:`Database <database/index>` method ``protect_identifiers()`` breaks when :doc:`Query Builder <database/query_builder>` isn't enabled.
- Fixed a bug (#4052) - :doc:`Routing <general/routing>` with anonymous functions didn't work for routes that don't use regular expressions.
+- Fixed a bug (#4056) - :doc:`Input Library <libraries/input>` method ``get_request_header()`` could not return a value unless ``request_headers()`` was called beforehand.
+- Fixed a bug where the :doc:`Database Class <database/index>` entered an endless loop if it fails to connect with the 'sqlsrv' driver.
+- Fixed a bug (#4065) - :doc:`Database <database/index>` method ``protect_identifiers()`` treats a traling space as an alias separator if the input doesn't contain ' AS '.
+- Fixed a bug (#4066) - :doc:`Cache Library <libraries/caching>` couldn't fallback to a backup driver if the primary one is Memcache(d) or Redis.
+- Fixed a bug (#4073) - :doc:`Email Library <libraries/email>` method ``send()`` could return TRUE in case of an actual failure when an SMTP command fails.
+- Fixed a bug (#4086) - :doc:`Query Builder <database/query_builder>` didn't apply *dbprefix* to LIKE conditions if the pattern included spaces.
+- Fixed a bug (#4091) - :doc:`Cache Library <libraries/caching>` 'file' driver could be tricked into accepting empty cache item IDs.
+- Fixed a bug (#4093) - :doc:`Query Builder <database/query_builder>` modified string values containing 'AND', 'OR' while compiling WHERE conditions.
+- Fixed a bug (#4096) - :doc:`Query Builder <database/query_builder>` didn't apply *dbprefix* when compiling BETWEEN conditions.
+- Fixed a bug (#4105) - :doc:`Form Validation Library <libraries/form_validation>` didn't allow pipe characters inside "bracket parameters" when using a string ruleset.
+- Fixed a bug (#4109) - :doc:`Routing <general/routing>` to *default_controller* didn't work when *enable_query_strings* is set to TRUE.
+- Fixed a bug (#4044) - :doc:`Cache Library <libraries/caching>` 'redis' driver didn't catch ``RedisException`` that could be thrown during authentication.
+- Fixed a bug (#4120) - :doc:`Database <database/index>` method ``error()`` didn't return error info when called after ``query()`` with the 'mssql' driver.
+- Fixed a bug (#4116) - :doc:`Pagination Library <libraries/pagination>` set the wrong page number on the "data-ci-pagination-page" attribute in generated links.
+- Fixed a bug where :doc:`Pagination Library <libraries/pagination>` added the 'rel="start"' attribute to the first displayed link even if it's not actually linking the first page.
+- Fixed a bug (#4137) - :doc:`Error Handling <general/errors>` breaks for the new ``Error`` exceptions under PHP 7.
+- Fixed a bug (#4126) - :doc:`Form Validation Library <libraries/form_validation>` method ``reset_validation()`` discarded validation rules from config files.
Version 3.0.1
=============
diff --git a/user_guide_src/source/contributing/index.rst b/user_guide_src/source/contributing/index.rst
index 0112ca065..5966070d1 100644
--- a/user_guide_src/source/contributing/index.rst
+++ b/user_guide_src/source/contributing/index.rst
@@ -29,12 +29,24 @@ own copy. This will require you to use the version control system called Git.
Support
*******
-Note that GitHub is not for general support questions!
+Please note that GitHub is not for general support questions! If you are
+having trouble using a feature of CodeIgniter, ask for help on our
+`forums <http://forum.codeigniter.com/>`_ instead.
-If you are having trouble using a feature of CodeIgniter, ask for help on the forum.
+If you are not sure whether you are using something correctly or if you
+have found a bug, again - please ask on the forums first.
-If you are wondering if you are using
-something correctly or if you have found a bug, ask on the forum first.
+********
+Security
+********
+
+Did you find a security issue in CodeIgniter?
+
+Please *don't* disclose it publicly, but e-mail us at security@codeigniter.com,
+or report it via our page on `HackerOne <https://hackerone.com/codeigniter>`_.
+
+If you've found a critical vulnerability, we'd be happy to credit you in our
+`ChangeLog <../changelog>`.
****************************
Tips for a Good Issue Report
diff --git a/user_guide_src/source/database/db_driver_reference.rst b/user_guide_src/source/database/db_driver_reference.rst
index 005e6b3dc..ea692515c 100644
--- a/user_guide_src/source/database/db_driver_reference.rst
+++ b/user_guide_src/source/database/db_driver_reference.rst
@@ -124,8 +124,8 @@ This article is intended to be a reference for them.
Enable/disable transaction "strict" mode.
When strict mode is enabled, if you are running multiple
- groups of transactions and one group fails, all groups
- will be rolled back.
+ groups of transactions and one group fails, all subsequent
+ groups will be rolled back.
If strict mode is disabled, each group is treated
autonomously, meaning a failure of one group will not
diff --git a/user_guide_src/source/database/examples.rst b/user_guide_src/source/database/examples.rst
index 8b3cc4701..5fd7fccfa 100644
--- a/user_guide_src/source/database/examples.rst
+++ b/user_guide_src/source/database/examples.rst
@@ -55,23 +55,6 @@ Standard Query With Multiple Results (Array Version)
The above result_array() function returns an array of standard array
indexes. Example: $row['title']
-Testing for Results
-===================
-
-If you run queries that might **not** produce a result, you are
-encouraged to test for a result first using the num_rows() function::
-
- $query = $this->db->query("YOUR QUERY");
- if ($query->num_rows() > 0)
- {
- foreach ($query->result() as $row)
- {
- echo $row->title;
- echo $row->name;
- echo $row->body;
- }
- }
-
Standard Query With Single Result
=================================
diff --git a/user_guide_src/source/general/controllers.rst b/user_guide_src/source/general/controllers.rst
index 7ab5a7f6a..5a111d8dc 100644
--- a/user_guide_src/source/general/controllers.rst
+++ b/user_guide_src/source/general/controllers.rst
@@ -140,9 +140,12 @@ file and set this variable::
$route['default_controller'] = 'blog';
-Where Blog is the name of the controller class you want used. If you now
+Where 'blog' is the name of the controller class you want used. If you now
load your main index.php file without specifying any URI segments you'll
-see your Hello World message by default.
+see your "Hello World" message by default.
+
+For more information, please refer to the "Reserved Routes" section of the
+:doc:`URI Routing <routing>` documentation.
Remapping Method Calls
======================
@@ -263,12 +266,12 @@ Trying to access it via the URL, like this, will not work::
Organizing Your Controllers into Sub-directories
================================================
-If you are building a large application you might find it convenient to
-organize your controllers into sub-directories. CodeIgniter permits you
-to do this.
+If you are building a large application you might want to hierarchically
+organize or structure your controllers into sub-directories. CodeIgniter
+permits you to do this.
-Simply create folders within your *application/controllers/* directory
-and place your controller classes within them.
+Simply create sub-directories under the main *application/controllers/*
+one and place your controller classes within them.
.. note:: When using this feature the first segment of your URI must
specify the folder. For example, let's say you have a controller located
@@ -281,8 +284,9 @@ and place your controller classes within them.
example.com/index.php/products/shoes/show/123
Each of your sub-directories may contain a default controller which will be
-called if the URL contains only the sub-folder. Simply name your default
-controller as specified in your *application/config/routes.php* file.
+called if the URL contains *only* the sub-directory. Simply put a controller
+in there that matches the name of your 'default_controller' as specified in
+your *application/config/routes.php* file.
CodeIgniter also permits you to remap your URIs using its :doc:`URI
Routing <routing>` feature.
diff --git a/user_guide_src/source/general/routing.rst b/user_guide_src/source/general/routing.rst
index 766e0b2ab..b2c9873ab 100644
--- a/user_guide_src/source/general/routing.rst
+++ b/user_guide_src/source/general/routing.rst
@@ -170,11 +170,16 @@ There are three reserved routes::
$route['default_controller'] = 'welcome';
-This route indicates which controller class should be loaded if the URI
-contains no data, which will be the case when people load your root URL.
-In the above example, the "welcome" class would be loaded. You are
-encouraged to always have a default route otherwise a 404 page will
-appear by default.
+This route points to the action that should be executed if the URI contains
+no data, which will be the case when people load your root URL.
+The setting accepts a **controller/method** value and ``index()`` would be
+the default method if you don't specify one. In the above example, it is
+``Welcome::index()`` that would be called.
+
+.. note:: You can NOT use a directory as a part of this setting!
+
+You are encouraged to always have a default route as otherwise a 404 page
+will appear by default.
::
@@ -182,11 +187,13 @@ appear by default.
This route indicates which controller class should be loaded if the
requested controller is not found. It will override the default 404
-error page. It won't affect to the ``show_404()`` function, which will
+error page. Same per-directory rules as with 'default_controller'
+apply here as well.
+
+It won't affect to the ``show_404()`` function, which will
continue loading the default *error_404.php* file at
*application/views/errors/error_404.php*.
-
::
$route['translate_uri_dashes'] = FALSE;
diff --git a/user_guide_src/source/general/security.rst b/user_guide_src/source/general/security.rst
index d4120d162..8afdaca31 100644
--- a/user_guide_src/source/general/security.rst
+++ b/user_guide_src/source/general/security.rst
@@ -5,6 +5,9 @@ Security
This page describes some "best practices" regarding web security, and
details CodeIgniter's internal security features.
+.. note:: If you came here looking for a security contact, please refer to
+ our `Contribution Guide <../contributing/index>`.
+
URI Security
============
diff --git a/user_guide_src/source/installation/downloads.rst b/user_guide_src/source/installation/downloads.rst
index 4fe36dd64..d3081719f 100644
--- a/user_guide_src/source/installation/downloads.rst
+++ b/user_guide_src/source/installation/downloads.rst
@@ -3,6 +3,8 @@ Downloading CodeIgniter
#######################
- `CodeIgniter v3.1.0-dev (Current version) <https://codeload.github.com/bcit-ci/CodeIgniter/zip/develop>`_
+- `CodeIgniter v3.0.3-dev <https://codeload.github.com/bcit-ci/CodeIgniter/zip/3.0-stable>`_
+- `CodeIgniter v3.0.2 <https://codeload.github.com/bcit-ci/CodeIgniter/zip/3.0.2>`_
- `CodeIgniter v3.0.1 <https://codeload.github.com/bcit-ci/CodeIgniter/zip/3.0.1>`_
- `CodeIgniter v3.0.0 <https://codeload.github.com/bcit-ci/CodeIgniter/zip/3.0.0>`_
- `CodeIgniter v2.2.3 <https://codeload.github.com/bcit-ci/CodeIgniter/zip/2.2.3>`_
diff --git a/user_guide_src/source/installation/upgrade_300.rst b/user_guide_src/source/installation/upgrade_300.rst
index 971f9e484..4b3b408a7 100644
--- a/user_guide_src/source/installation/upgrade_300.rst
+++ b/user_guide_src/source/installation/upgrade_300.rst
@@ -266,8 +266,7 @@ cause your 'Main' controller to be loaded.
However, what happens if you have an *application/controllers/admin/*
directory and the user visits ``http://example.com/admin/``?
In CodeIgniter 3, the router will look for a 'Main' controller under the
-admin/ directory as well. If not found, it will fallback to the parent
-(*application/controllers/*) directory, like in version 2.x.
+admin/ directory as well. If not found, a Not Found (404) will be triggered.
The same rule applies to the '404_override' setting.
diff --git a/user_guide_src/source/installation/upgrade_303.rst b/user_guide_src/source/installation/upgrade_303.rst
new file mode 100644
index 000000000..a98eed0d4
--- /dev/null
+++ b/user_guide_src/source/installation/upgrade_303.rst
@@ -0,0 +1,14 @@
+#############################
+Upgrading from 3.0.2 to 3.0.3
+#############################
+
+Before performing an update you should take your site offline by
+replacing the index.php file with a static one.
+
+Step 1: Update your CodeIgniter files
+=====================================
+
+Replace all files and directories in your *system/* directory.
+
+.. note:: If you have any custom developed files in these directories,
+ please make copies of them first. \ No newline at end of file
diff --git a/user_guide_src/source/installation/upgrading.rst b/user_guide_src/source/installation/upgrading.rst
index 9dda16786..de2877165 100644
--- a/user_guide_src/source/installation/upgrading.rst
+++ b/user_guide_src/source/installation/upgrading.rst
@@ -9,6 +9,7 @@ upgrading from.
:titlesonly:
Upgrading from 3.0.x to 3.1.x <upgrade_310>
+ Upgrading from 3.0.2 to 3.0.3 <upgrade_303>
Upgrading from 3.0.1 to 3.0.2 <upgrade_302>
Upgrading from 3.0.0 to 3.0.1 <upgrade_301>
Upgrading from 2.2.x to 3.0.x <upgrade_300>
diff --git a/user_guide_src/source/libraries/encryption.rst b/user_guide_src/source/libraries/encryption.rst
index 599be4df0..cac4b7921 100644
--- a/user_guide_src/source/libraries/encryption.rst
+++ b/user_guide_src/source/libraries/encryption.rst
@@ -75,7 +75,7 @@ process that allows you to be the only one who is able to decrypt data
that you've decided to hide from the eyes of the public.
After one key is used to encrypt data, that same key provides the **only**
means to decrypt it, so not only must you chose one carefully, but you
-must not lose it or you will also use the encrypted data.
+must not lose it or you will also lose access to the data.
It must be noted that to ensure maximum security, such key *should* not
only be as strong as possible, but also often changed. Such behavior
diff --git a/user_guide_src/source/libraries/form_validation.rst b/user_guide_src/source/libraries/form_validation.rst
index 140bbc65d..c288cc8c0 100644
--- a/user_guide_src/source/libraries/form_validation.rst
+++ b/user_guide_src/source/libraries/form_validation.rst
@@ -547,7 +547,10 @@ All of the native error messages are located in the following language
file: **system/language/english/form_validation_lang.php**
To set your own global custom message for a rule, you can either
-edit that file, or use the following method::
+extend/override the language file by creating your own in
+**application/language/english/form_validation_lang.php** (read more
+about this in the :doc:`Language Class <language>` documentation),
+or use the following method::
$this->form_validation->set_message('rule', 'Error Message');
diff --git a/user_guide_src/source/libraries/security.rst b/user_guide_src/source/libraries/security.rst
index 305a8e57c..f7604ef00 100644
--- a/user_guide_src/source/libraries/security.rst
+++ b/user_guide_src/source/libraries/security.rst
@@ -16,20 +16,11 @@ application, processing input data for security.
XSS Filtering
*************
-CodeIgniter comes with a Cross Site Scripting Hack prevention filter
-which can either run automatically to filter all POST and COOKIE data
-that is encountered, or you can run it on a per item basis. By default
-it does **not** run globally since it requires a bit of processing
-overhead, and since you may not need it in all cases.
-
-The XSS filter looks for commonly used techniques to trigger Javascript
-or other types of code that attempt to hijack cookies or do other
-malicious things. If anything disallowed is encountered it is rendered
-safe by converting the data to character entities.
-
-Note: This function should only be used to deal with data upon
-submission. It's not something that should be used for general runtime
-processing since it requires a fair amount of processing overhead.
+CodeIgniter comes with a Cross Site Scripting prevention filter, which
+looks for commonly used techniques to trigger JavaScript or other types
+of code that attempt to hijack cookies or do other malicious things.
+If anything disallowed is encountered it is rendered safe by converting
+the data to character entities.
To filter data through the XSS filter use the ``xss_clean()`` method::
diff --git a/user_guide_src/source/libraries/sessions.rst b/user_guide_src/source/libraries/sessions.rst
index 2034ed2b0..9c9761bbf 100644
--- a/user_guide_src/source/libraries/sessions.rst
+++ b/user_guide_src/source/libraries/sessions.rst
@@ -598,7 +598,6 @@ For MySQL::
`ip_address` varchar(45) NOT NULL,
`timestamp` int(10) unsigned DEFAULT 0 NOT NULL,
`data` blob NOT NULL,
- PRIMARY KEY (id),
KEY `ci_sessions_timestamp` (`timestamp`)
);
@@ -608,17 +607,23 @@ For PostgreSQL::
"id" varchar(40) NOT NULL,
"ip_address" varchar(45) NOT NULL,
"timestamp" bigint DEFAULT 0 NOT NULL,
- "data" text DEFAULT '' NOT NULL,
- PRIMARY KEY ("id")
+ "data" text DEFAULT '' NOT NULL
);
CREATE INDEX "ci_sessions_timestamp" ON "ci_sessions" ("timestamp");
-However, if you want to turn on the *sess_match_ip* setting, you should
-also do the following, after creating the table::
+You will also need to add a PRIMARY KEY **depending on your 'sess_match_ip'
+setting**. The examples below work both on MySQL and PostgreSQL::
+
+ // When sess_match_ip = TRUE
+ ALTER TABLE ci_sessions ADD PRIMARY KEY (id, ip_address);
+
+ // When sess_match_ip = FALSE
+ ALTER TABLE ci_sessions ADD PRIMARY KEY (id);
+
+ // To drop a previously created primary key (use when changing the setting)
+ ALTER TABLE ci_sessions DROP PRIMARY KEY;
- // Works both on MySQL and PostgreSQL
- ALTER TABLE ci_sessions ADD CONSTRAINT ci_sessions_id_ip UNIQUE (id, ip_address);
.. important:: Only MySQL and PostgreSQL databases are officially
supported, due to lack of advisory locking mechanisms on other