summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xbuild-release.sh99
-rw-r--r--system/core/CodeIgniter.php23
-rw-r--r--system/core/Common.php8
-rw-r--r--system/core/Exceptions.php1
-rw-r--r--system/core/Log.php53
-rw-r--r--system/core/Output.php62
-rw-r--r--system/core/Security.php94
-rw-r--r--system/database/DB_driver.php2
-rw-r--r--system/database/DB_forge.php4
-rw-r--r--system/database/DB_query_builder.php10
-rw-r--r--system/database/drivers/ibase/ibase_driver.php17
-rw-r--r--system/database/drivers/ibase/ibase_forge.php2
-rw-r--r--system/database/drivers/odbc/odbc_driver.php2
-rw-r--r--system/database/drivers/pdo/subdrivers/pdo_firebird_driver.php16
-rw-r--r--system/database/drivers/pdo/subdrivers/pdo_firebird_forge.php2
-rw-r--r--system/database/drivers/pdo/subdrivers/pdo_mysql_driver.php49
-rw-r--r--system/database/drivers/pdo/subdrivers/pdo_odbc_driver.php2
-rw-r--r--system/database/drivers/pdo/subdrivers/pdo_pgsql_driver.php2
-rw-r--r--system/database/drivers/pdo/subdrivers/pdo_sqlite_forge.php4
-rw-r--r--system/database/drivers/postgre/postgre_driver.php2
-rw-r--r--system/database/drivers/sqlite/sqlite_forge.php4
-rw-r--r--system/database/drivers/sqlite3/sqlite3_forge.php4
-rw-r--r--system/libraries/Email.php87
-rw-r--r--system/libraries/Encryption.php2
-rw-r--r--system/libraries/Form_validation.php4
-rw-r--r--system/libraries/Session/Session.php82
-rw-r--r--system/libraries/Session/drivers/Session_files_driver.php45
-rw-r--r--system/libraries/Zip.php74
-rw-r--r--tests/codeigniter/core/Security_test.php6
-rw-r--r--user_guide_src/source/changelog.rst36
-rw-r--r--user_guide_src/source/database/transactions.rst14
-rw-r--r--user_guide_src/source/installation/downloads.rst6
-rw-r--r--user_guide_src/source/installation/upgrade_312.rst40
-rw-r--r--user_guide_src/source/installation/upgrade_313.rst14
-rw-r--r--user_guide_src/source/installation/upgrading.rst4
-rw-r--r--user_guide_src/source/libraries/sessions.rst4
36 files changed, 756 insertions, 124 deletions
diff --git a/build-release.sh b/build-release.sh
new file mode 100755
index 000000000..6b3b31d12
--- /dev/null
+++ b/build-release.sh
@@ -0,0 +1,99 @@
+#!/usr/bin/env bash
+
+cd $(dirname $BASH_SOURCE)
+
+if [ $# -eq 0 ]; then
+ echo 'Usage: '$BASH_SOURCE' <version_number>'
+ exit 1
+fi
+
+version_number=$1
+
+if [ ${#version_number} -lt 5 ]
+then
+ echo "Provided version number is too short"
+ exit 1
+elif [ ${version_number: -4} == "-dev" ]
+then
+ echo "'-dev' releases are not allowed"
+ exit 1
+fi
+
+version_id=${version_number:0:5}
+version_id=${version_id//./}
+upgrade_rst='user_guide_src/source/installation/upgrade_'$version_id'.rst'
+
+if [ ${#version_id} -ne 3 ]
+then
+ echo "Invalid version number format"
+ exit 1
+elif [ `grep -c -F --regexp="'$version_number'" system/core/CodeIgniter.php` -ne 1 ]
+then
+ echo "Provided version number doesn't match in system/core/CodeIgniter.php"
+ exit 1
+elif [ `grep -c -F --regexp="'$version_number'" user_guide_src/source/conf.py` -ne 2 ]
+then
+ echo "Provided version number doesn't match in user_guide_src/source/conf.py"
+ exit 1
+elif [ `grep -c -F --regexp="$version_number (Current version) <https://codeload.github.com/bcit-ci/CodeIgniter/zip/$version_number>" user_guide_src/source/installation/downloads.rst` -ne 1 ]
+then
+ echo "user_guide_src/source/installation/downloads.rst doesn't appear to contain a link for this version"
+ exit 1
+elif [ ! -f "$upgrade_rst" ]
+then
+ echo "${upgrade_rst} doesn't exist"
+ exit 1
+fi
+
+echo "Running tests ..."
+
+cd tests/
+phpunit
+
+if [ $? -ne 0 ]
+then
+ echo "Build FAILED!"
+ exit 1
+fi
+
+cd ..
+cd user_guide_src/
+
+echo ""
+echo "Building HTML docs; please check output for warnings ..."
+echo ""
+
+make html
+
+echo ""
+
+if [ $? -ne 0 ]
+then
+ echo "Build FAILED!"
+ exit 1
+fi
+
+echo "Building EPUB docs; please check output for warnings ..."
+echo ""
+
+make epub
+
+echo ""
+
+if [ $? -ne 0 ]
+then
+ echo "Build FAILED!"
+ exit 1
+fi
+
+cd ..
+
+if [ -d user_guide/ ]
+then
+ rm -r user_guide/
+fi
+
+cp -r user_guide_src/build/html/ user_guide/
+cp user_guide_src/build/epub/CodeIgniter.epub "CodeIgniter ${version_number}.epub"
+
+echo "Build complete."
diff --git a/system/core/CodeIgniter.php b/system/core/CodeIgniter.php
index e6ce13d81..66ed9ec8f 100644
--- a/system/core/CodeIgniter.php
+++ b/system/core/CodeIgniter.php
@@ -55,7 +55,7 @@ defined('BASEPATH') OR exit('No direct script access allowed');
* @var string
*
*/
- define('CI_VERSION', '3.2.0-dev');
+ const CI_VERSION = '3.2.0-dev';
/*
* ------------------------------------------------------
@@ -416,10 +416,29 @@ if ( ! is_php('5.4'))
$params = array($method, array_slice($URI->rsegments, 2));
$method = '_remap';
}
- elseif ( ! is_callable(array($class, $method)))
+ elseif ( ! method_exists($class, $method))
{
$e404 = TRUE;
}
+ /**
+ * DO NOT CHANGE THIS, NOTHING ELSE WORKS!
+ *
+ * - method_exists() returns true for non-public methods, which passes the previous elseif
+ * - is_callable() returns false for PHP 4-style constructors, even if there's a __construct()
+ * - method_exists($class, '__construct') won't work because CI_Controller::__construct() is inherited
+ * - People will only complain if this doesn't work, even though it is documented that it shouldn't.
+ *
+ * ReflectionMethod::isConstructor() is the ONLY reliable check,
+ * knowing which method will be executed as a constructor.
+ */
+ elseif ( ! is_callable(array($class, $method)) && strcasecmp($class, $method) === 0)
+ {
+ $reflection = new ReflectionMethod($class, $method);
+ if ( ! $reflection->isPublic() OR $reflection->isConstructor())
+ {
+ $e404 = TRUE;
+ }
+ }
}
if ($e404)
diff --git a/system/core/Common.php b/system/core/Common.php
index 2c7651943..91c585f7d 100644
--- a/system/core/Common.php
+++ b/system/core/Common.php
@@ -544,13 +544,18 @@ if ( ! function_exists('set_status_header'))
416 => 'Requested Range Not Satisfiable',
417 => 'Expectation Failed',
422 => 'Unprocessable Entity',
+ 426 => 'Upgrade Required',
+ 428 => 'Precondition Required',
+ 429 => 'Too Many Requests',
+ 431 => 'Request Header Fields Too Large',
500 => 'Internal Server Error',
501 => 'Not Implemented',
502 => 'Bad Gateway',
503 => 'Service Unavailable',
504 => 'Gateway Timeout',
- 505 => 'HTTP Version Not Supported'
+ 505 => 'HTTP Version Not Supported',
+ 511 => 'Network Authentication Required',
);
if (isset($stati[$code]))
@@ -656,6 +661,7 @@ if ( ! function_exists('_exception_handler'))
$_error =& load_class('Exceptions', 'core');
$_error->log_exception('error', 'Exception: '.$exception->getMessage(), $exception->getFile(), $exception->getLine());
+ is_cli() OR set_status_header(500);
// Should we display the error?
if (str_ireplace(array('off', 'none', 'no', 'false', 'null'), '', ini_get('display_errors')))
{
diff --git a/system/core/Exceptions.php b/system/core/Exceptions.php
index a1c6a1970..4e10f2831 100644
--- a/system/core/Exceptions.php
+++ b/system/core/Exceptions.php
@@ -207,7 +207,6 @@ class CI_Exceptions {
}
else
{
- set_status_header(500);
$templates_path .= 'html'.DIRECTORY_SEPARATOR;
}
diff --git a/system/core/Log.php b/system/core/Log.php
index 986121526..cf6c75a95 100644
--- a/system/core/Log.php
+++ b/system/core/Log.php
@@ -104,6 +104,13 @@ class CI_Log {
*/
protected $_levels = array('ERROR' => 1, 'DEBUG' => 2, 'INFO' => 3, 'ALL' => 4);
+ /**
+ * mbstring.func_override flag
+ *
+ * @var bool
+ */
+ protected static $func_override;
+
// --------------------------------------------------------------------
/**
@@ -115,6 +122,8 @@ class CI_Log {
{
$config =& get_config();
+ isset(self::$func_override) OR self::$func_override = (extension_loaded('mbstring') && ini_get('mbstring.func_override'));
+
$this->_log_path = ($config['log_path'] !== '') ? $config['log_path'] : APPPATH.'logs/';
$this->_file_ext = (isset($config['log_file_extension']) && $config['log_file_extension'] !== '')
? ltrim($config['log_file_extension'], '.') : 'php';
@@ -208,9 +217,9 @@ class CI_Log {
$message .= $this->_format_line($level, $date, $msg);
- for ($written = 0, $length = strlen($message); $written < $length; $written += $result)
+ for ($written = 0, $length = self::strlen($message); $written < $length; $written += $result)
{
- if (($result = fwrite($fp, substr($message, $written))) === FALSE)
+ if (($result = fwrite($fp, self::substr($message, $written))) === FALSE)
{
break;
}
@@ -244,4 +253,44 @@ class CI_Log {
{
return $level.' - '.$date.' --> '.$message."\n";
}
+
+ // --------------------------------------------------------------------
+
+ /**
+ * Byte-safe strlen()
+ *
+ * @param string $str
+ * @return int
+ */
+ protected static function strlen($str)
+ {
+ return (self::$func_override)
+ ? mb_strlen($str, '8bit')
+ : strlen($str);
+ }
+
+ // --------------------------------------------------------------------
+
+ /**
+ * Byte-safe substr()
+ *
+ * @param string $str
+ * @param int $start
+ * @param int $length
+ * @return string
+ */
+ protected static function substr($str, $start, $length = NULL)
+ {
+ if (self::$func_override)
+ {
+ // mb_substr($str, $start, null, '8bit') returns an empty
+ // string on PHP 5.3
+ isset($length) OR $length = ($start >= 0 ? self::strlen($str) - $start : -$start);
+ return mb_substr($str, $start, $length, '8bit');
+ }
+
+ return isset($length)
+ ? substr($str, $start, $length)
+ : substr($str, $start);
+ }
}
diff --git a/system/core/Output.php b/system/core/Output.php
index 98db212a0..7921a54ef 100644
--- a/system/core/Output.php
+++ b/system/core/Output.php
@@ -123,6 +123,13 @@ class CI_Output {
public $parse_exec_vars = TRUE;
/**
+ * mbstring.func_override flag
+ *
+ * @var bool
+ */
+ protected static $func_override;
+
+ /**
* Class constructor
*
* Determines whether zLib output compression will be used.
@@ -138,6 +145,8 @@ class CI_Output {
&& extension_loaded('zlib')
);
+ isset(self::$func_override) OR self::$func_override = (extension_loaded('mbstring') && ini_get('mbstring.func_override'));
+
// Get mime types for later
$this->mimes =& get_mimes();
@@ -304,9 +313,9 @@ class CI_Output {
for ($i = 0, $c = count($headers); $i < $c; $i++)
{
- if (strncasecmp($header, $headers[$i], $l = strlen($header)) === 0)
+ if (strncasecmp($header, $headers[$i], $l = self::strlen($header)) === 0)
{
- return trim(substr($headers[$i], $l+1));
+ return trim(self::substr($headers[$i], $l+1));
}
}
@@ -480,13 +489,13 @@ class CI_Output {
if (isset($_SERVER['HTTP_ACCEPT_ENCODING']) && strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== FALSE)
{
header('Content-Encoding: gzip');
- header('Content-Length: '.strlen($output));
+ header('Content-Length: '.self::strlen($output));
}
else
{
// User agent doesn't support gzip compression,
// so we'll have to decompress our cache
- $output = gzinflate(substr($output, 10, -8));
+ $output = gzinflate(self::substr($output, 10, -8));
}
}
@@ -601,9 +610,9 @@ class CI_Output {
$output = $cache_info.'ENDCI--->'.$output;
- for ($written = 0, $length = strlen($output); $written < $length; $written += $result)
+ for ($written = 0, $length = self::strlen($output); $written < $length; $written += $result)
{
- if (($result = fwrite($fp, substr($output, $written))) === FALSE)
+ if (($result = fwrite($fp, self::substr($output, $written))) === FALSE)
{
break;
}
@@ -711,7 +720,7 @@ class CI_Output {
}
// Display the cache
- $this->_display(substr($cache, strlen($match[0])));
+ $this->_display(self::substr($cache, self::strlen($match[0])));
log_message('debug', 'Cache file is current. Sending it to browser.');
return TRUE;
}
@@ -797,4 +806,43 @@ class CI_Output {
}
}
+ // --------------------------------------------------------------------
+
+ /**
+ * Byte-safe strlen()
+ *
+ * @param string $str
+ * @return int
+ */
+ protected static function strlen($str)
+ {
+ return (self::$func_override)
+ ? mb_strlen($str, '8bit')
+ : strlen($str);
+ }
+
+ // --------------------------------------------------------------------
+
+ /**
+ * Byte-safe substr()
+ *
+ * @param string $str
+ * @param int $start
+ * @param int $length
+ * @return string
+ */
+ protected static function substr($str, $start, $length = NULL)
+ {
+ if (self::$func_override)
+ {
+ // mb_substr($str, $start, null, '8bit') returns an empty
+ // string on PHP 5.3
+ isset($length) OR $length = ($start >= 0 ? self::strlen($str) - $start : -$start);
+ return mb_substr($str, $start, $length, '8bit');
+ }
+
+ return isset($length)
+ ? substr($str, $start, $length)
+ : substr($str, $start);
+ }
}
diff --git a/system/core/Security.php b/system/core/Security.php
index 3a5da4fde..d0308c5f9 100644
--- a/system/core/Security.php
+++ b/system/core/Security.php
@@ -133,15 +133,16 @@ class CI_Security {
* @var array
*/
protected $_never_allowed_str = array(
- 'document.cookie' => '[removed]',
- 'document.write' => '[removed]',
- '.parentNode' => '[removed]',
- '.innerHTML' => '[removed]',
- '-moz-binding' => '[removed]',
- '<!--' => '&lt;!--',
- '-->' => '--&gt;',
- '<![CDATA[' => '&lt;![CDATA[',
- '<comment>' => '&lt;comment&gt;'
+ 'document.cookie' => '[removed]',
+ 'document.write' => '[removed]',
+ '.parentNode' => '[removed]',
+ '.innerHTML' => '[removed]',
+ '-moz-binding' => '[removed]',
+ '<!--' => '&lt;!--',
+ '-->' => '--&gt;',
+ '<![CDATA[' => '&lt;![CDATA[',
+ '<comment>' => '&lt;comment&gt;',
+ '<%' => '&lt;&#37;'
);
/**
@@ -371,11 +372,17 @@ class CI_Security {
*
* Note: Use rawurldecode() so it does not remove plus signs
*/
- do
+ if (stripos($str, '%') !== false)
{
- $str = rawurldecode($str);
+ do
+ {
+ $oldstr = $str;
+ $str = rawurldecode($str);
+ $str = preg_replace_callback('#%(?:\s*[0-9a-f]){2,}#i', array($this, '_urldecodespaces'), $str);
+ }
+ while ($oldstr !== $str);
+ unset($oldstr);
}
- while (preg_match('/%[0-9a-f]{2,}/i', $str));
/*
* Convert character entities to ASCII
@@ -466,7 +473,7 @@ class CI_Security {
if (preg_match('/<a/i', $str))
{
- $str = preg_replace_callback('#<a[^a-z0-9>]+([^>]*?)(?:>|$)#si', array($this, '_js_link_removal'), $str);
+ $str = preg_replace_callback('#<a(?:rea)?[^a-z0-9>]+([^>]*?)(?:>|$)#si', array($this, '_js_link_removal'), $str);
}
if (preg_match('/<img/i', $str))
@@ -669,6 +676,22 @@ class CI_Security {
? ENT_COMPAT | ENT_HTML5
: ENT_COMPAT;
+ if ( ! isset($_entities))
+ {
+ $_entities = array_map('strtolower', get_html_translation_table(HTML_ENTITIES, $flag, $charset));
+
+ // If we're not on PHP 5.4+, add the possibly dangerous HTML 5
+ // entities to the array manually
+ if ($flag === ENT_COMPAT)
+ {
+ $_entities[':'] = '&colon;';
+ $_entities['('] = '&lpar;';
+ $_entities[')'] = '&rpar;';
+ $_entities["\n"] = '&NewLine;';
+ $_entities["\t"] = '&Tab;';
+ }
+ }
+
do
{
$str_compare = $str;
@@ -676,22 +699,6 @@ class CI_Security {
// Decode standard entities, avoiding false positives
if (preg_match_all('/&[a-z]{2,}(?![a-z;])/i', $str, $matches))
{
- if ( ! isset($_entities))
- {
- $_entities = array_map('strtolower', get_html_translation_table(HTML_ENTITIES, $flag, $charset));
-
- // If we're not on PHP 5.4+, add the possibly dangerous HTML 5
- // entities to the array manually
- if ($flag === ENT_COMPAT)
- {
- $_entities[':'] = '&colon;';
- $_entities['('] = '&lpar;';
- $_entities[')'] = '&rpar;';
- $_entities["\n"] = '&newline;';
- $_entities["\t"] = '&tab;';
- }
- }
-
$replace = array();
$matches = array_unique(array_map('strtolower', $matches[0]));
foreach ($matches as &$match)
@@ -702,7 +709,7 @@ class CI_Security {
}
}
- $str = str_ireplace(array_keys($replace), array_values($replace), $str);
+ $str = str_replace(array_keys($replace), array_values($replace), $str);
}
// Decode numeric & UTF16 two byte entities
@@ -711,6 +718,11 @@ class CI_Security {
$flag,
$charset
);
+
+ if ($flag === ENT_COMPAT)
+ {
+ $str = str_replace(array_values($_entities), array_keys($_entities), $str);
+ }
}
while ($str_compare !== $str);
return $str;
@@ -770,6 +782,24 @@ class CI_Security {
// ----------------------------------------------------------------
/**
+ * URL-decode taking spaces into account
+ *
+ * @see https://github.com/bcit-ci/CodeIgniter/issues/4877
+ * @param array $matches
+ * @return string
+ */
+ protected function _urldecodespaces($matches)
+ {
+ $input = $matches[0];
+ $nospaces = preg_replace('#\s+#', '', $input);
+ return ($nospaces === $input)
+ ? $input
+ : rawurldecode($nospaces);
+ }
+
+ // ----------------------------------------------------------------
+
+ /**
* Compact Exploded Words
*
* Callback method for xss_clean() to remove whitespace from
@@ -798,7 +828,7 @@ class CI_Security {
protected function _sanitize_naughty_html($matches)
{
static $naughty_tags = array(
- 'alert', 'prompt', 'confirm', 'applet', 'audio', 'basefont', 'base', 'behavior', 'bgsound',
+ 'alert', 'area', 'prompt', 'confirm', 'applet', 'audio', 'basefont', 'base', 'behavior', 'bgsound',
'blink', 'body', 'embed', 'expression', 'form', 'frameset', 'frame', 'head', 'html', 'ilayer',
'iframe', 'input', 'button', 'select', 'isindex', 'layer', 'link', 'meta', 'keygen', 'object',
'plaintext', 'style', 'script', 'textarea', 'title', 'math', 'video', 'svg', 'xml', 'xss'
@@ -895,7 +925,7 @@ class CI_Security {
return str_replace(
$match[1],
preg_replace(
- '#href=.*?(?:(?:alert|prompt|confirm)(?:\(|&\#40;)|javascript:|livescript:|mocha:|charset=|window\.|document\.|\.cookie|<script|<xss|data\s*:)#si',
+ '#href=.*?(?:(?:alert|prompt|confirm)(?:\(|&\#40;)|javascript:|livescript:|mocha:|charset=|window\.|document\.|\.cookie|<script|<xss|d\s*a\s*t\s*a\s*:)#si',
'',
$this->_filter_attributes($match[1])
),
diff --git a/system/database/DB_driver.php b/system/database/DB_driver.php
index 43e8eeac6..6f39cc963 100644
--- a/system/database/DB_driver.php
+++ b/system/database/DB_driver.php
@@ -918,7 +918,7 @@ abstract class CI_DB_driver {
*/
public function compile_binds($sql, $binds)
{
- if (empty($binds) OR empty($this->bind_marker) OR strpos($sql, $this->bind_marker) === FALSE)
+ if (empty($this->bind_marker) OR strpos($sql, $this->bind_marker) === FALSE)
{
return $sql;
}
diff --git a/system/database/DB_forge.php b/system/database/DB_forge.php
index 826aa1ebf..ed6f4b672 100644
--- a/system/database/DB_forge.php
+++ b/system/database/DB_forge.php
@@ -184,7 +184,7 @@ abstract class CI_DB_forge {
{
return ($this->db->db_debug) ? $this->db->display_error('db_unsupported_feature') : FALSE;
}
- elseif ( ! $this->db->query(sprintf($this->_create_database, $db_name, $this->db->char_set, $this->db->dbcollat)))
+ elseif ( ! $this->db->query(sprintf($this->_create_database, $this->db->escape_identifiers($db_name), $this->db->char_set, $this->db->dbcollat)))
{
return ($this->db->db_debug) ? $this->db->display_error('db_unable_to_drop') : FALSE;
}
@@ -211,7 +211,7 @@ abstract class CI_DB_forge {
{
return ($this->db->db_debug) ? $this->db->display_error('db_unsupported_feature') : FALSE;
}
- elseif ( ! $this->db->query(sprintf($this->_drop_database, $db_name)))
+ elseif ( ! $this->db->query(sprintf($this->_drop_database, $this->db->escape_identifiers($db_name))))
{
return ($this->db->db_debug) ? $this->db->display_error('db_unable_to_drop') : FALSE;
}
diff --git a/system/database/DB_query_builder.php b/system/database/DB_query_builder.php
index 7a008eeb8..5a86ce50f 100644
--- a/system/database/DB_query_builder.php
+++ b/system/database/DB_query_builder.php
@@ -679,7 +679,7 @@ abstract class CI_DB_query_builder extends CI_DB_driver {
// value appears not to have been set, assign the test to IS NULL
$k .= ' IS NULL';
}
- elseif (preg_match('/\s*(!?=|<>|IS(?:\s+NOT)?)\s*$/i', $k, $match, PREG_OFFSET_CAPTURE))
+ elseif (preg_match('/\s*(!?=|<>|\sIS(?:\s+NOT)?\s)\s*$/i', $k, $match, PREG_OFFSET_CAPTURE))
{
$k = substr($k, 0, $match[0][1]).($match[1][0] === '=' ? ' IS NULL' : ' IS NOT NULL');
}
@@ -1915,7 +1915,7 @@ abstract class CI_DB_query_builder extends CI_DB_driver {
$affected_rows = 0;
for ($i = 0, $total = count($this->qb_set); $i < $total; $i += $batch_size)
{
- if ($this->query($this->_update_batch($this->protect_identifiers($table, TRUE, NULL, FALSE), array_slice($this->qb_set, $i, $batch_size), $this->protect_identifiers($index))))
+ if ($this->query($this->_update_batch($this->protect_identifiers($table, TRUE, NULL, FALSE), array_slice($this->qb_set, $i, $batch_size), $index)))
{
$affected_rows += $this->affected_rows();
}
@@ -1941,6 +1941,8 @@ abstract class CI_DB_query_builder extends CI_DB_driver {
*/
protected function _update_batch($table, $values, $index)
{
+ $index_escaped = $this->protect_identifiers($index);
+
$ids = array();
foreach ($values as $key => $val)
{
@@ -1950,7 +1952,7 @@ abstract class CI_DB_query_builder extends CI_DB_driver {
{
if ($field !== $index)
{
- $final[$field][] = 'WHEN '.$index.' = '.$val[$index].' THEN '.$val[$field];
+ $final[$field][] = 'WHEN '.$index_escaped.' = '.$val[$index].' THEN '.$val[$field];
}
}
}
@@ -1963,7 +1965,7 @@ abstract class CI_DB_query_builder extends CI_DB_driver {
.'ELSE '.$k.' END, ';
}
- $this->where($index.' IN('.implode(',', $ids).')', NULL, FALSE);
+ $this->where($index_escaped.' IN('.implode(',', $ids).')', NULL, FALSE);
return 'UPDATE '.$table.' SET '.substr($cases, 0, -2).$this->_compile_wh('qb_where');
}
diff --git a/system/database/drivers/ibase/ibase_driver.php b/system/database/drivers/ibase/ibase_driver.php
index c1055c1e6..671a353bc 100644
--- a/system/database/drivers/ibase/ibase_driver.php
+++ b/system/database/drivers/ibase/ibase_driver.php
@@ -384,6 +384,23 @@ class CI_DB_ibase_driver extends CI_DB {
// --------------------------------------------------------------------
/**
+ * Insert batch statement
+ *
+ * Generates a platform-specific insert string from the supplied data.
+ *
+ * @param string $table Table name
+ * @param array $keys INSERT keys
+ * @param array $values INSERT values
+ * @return string|bool
+ */
+ protected function _insert_batch($table, $keys, $values)
+ {
+ return ($this->db->db_debug) ? $this->db->display_error('db_unsupported_feature') : FALSE;
+ }
+
+ // --------------------------------------------------------------------
+
+ /**
* Close DB Connection
*
* @return void
diff --git a/system/database/drivers/ibase/ibase_forge.php b/system/database/drivers/ibase/ibase_forge.php
index 9c358c365..b35cc3749 100644
--- a/system/database/drivers/ibase/ibase_forge.php
+++ b/system/database/drivers/ibase/ibase_forge.php
@@ -111,7 +111,7 @@ class CI_DB_ibase_forge extends CI_DB_forge {
* @param string $db_name (ignored)
* @return bool
*/
- public function drop_database($db_name = '')
+ public function drop_database($db_name)
{
if ( ! ibase_drop_db($this->conn_id))
{
diff --git a/system/database/drivers/odbc/odbc_driver.php b/system/database/drivers/odbc/odbc_driver.php
index 63df2963d..dbce1cf79 100644
--- a/system/database/drivers/odbc/odbc_driver.php
+++ b/system/database/drivers/odbc/odbc_driver.php
@@ -298,7 +298,7 @@ class CI_DB_odbc_driver extends CI_DB_driver {
*/
public function is_write_type($sql)
{
- if (preg_match('#^(INSERT|UPDATE).*RETURNING\s.+(\,\s?.+)*$#i', $sql))
+ if (preg_match('#^(INSERT|UPDATE).*RETURNING\s.+(\,\s?.+)*$#is', $sql))
{
return FALSE;
}
diff --git a/system/database/drivers/pdo/subdrivers/pdo_firebird_driver.php b/system/database/drivers/pdo/subdrivers/pdo_firebird_driver.php
index 96dcc5ec1..7811d3da4 100644
--- a/system/database/drivers/pdo/subdrivers/pdo_firebird_driver.php
+++ b/system/database/drivers/pdo/subdrivers/pdo_firebird_driver.php
@@ -260,4 +260,20 @@ class CI_DB_pdo_firebird_driver extends CI_DB_pdo_driver {
return preg_replace('`SELECT`i', 'SELECT '.$select, $sql);
}
+ // --------------------------------------------------------------------
+
+ /**
+ * Insert batch statement
+ *
+ * Generates a platform-specific insert string from the supplied data.
+ *
+ * @param string $table Table name
+ * @param array $keys INSERT keys
+ * @param array $values INSERT values
+ * @return string|bool
+ */
+ protected function _insert_batch($table, $keys, $values)
+ {
+ return ($this->db->db_debug) ? $this->db->display_error('db_unsupported_feature') : FALSE;
+ }
}
diff --git a/system/database/drivers/pdo/subdrivers/pdo_firebird_forge.php b/system/database/drivers/pdo/subdrivers/pdo_firebird_forge.php
index 256fa1413..50df76905 100644
--- a/system/database/drivers/pdo/subdrivers/pdo_firebird_forge.php
+++ b/system/database/drivers/pdo/subdrivers/pdo_firebird_forge.php
@@ -97,7 +97,7 @@ class CI_DB_pdo_firebird_forge extends CI_DB_pdo_forge {
* @param string $db_name (ignored)
* @return bool
*/
- public function drop_database($db_name = '')
+ public function drop_database($db_name)
{
if ( ! ibase_drop_db($this->conn_id))
{
diff --git a/system/database/drivers/pdo/subdrivers/pdo_mysql_driver.php b/system/database/drivers/pdo/subdrivers/pdo_mysql_driver.php
index 3631cdf7a..6452b787b 100644
--- a/system/database/drivers/pdo/subdrivers/pdo_mysql_driver.php
+++ b/system/database/drivers/pdo/subdrivers/pdo_mysql_driver.php
@@ -216,6 +216,55 @@ class CI_DB_pdo_mysql_driver extends CI_DB_pdo_driver {
// --------------------------------------------------------------------
/**
+ * Begin Transaction
+ *
+ * @return bool
+ */
+ protected function _trans_begin()
+ {
+ $this->conn_id->setAttribute(PDO::ATTR_AUTOCOMMIT, FALSE);
+ return $this->conn_id->beginTransaction();
+ }
+
+ // --------------------------------------------------------------------
+
+ /**
+ * Commit Transaction
+ *
+ * @return bool
+ */
+ protected function _trans_commit()
+ {
+ if ($this->conn_id->commit())
+ {
+ $this->conn_id->setAttribute(PDO::ATTR_AUTOCOMMIT, TRUE);
+ return TRUE;
+ }
+
+ return FALSE;
+ }
+
+ // --------------------------------------------------------------------
+
+ /**
+ * Rollback Transaction
+ *
+ * @return bool
+ */
+ protected function _trans_rollback()
+ {
+ if ($this->conn_id->rollBack())
+ {
+ $this->conn_id->setAttribute(PDO::ATTR_AUTOCOMMIT, TRUE);
+ return TRUE;
+ }
+
+ return FALSE;
+ }
+
+ // --------------------------------------------------------------------
+
+ /**
* Show table query
*
* Generates a platform-specific query string so that the table names can be fetched
diff --git a/system/database/drivers/pdo/subdrivers/pdo_odbc_driver.php b/system/database/drivers/pdo/subdrivers/pdo_odbc_driver.php
index 82554ec80..ebe1ed6f0 100644
--- a/system/database/drivers/pdo/subdrivers/pdo_odbc_driver.php
+++ b/system/database/drivers/pdo/subdrivers/pdo_odbc_driver.php
@@ -181,7 +181,7 @@ class CI_DB_pdo_odbc_driver extends CI_DB_pdo_driver {
*/
public function is_write_type($sql)
{
- if (preg_match('#^(INSERT|UPDATE).*RETURNING\s.+(\,\s?.+)*$#i', $sql))
+ if (preg_match('#^(INSERT|UPDATE).*RETURNING\s.+(\,\s?.+)*$#is', $sql))
{
return FALSE;
}
diff --git a/system/database/drivers/pdo/subdrivers/pdo_pgsql_driver.php b/system/database/drivers/pdo/subdrivers/pdo_pgsql_driver.php
index ee8f76348..9483d2457 100644
--- a/system/database/drivers/pdo/subdrivers/pdo_pgsql_driver.php
+++ b/system/database/drivers/pdo/subdrivers/pdo_pgsql_driver.php
@@ -154,7 +154,7 @@ class CI_DB_pdo_pgsql_driver extends CI_DB_pdo_driver {
*/
public function is_write_type($sql)
{
- if (preg_match('#^(INSERT|UPDATE).*RETURNING\s.+(\,\s?.+)*$#i', $sql))
+ if (preg_match('#^(INSERT|UPDATE).*RETURNING\s.+(\,\s?.+)*$#is', $sql))
{
return FALSE;
}
diff --git a/system/database/drivers/pdo/subdrivers/pdo_sqlite_forge.php b/system/database/drivers/pdo/subdrivers/pdo_sqlite_forge.php
index f6f9bb481..b124bcad1 100644
--- a/system/database/drivers/pdo/subdrivers/pdo_sqlite_forge.php
+++ b/system/database/drivers/pdo/subdrivers/pdo_sqlite_forge.php
@@ -101,7 +101,7 @@ class CI_DB_pdo_sqlite_forge extends CI_DB_pdo_forge {
* @param string $db_name (ignored)
* @return bool
*/
- public function create_database($db_name = '')
+ public function create_database($db_name)
{
// In SQLite, a database is created when you connect to the database.
// We'll return TRUE so that an error isn't generated
@@ -116,7 +116,7 @@ class CI_DB_pdo_sqlite_forge extends CI_DB_pdo_forge {
* @param string $db_name (ignored)
* @return bool
*/
- public function drop_database($db_name = '')
+ public function drop_database($db_name)
{
// In SQLite, a database is dropped when we delete a file
if (file_exists($this->db->database))
diff --git a/system/database/drivers/postgre/postgre_driver.php b/system/database/drivers/postgre/postgre_driver.php
index ce21260ef..c7c827ea4 100644
--- a/system/database/drivers/postgre/postgre_driver.php
+++ b/system/database/drivers/postgre/postgre_driver.php
@@ -282,7 +282,7 @@ class CI_DB_postgre_driver extends CI_DB {
*/
public function is_write_type($sql)
{
- if (preg_match('#^(INSERT|UPDATE).*RETURNING\s.+(\,\s?.+)*$#i', $sql))
+ if (preg_match('#^(INSERT|UPDATE).*RETURNING\s.+(\,\s?.+)*$#is', $sql))
{
return FALSE;
}
diff --git a/system/database/drivers/sqlite/sqlite_forge.php b/system/database/drivers/sqlite/sqlite_forge.php
index 8a1659430..3ad3477e4 100644
--- a/system/database/drivers/sqlite/sqlite_forge.php
+++ b/system/database/drivers/sqlite/sqlite_forge.php
@@ -75,7 +75,7 @@ class CI_DB_sqlite_forge extends CI_DB_forge {
* @param string $db_name (ignored)
* @return bool
*/
- public function create_database($db_name = '')
+ public function create_database($db_name)
{
// In SQLite, a database is created when you connect to the database.
// We'll return TRUE so that an error isn't generated
@@ -90,7 +90,7 @@ class CI_DB_sqlite_forge extends CI_DB_forge {
* @param string $db_name (ignored)
* @return bool
*/
- public function drop_database($db_name = '')
+ public function drop_database($db_name)
{
if ( ! file_exists($this->db->database) OR ! @unlink($this->db->database))
{
diff --git a/system/database/drivers/sqlite3/sqlite3_forge.php b/system/database/drivers/sqlite3/sqlite3_forge.php
index 43cbe33e5..c45472f54 100644
--- a/system/database/drivers/sqlite3/sqlite3_forge.php
+++ b/system/database/drivers/sqlite3/sqlite3_forge.php
@@ -87,7 +87,7 @@ class CI_DB_sqlite3_forge extends CI_DB_forge {
* @param string $db_name
* @return bool
*/
- public function create_database($db_name = '')
+ public function create_database($db_name)
{
// In SQLite, a database is created when you connect to the database.
// We'll return TRUE so that an error isn't generated
@@ -102,7 +102,7 @@ class CI_DB_sqlite3_forge extends CI_DB_forge {
* @param string $db_name (ignored)
* @return bool
*/
- public function drop_database($db_name = '')
+ public function drop_database($db_name)
{
// In SQLite, a database is dropped when we delete a file
if (file_exists($this->db->database))
diff --git a/system/libraries/Email.php b/system/libraries/Email.php
index 6a7e2b3a7..525a1277e 100644
--- a/system/libraries/Email.php
+++ b/system/libraries/Email.php
@@ -374,6 +374,13 @@ class CI_Email {
5 => '5 (Lowest)'
);
+ /**
+ * mbstring.func_override flag
+ *
+ * @var bool
+ */
+ protected static $func_override;
+
// --------------------------------------------------------------------
/**
@@ -390,6 +397,8 @@ class CI_Email {
$this->initialize($config);
$this->_safe_mode = ( ! is_php('5.4') && ini_get('safe_mode'));
+ isset(self::$func_override) OR self::$func_override = (extension_loaded('mbstring') && ini_get('mbstring.func_override'));
+
log_message('info', 'Email Class Initialized');
}
@@ -1037,7 +1046,7 @@ class CI_Email {
{
if (function_exists('idn_to_ascii') && $atpos = strpos($email, '@'))
{
- $email = substr($email, 0, ++$atpos).idn_to_ascii(substr($email, $atpos));
+ $email = self::substr($email, 0, ++$atpos).idn_to_ascii(self::substr($email, $atpos));
}
return (bool) filter_var($email, FILTER_VALIDATE_EMAIL);
@@ -1154,7 +1163,7 @@ class CI_Email {
{
// Is the line within the allowed character count?
// If so we'll join it to the output and continue
- if (mb_strlen($line) <= $charlim)
+ if (self::strlen($line) <= $charlim)
{
$output .= $line.$this->newline;
continue;
@@ -1170,10 +1179,10 @@ class CI_Email {
}
// Trim the word down
- $temp .= mb_substr($line, 0, $charlim - 1);
- $line = mb_substr($line, $charlim - 1);
+ $temp .= self::substr($line, 0, $charlim - 1);
+ $line = self::substr($line, $charlim - 1);
}
- while (mb_strlen($line) > $charlim);
+ while (self::strlen($line) > $charlim);
// If $temp contains data it means we had to split up an over-length
// word into smaller chunks so we'll add it back to our current line
@@ -1385,7 +1394,7 @@ class CI_Email {
$this->_header_str .= $hdr;
}
- strlen($body) && $body .= $this->newline.$this->newline;
+ self::strlen($body) && $body .= $this->newline.$this->newline;
$body .= $this->_get_mime_message().$this->newline.$this->newline
.'--'.$last_boundary.$this->newline
@@ -1532,7 +1541,7 @@ class CI_Email {
foreach (explode("\n", $str) as $line)
{
- $length = strlen($line);
+ $length = self::strlen($line);
$temp = '';
// Loop through each character in the line to add soft-wrap
@@ -1567,7 +1576,7 @@ class CI_Email {
// If we're at the character limit, add the line to the output,
// reset our temp variable, and keep on chuggin'
- if ((strlen($temp) + strlen($char)) >= 76)
+ if ((self::strlen($temp) + self::strlen($char)) >= 76)
{
$output .= $temp.$escape.$this->crlf;
$temp = '';
@@ -1582,7 +1591,7 @@ class CI_Email {
}
// get rid of extra CRLF tacked onto the end
- return substr($output, 0, strlen($this->crlf) * -1);
+ return self::substr($output, 0, self::strlen($this->crlf) * -1);
}
// --------------------------------------------------------------------
@@ -1624,7 +1633,7 @@ class CI_Email {
// iconv_mime_encode() will always put a header field name.
// We've passed it an empty one, but it still prepends our
// encoded string with ': ', so we need to strip it.
- return substr($output, 2);
+ return self::substr($output, 2);
}
$chars = iconv_strlen($str, 'UTF-8');
@@ -1636,10 +1645,10 @@ class CI_Email {
}
// We might already have this set for UTF-8
- isset($chars) OR $chars = strlen($str);
+ isset($chars) OR $chars = self::strlen($str);
$output = '=?'.$this->charset.'?Q?';
- for ($i = 0, $length = strlen($output); $i < $chars; $i++)
+ for ($i = 0, $length = self::strlen($output); $i < $chars; $i++)
{
$chr = ($this->charset === 'UTF-8' && ICONV_ENABLED === TRUE)
? '='.implode('=', str_split(strtoupper(bin2hex(iconv_substr($str, $i, 1, $this->charset))), 2))
@@ -1647,11 +1656,11 @@ class CI_Email {
// RFC 2045 sets a limit of 76 characters per line.
// We'll append ?= to the end of each line though.
- if ($length + ($l = strlen($chr)) > 74)
+ if ($length + ($l = self::strlen($chr)) > 74)
{
$output .= '?='.$this->crlf // EOL
.' =?'.$this->charset.'?Q?'.$chr; // New line
- $length = 6 + strlen($this->charset) + $l; // Reset the length for the new line
+ $length = 6 + self::strlen($this->charset) + $l; // Reset the length for the new line
}
else
{
@@ -1744,14 +1753,14 @@ class CI_Email {
if ($i === $float)
{
- $chunk[] = substr($set, 1);
+ $chunk[] = self::substr($set, 1);
$float += $this->bcc_batch_size;
$set = '';
}
if ($i === $c-1)
{
- $chunk[] = substr($set, 1);
+ $chunk[] = self::substr($set, 1);
}
}
@@ -2109,7 +2118,7 @@ class CI_Email {
$this->_debug_msg[] = '<pre>'.$cmd.': '.$reply.'</pre>';
- if ((int) substr($reply, 0, 3) !== $resp)
+ if ((int) self::substr($reply, 0, 3) !== $resp)
{
$this->_set_error_message('lang:email_smtp_error', $reply);
return FALSE;
@@ -2196,9 +2205,9 @@ class CI_Email {
protected function _send_data($data)
{
$data .= $this->newline;
- for ($written = $timestamp = 0, $length = strlen($data); $written < $length; $written += $result)
+ for ($written = $timestamp = 0, $length = self::strlen($data); $written < $length; $written += $result)
{
- if (($result = fwrite($this->_smtp_connect, substr($data, $written))) === FALSE)
+ if (($result = fwrite($this->_smtp_connect, self::substr($data, $written))) === FALSE)
{
break;
}
@@ -2382,4 +2391,44 @@ class CI_Email {
{
is_resource($this->_smtp_connect) && $this->_send_command('quit');
}
+
+ // --------------------------------------------------------------------
+
+ /**
+ * Byte-safe strlen()
+ *
+ * @param string $str
+ * @return int
+ */
+ protected static function strlen($str)
+ {
+ return (self::$func_override)
+ ? mb_strlen($str, '8bit')
+ : strlen($str);
+ }
+
+ // --------------------------------------------------------------------
+
+ /**
+ * Byte-safe substr()
+ *
+ * @param string $str
+ * @param int $start
+ * @param int $length
+ * @return string
+ */
+ protected static function substr($str, $start, $length = NULL)
+ {
+ if (self::$func_override)
+ {
+ // mb_substr($str, $start, null, '8bit') returns an empty
+ // string on PHP 5.3
+ isset($length) OR $length = ($start >= 0 ? self::strlen($str) - $start : -$start);
+ return mb_substr($str, $start, $length, '8bit');
+ }
+
+ return isset($length)
+ ? substr($str, $start, $length)
+ : substr($str, $start);
+ }
}
diff --git a/system/libraries/Encryption.php b/system/libraries/Encryption.php
index 06284c2ed..545081b3b 100644
--- a/system/libraries/Encryption.php
+++ b/system/libraries/Encryption.php
@@ -907,7 +907,7 @@ class CI_Encryption {
* Byte-safe strlen()
*
* @param string $str
- * @return integer
+ * @return int
*/
protected static function strlen($str)
{
diff --git a/system/libraries/Form_validation.php b/system/libraries/Form_validation.php
index 4380dd276..384caf000 100644
--- a/system/libraries/Form_validation.php
+++ b/system/libraries/Form_validation.php
@@ -1233,9 +1233,9 @@ class CI_Form_validation {
*/
public function valid_email($str)
{
- if (function_exists('idn_to_ascii') && $atpos = strpos($str, '@'))
+ if (function_exists('idn_to_ascii') && sscanf($str, '%[^@]@%s', $name, $domain) === 2)
{
- $str = substr($str, 0, ++$atpos).idn_to_ascii(substr($str, $atpos));
+ $str = $name.'@'.idn_to_ascii($domain);
}
return (bool) filter_var($str, FILTER_VALIDATE_EMAIL);
diff --git a/system/libraries/Session/Session.php b/system/libraries/Session/Session.php
index 3b391a8ef..01989d2d7 100644
--- a/system/libraries/Session/Session.php
+++ b/system/libraries/Session/Session.php
@@ -57,6 +57,7 @@ class CI_Session {
protected $_driver = 'files';
protected $_config;
+ protected $_sid_regexp;
// ------------------------------------------------------------------------
@@ -99,6 +100,7 @@ class CI_Session {
// Configuration ...
$this->_configure($params);
+ $this->_config['_sid_regexp'] = $this->_sid_regexp;
$class = new $class($this->_config);
if ($class instanceof SessionHandlerInterface)
@@ -131,7 +133,7 @@ class CI_Session {
if (isset($_COOKIE[$this->_config['cookie_name']])
&& (
! is_string($_COOKIE[$this->_config['cookie_name']])
- OR ! preg_match('/^[0-9a-f]{40}$/', $_COOKIE[$this->_config['cookie_name']])
+ OR ! preg_match('#\A'.$this->_sid_regexp.'\z#', $_COOKIE[$this->_config['cookie_name']])
)
)
{
@@ -315,8 +317,82 @@ class CI_Session {
ini_set('session.use_strict_mode', 1);
ini_set('session.use_cookies', 1);
ini_set('session.use_only_cookies', 1);
- ini_set('session.hash_function', 1);
- ini_set('session.hash_bits_per_character', 4);
+
+ $this->_configure_sid_length();
+ }
+
+ // ------------------------------------------------------------------------
+
+ /**
+ * Configure session ID length
+ *
+ * To make life easier, we used to force SHA-1 and 4 bits per
+ * character on everyone. And of course, someone was unhappy.
+ *
+ * Then PHP 7.1 broke backwards-compatibility because ext/session
+ * is such a mess that nobody wants to touch it with a pole stick,
+ * and the one guy who does, nobody has the energy to argue with.
+ *
+ * So we were forced to make changes, and OF COURSE something was
+ * going to break and now we have this pile of shit. -- Narf
+ *
+ * @return void
+ */
+ protected function _configure_sid_length()
+ {
+ if (PHP_VERSION_ID < 70100)
+ {
+ $hash_function = ini_get('session.hash_function');
+ if (ctype_digit($hash_function))
+ {
+ if ($hash_function !== '1')
+ {
+ ini_set('session.hash_function', 1);
+ }
+
+ $bits = 160;
+ }
+ elseif ( ! in_array($hash_function, hash_algos(), TRUE))
+ {
+ ini_set('session.hash_function', 1);
+ $bits = 160;
+ }
+ elseif (($bits = strlen(hash($hash_function, 'dummy', false)) * 4) < 160)
+ {
+ ini_set('session.hash_function', 1);
+ $bits = 160;
+ }
+
+ $bits_per_character = (int) ini_get('session.hash_bits_per_character');
+ $sid_length = (int) ceil($bits / $bits_per_character);
+ }
+ else
+ {
+ $bits_per_character = (int) ini_get('session.sid_bits_per_character');
+ $sid_length = (int) ini_get('session.sid_length');
+ if (($bits = $sid_length * $bits_per_character) < 160)
+ {
+ // Add as many more characters as necessary to reach at least 160 bits
+ $sid_length += (int) ceil((160 % $bits) / $bits_per_character);
+ ini_set('session.sid_length', $sid_length);
+ }
+ }
+
+ // Yes, 4,5,6 are the only known possible values as of 2016-10-27
+ switch ($bits_per_character)
+ {
+ case 4:
+ $this->_sid_regexp = '[0-9a-f]';
+ break;
+ case 5:
+ $this->_sid_regexp = '[0-9a-v]';
+ break;
+ case 6:
+ $this->_sid_regexp = '[0-9a-zA-Z,-]';
+ break;
+ }
+
+ $this->_sid_regexp .= '{'.$sid_length.'}';
}
// ------------------------------------------------------------------------
diff --git a/system/libraries/Session/drivers/Session_files_driver.php b/system/libraries/Session/drivers/Session_files_driver.php
index bf4df8b20..37315d3cd 100644
--- a/system/libraries/Session/drivers/Session_files_driver.php
+++ b/system/libraries/Session/drivers/Session_files_driver.php
@@ -76,6 +76,20 @@ class CI_Session_files_driver extends CI_Session_driver implements SessionHandle
*/
protected $_file_new;
+ /**
+ * Validate SID regular expression
+ *
+ * @var string
+ */
+ protected $_sid_regexp;
+
+ /**
+ * mbstring.func_override flag
+ *
+ * @var bool
+ */
+ protected static $func_override;
+
// ------------------------------------------------------------------------
/**
@@ -98,6 +112,10 @@ class CI_Session_files_driver extends CI_Session_driver implements SessionHandle
log_message('debug', 'Session: "sess_save_path" is empty; using "session.save_path" value from php.ini.');
$this->_config['save_path'] = rtrim(ini_get('session.save_path'), '/\\');
}
+
+ $this->_sid_regexp = $this->_config['_sid_regexp'];
+
+ isset(self::$func_override) OR self::$func_override = (extension_loaded('mbstring') && ini_get('mbstring.func_override'));
}
// ------------------------------------------------------------------------
@@ -187,7 +205,7 @@ class CI_Session_files_driver extends CI_Session_driver implements SessionHandle
}
$session_data = '';
- for ($read = 0, $length = filesize($this->_file_path.$session_id); $read < $length; $read += strlen($buffer))
+ for ($read = 0, $length = filesize($this->_file_path.$session_id); $read < $length; $read += self::strlen($buffer))
{
if (($buffer = fread($this->_file_handle, $length - $read)) === FALSE)
{
@@ -343,10 +361,13 @@ class CI_Session_files_driver extends CI_Session_driver implements SessionHandle
$ts = time() - $maxlifetime;
+ $pattern = ($this->_config['match_ip'] === TRUE)
+ ? '[0-9a-f]{32}'
+ : '';
+
$pattern = sprintf(
- '/^%s[0-9a-f]{%d}$/',
- preg_quote($this->_config['cookie_name'], '/'),
- ($this->_config['match_ip'] === TRUE ? 72 : 40)
+ '#\A%s'.$pattern.$this->_sid_regexp.'\z#',
+ preg_quote($this->_config['cookie_name'])
);
while (($file = readdir($directory)) !== FALSE)
@@ -368,4 +389,18 @@ class CI_Session_files_driver extends CI_Session_driver implements SessionHandle
return $this->_success;
}
-} \ No newline at end of file
+ // --------------------------------------------------------------------
+
+ /**
+ * Byte-safe strlen()
+ *
+ * @param string $str
+ * @return int
+ */
+ protected static function strlen($str)
+ {
+ return (self::$func_override)
+ ? mb_strlen($str, '8bit')
+ : strlen($str);
+ }
+}
diff --git a/system/libraries/Zip.php b/system/libraries/Zip.php
index 140ad7212..25315c92e 100644
--- a/system/libraries/Zip.php
+++ b/system/libraries/Zip.php
@@ -106,12 +106,21 @@ class CI_Zip {
public $compression_level = 2;
/**
+ * mbstring.func_override flag
+ *
+ * @var bool
+ */
+ protected static $func_override;
+
+ /**
* Initialize zip compression class
*
* @return void
*/
public function __construct()
{
+ isset(self::$func_override) OR self::$func_override = (extension_loaded('mbstring') && ini_get('mbstring.func_override'));
+
$this->now = time();
log_message('info', 'Zip Compression Class Initialized');
}
@@ -182,7 +191,7 @@ class CI_Zip {
.pack('V', 0) // crc32
.pack('V', 0) // compressed filesize
.pack('V', 0) // uncompressed filesize
- .pack('v', strlen($dir)) // length of pathname
+ .pack('v', self::strlen($dir)) // length of pathname
.pack('v', 0) // extra field length
.$dir
// below is "data descriptor" segment
@@ -197,7 +206,7 @@ class CI_Zip {
.pack('V',0) // crc32
.pack('V',0) // compressed filesize
.pack('V',0) // uncompressed filesize
- .pack('v', strlen($dir)) // length of pathname
+ .pack('v', self::strlen($dir)) // length of pathname
.pack('v', 0) // extra field length
.pack('v', 0) // file comment length
.pack('v', 0) // disk number start
@@ -206,7 +215,7 @@ class CI_Zip {
.pack('V', $this->offset) // relative offset of local header
.$dir;
- $this->offset = strlen($this->zipdata);
+ $this->offset = self::strlen($this->zipdata);
$this->entries++;
}
@@ -255,10 +264,10 @@ class CI_Zip {
{
$filepath = str_replace('\\', '/', $filepath);
- $uncompressed_size = strlen($data);
+ $uncompressed_size = self::strlen($data);
$crc32 = crc32($data);
- $gzdata = substr(gzcompress($data, $this->compression_level), 2, -4);
- $compressed_size = strlen($gzdata);
+ $gzdata = self::substr(gzcompress($data, $this->compression_level), 2, -4);
+ $compressed_size = self::strlen($gzdata);
$this->zipdata .=
"\x50\x4b\x03\x04\x14\x00\x00\x00\x08\x00"
@@ -267,7 +276,7 @@ class CI_Zip {
.pack('V', $crc32)
.pack('V', $compressed_size)
.pack('V', $uncompressed_size)
- .pack('v', strlen($filepath)) // length of filename
+ .pack('v', self::strlen($filepath)) // length of filename
.pack('v', 0) // extra field length
.$filepath
.$gzdata; // "file data" segment
@@ -279,7 +288,7 @@ class CI_Zip {
.pack('V', $crc32)
.pack('V', $compressed_size)
.pack('V', $uncompressed_size)
- .pack('v', strlen($filepath)) // length of filename
+ .pack('v', self::strlen($filepath)) // length of filename
.pack('v', 0) // extra field length
.pack('v', 0) // file comment length
.pack('v', 0) // disk number start
@@ -288,7 +297,7 @@ class CI_Zip {
.pack('V', $this->offset) // relative offset of local header
.$filepath;
- $this->offset = strlen($this->zipdata);
+ $this->offset = self::strlen($this->zipdata);
$this->entries++;
$this->file_num++;
}
@@ -401,8 +410,8 @@ class CI_Zip {
.$this->directory."\x50\x4b\x05\x06\x00\x00\x00\x00"
.pack('v', $this->entries) // total # of entries "on this disk"
.pack('v', $this->entries) // total # of entries overall
- .pack('V', strlen($this->directory)) // size of central dir
- .pack('V', strlen($this->zipdata)) // offset to start of central dir
+ .pack('V', self::strlen($this->directory)) // size of central dir
+ .pack('V', self::strlen($this->zipdata)) // offset to start of central dir
."\x00\x00"; // .zip file comment length
}
@@ -425,9 +434,9 @@ class CI_Zip {
flock($fp, LOCK_EX);
- for ($result = $written = 0, $data = $this->get_zip(), $length = strlen($data); $written < $length; $written += $result)
+ for ($result = $written = 0, $data = $this->get_zip(), $length = self::strlen($data); $written < $length; $written += $result)
{
- if (($result = fwrite($fp, substr($data, $written))) === FALSE)
+ if (($result = fwrite($fp, self::substr($data, $written))) === FALSE)
{
break;
}
@@ -481,4 +490,43 @@ class CI_Zip {
return $this;
}
+ // --------------------------------------------------------------------
+
+ /**
+ * Byte-safe strlen()
+ *
+ * @param string $str
+ * @return int
+ */
+ protected static function strlen($str)
+ {
+ return (self::$func_override)
+ ? mb_strlen($str, '8bit')
+ : strlen($str);
+ }
+
+ // --------------------------------------------------------------------
+
+ /**
+ * Byte-safe substr()
+ *
+ * @param string $str
+ * @param int $start
+ * @param int $length
+ * @return string
+ */
+ protected static function substr($str, $start, $length = NULL)
+ {
+ if (self::$func_override)
+ {
+ // mb_substr($str, $start, null, '8bit') returns an empty
+ // string on PHP 5.3
+ isset($length) OR $length = ($start >= 0 ? self::strlen($str) - $start : -$start);
+ return mb_substr($str, $start, $length, '8bit');
+ }
+
+ return isset($length)
+ ? substr($str, $start, $length)
+ : substr($str, $start);
+ }
}
diff --git a/tests/codeigniter/core/Security_test.php b/tests/codeigniter/core/Security_test.php
index 8328c37cb..cbf0285ec 100644
--- a/tests/codeigniter/core/Security_test.php
+++ b/tests/codeigniter/core/Security_test.php
@@ -270,6 +270,12 @@ class Security_test extends CI_TestCase {
$this->assertEquals('<div>Hello <b>Booya</b></div>', $decoded);
+ $this->assertEquals('colon:', $this->security->entity_decode('colon&colon;'));
+ $this->assertEquals("NewLine\n", $this->security->entity_decode('NewLine&NewLine;'));
+ $this->assertEquals("Tab\t", $this->security->entity_decode('Tab&Tab;'));
+ $this->assertEquals("lpar(", $this->security->entity_decode('lpar&lpar;'));
+ $this->assertEquals("rpar)", $this->security->entity_decode('rpar&rpar;'));
+
// Issue #3057 (https://github.com/bcit-ci/CodeIgniter/issues/3057)
$this->assertEquals(
'&foo should not include a semicolon',
diff --git a/user_guide_src/source/changelog.rst b/user_guide_src/source/changelog.rst
index 518569097..618dd604a 100644
--- a/user_guide_src/source/changelog.rst
+++ b/user_guide_src/source/changelog.rst
@@ -43,19 +43,43 @@ Release Date: Not Released
- Updated :doc:`HTML Helper <helpers/html_helper>` function :php:func:`meta()` with support for "charset" and "property" properties.
- Changed :doc:`HTML Helper <helpers/html_helper>` function :php:func:`doctype()` default document type to HTML 5.
-Bug fixes for 3.1.0
+Version 3.1.2
+=============
+
+Release Date: Oct 28, 2016
+
+- **Security**
+
+ - Fixed a number of new vulnerabilities in :doc:`Security Library <libraries/security>` method ``xss_clean()``.
+
+- General Changes
+
+ - Allowed PHP 4-style constructors (``Mathching_name::Matching_name()`` methods) to be used as routes, if there's a ``__construct()`` to override them.
+
+Bug fixes for 3.1.2
-------------------
-- Fixed a bug (#4528) - :doc:`Cache Library <libraries/caching>` stored all scalar values as strings with the 'redis' driver.
+- Fixed a regression (#4874) - :doc:`Session Library <libraries/sessions>` didn't take into account ``session.hash_bits_per_character`` when validating session IDs.
+- Fixed a bug (#4871) - :doc:`Query Builder <database/query_builder>` method ``update_batch()`` didn't properly handle identifier escaping.
+- Fixed a bug (#4884) - :doc:`Query Builder <database/query_builder>` didn't properly parse field names ending in 'is' when used inside WHERE and HAVING statements.
+- Fixed a bug where ``CI_Log``, ``CI_Output``, ``CI_Email`` and ``CI_Zip`` didn't handle strings in a byte-safe manner when ``mbstring.func_override`` is enabled.
Version 3.1.1
=============
-Release Date: Not Released
+Release Date: Oct 22, 2016
+
+- **Security**
+
+ - Fixed a flaw in :doc:`Security Library <libraries/security>` method ``entity_decode()`` (used by ``xss_clean()``) that affects HTML 5 entities when using PHP 5.3.
- General Changes
- Added ``E_PARSE`` to the list of error levels detected by the shutdown handler.
+ - Updated :doc:`Inflector Helper <helpers/inflector_helper>` :php:func:`is_countable()` with more words.
+ - Updated :doc:`common function <general/common_functions>` :php:func:`set_status_header()` with new status codes from IETF RFCs
+ `2817 <https://tools.ietf.org/html/rfc2817>`_ (426)
+ and `6585 <https://tools.ietf.org/html/rfc6585>`_ (428, 429, 431, 511).
Bug fixes for 3.1.1
-------------------
@@ -65,7 +89,7 @@ Bug fixes for 3.1.1
- Fixed a bug (#4737) - :doc:`Query Builder <database/query_builder>` didn't add an ``OFFSET`` when ``LIMIT`` is zero or unused.
- Fixed a regression (#4739) - :doc:`Email Library <libraries/email>` doesn't properly separate attachment bodies from headers.
- Fixed a bug (#4754) - :doc:`Unit Testing Library <libraries/unit_testing>` method ``result()`` didn't translate ``res_datatype``.
-- Fixed a bug (#4759) - :doc:`Form Validation <libraries/form_validation>`, :doc:`Trackback <libraries/trackback>` and `XML-RPC <libraries/xmlrpc>` libraries treated URI schemes in a case-sensitive manner.
+- Fixed a bug (#4759) - :doc:`Form Validation <libraries/form_validation>`, :doc:`Trackback <libraries/trackback>` and :doc:`XML-RPC <libraries/xmlrpc>` libraries treated URI schemes in a case-sensitive manner.
- Fixed a bug (#4762) - :doc:`Cache Library <libraries/caching>` 'file' driver method ``get_metadata()`` checked TTL time against ``mtime`` instead of the cache item's creation time.
- Fixed a bug where :doc:`File Uploading Library <libraries/file_uploading>` generated error messages on PHP 7.1.
- Fixed a bug (#4780) - :doc:`compatibility function <general/compatibility_functions>` ``hex2bin()`` didn't reject inputs of type "resource".
@@ -79,6 +103,8 @@ Bug fixes for 3.1.1
- Fixed a bug (#4851) - :doc:`Database Forge <database/forge>` didn't quote schema names passed to its ``create_database()`` method.
- Fixed a bug (#4863) - :doc:`HTML Table Library <libraries/table>` method ``set_caption()`` was missing method chaining support.
- Fixed a bug (#4843) - :doc:`XML-RPC Library <libraries/xmlrpc>` client class didn't set a read/write socket timeout.
+- Fixed a bug (#4865) - uncaught exceptions didn't set the HTTP Response status code to 500 unless ``display_errors`` was turned On.
+- Fixed a bug (#4830) - :doc:`Session Library <libraries/sessions>` didn't take into account the new session INI settings in PHP 7.1.
Version 3.1.0
=============
@@ -88,7 +114,7 @@ Release Date: July 26, 2016
- **Security**
- Fixed an SQL injection in the 'odbc' database driver.
- - Updated :php:func:`set_realpath()` :doc:`Path Helpr <helpers/path_helper>` function to filter-out ``php://`` wrapper inputs.
+ - Updated :php:func:`set_realpath()` :doc:`Path Helper <helpers/path_helper>` function to filter-out ``php://`` wrapper inputs.
- Officially dropped any kind of support for PHP 5.2.x and anything under 5.3.7.
- General Changes
diff --git a/user_guide_src/source/database/transactions.rst b/user_guide_src/source/database/transactions.rst
index 2e6d4b477..e25b8ed14 100644
--- a/user_guide_src/source/database/transactions.rst
+++ b/user_guide_src/source/database/transactions.rst
@@ -75,12 +75,11 @@ debugging is turned off, you can manage your own errors like this::
// generate an error... or use the log_message() function to log your error
}
-Enabling Transactions
-=====================
+Disabling Transactions
+======================
-Transactions are enabled automatically the moment you use
-$this->db->trans_start(). If you would like to disable transactions you
-can do so using $this->db->trans_off()::
+If you would like to disable transactions you can do so using
+``$this->db->trans_off()``::
$this->db->trans_off();
@@ -88,8 +87,9 @@ can do so using $this->db->trans_off()::
$this->db->query('AN SQL QUERY...');
$this->db->trans_complete();
-When transactions are disabled, your queries will be auto-commited, just
-as they are when running queries without transactions.
+When transactions are disabled, your queries will be auto-commited, just as
+they are when running queries without transactions, practically ignoring
+any calls to ``trans_start()``, ``trans_complete()``, etc.
Test Mode
=========
diff --git a/user_guide_src/source/installation/downloads.rst b/user_guide_src/source/installation/downloads.rst
index 1e28a5bf0..ae58e796f 100644
--- a/user_guide_src/source/installation/downloads.rst
+++ b/user_guide_src/source/installation/downloads.rst
@@ -3,7 +3,9 @@ Downloading CodeIgniter
#######################
- `CodeIgniter v3.2.0-dev (Current version) <https://codeload.github.com/bcit-ci/CodeIgniter/zip/develop>`_
-- `CodeIgniter v3.1.1-dev <https://codeload.github.com/bcit-ci/CodeIgniter/zip/3.1-stable>`_
+- `CodeIgniter v3.1.3-dev <https://codeload.github.com/bcit-ci/CodeIgniter/zip/3.1-stable>`_
+- `CodeIgniter v3.1.2 <https://codeload.github.com/bcit-ci/CodeIgniter/zip/3.1.2>`_
+- `CodeIgniter v3.1.1 <https://codeload.github.com/bcit-ci/CodeIgniter/zip/3.1.1>`_
- `CodeIgniter v3.1.0 <https://codeload.github.com/bcit-ci/CodeIgniter/zip/3.1.0>`_
- `CodeIgniter v3.0.6 <https://codeload.github.com/bcit-ci/CodeIgniter/zip/3.0.6>`_
- `CodeIgniter v3.0.5 <https://codeload.github.com/bcit-ci/CodeIgniter/zip/3.0.5>`_
@@ -33,4 +35,4 @@ Please note that while every effort is made to keep this code base
functional, we cannot guarantee the functionality of code taken from
the develop branch.
-Beginning with version 2.0.3, stable versions are also available via `GitHub Releases <https://github.com/bcit-ci/CodeIgniter/releases>`_. \ No newline at end of file
+Beginning with version 2.0.3, stable versions are also available via `GitHub Releases <https://github.com/bcit-ci/CodeIgniter/releases>`_.
diff --git a/user_guide_src/source/installation/upgrade_312.rst b/user_guide_src/source/installation/upgrade_312.rst
new file mode 100644
index 000000000..e0b2191dd
--- /dev/null
+++ b/user_guide_src/source/installation/upgrade_312.rst
@@ -0,0 +1,40 @@
+#############################
+Upgrading from 3.1.1 to 3.1.2
+#############################
+
+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.
+
+Step 2: Update your "ci_sessions" database table
+================================================
+
+If you're using the :doc:`Session Library </libraries/sessions>` with the
+'database' driver, you may have to ``ALTER`` your sessions table for your
+sessions to continue to work.
+
+.. note:: The table in question is not necessarily named "ci_sessions".
+ It is what you've set as your ``$config['sess_save_path']``.
+
+This will only affect you if you've changed your ``session.hash_function``
+*php.ini* setting to something like 'sha512'. Or if you've been running
+an older CodeIgniter version on PHP 7.1+.
+
+It is recommended that you do this anyway, just to avoid potential issues
+in the future if you do change your configuration.
+
+Just execute the one of the following SQL queries, depending on your
+database::
+
+ // MySQL:
+ ALTER TABLE ci_sessions CHANGE id id varchar(128) NOT NULL;
+
+ // PostgreSQL
+ ALTER TABLE ci_sessions ALTER COLUMN id SET DATA TYPE varchar(128);
diff --git a/user_guide_src/source/installation/upgrade_313.rst b/user_guide_src/source/installation/upgrade_313.rst
new file mode 100644
index 000000000..71afc6f6a
--- /dev/null
+++ b/user_guide_src/source/installation/upgrade_313.rst
@@ -0,0 +1,14 @@
+#############################
+Upgrading from 3.1.2 to 3.1.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.
diff --git a/user_guide_src/source/installation/upgrading.rst b/user_guide_src/source/installation/upgrading.rst
index 01812169b..14127d42e 100644
--- a/user_guide_src/source/installation/upgrading.rst
+++ b/user_guide_src/source/installation/upgrading.rst
@@ -8,7 +8,9 @@ upgrading from.
.. toctree::
:titlesonly:
- Upgrading from 3.1.x to 3.2.x <upgrade_320>
+ Upgrading from 3.1.2+ to 3.2.x <upgrade_320>
+ Upgrading from 3.1.2 to 3.1.3 <upgrade_313>
+ Upgrading from 3.1.1 to 3.1.2 <upgrade_312>
Upgrading from 3.1.0 to 3.1.1 <upgrade_311>
Upgrading from 3.0.6 to 3.1.0 <upgrade_310>
Upgrading from 3.0.5 to 3.0.6 <upgrade_306>
diff --git a/user_guide_src/source/libraries/sessions.rst b/user_guide_src/source/libraries/sessions.rst
index 082828c4e..a95cd5a19 100644
--- a/user_guide_src/source/libraries/sessions.rst
+++ b/user_guide_src/source/libraries/sessions.rst
@@ -594,7 +594,7 @@ And then of course, create the database table ...
For MySQL::
CREATE TABLE IF NOT EXISTS `ci_sessions` (
- `id` varchar(40) NOT NULL,
+ `id` varchar(128) NOT NULL,
`ip_address` varchar(45) NOT NULL,
`timestamp` int(10) unsigned DEFAULT 0 NOT NULL,
`data` blob NOT NULL,
@@ -604,7 +604,7 @@ For MySQL::
For PostgreSQL::
CREATE TABLE "ci_sessions" (
- "id" varchar(40) NOT NULL,
+ "id" varchar(128) NOT NULL,
"ip_address" varchar(45) NOT NULL,
"timestamp" bigint DEFAULT 0 NOT NULL,
"data" text DEFAULT '' NOT NULL