Merge branch 'security/entity_decode' into 3.1-stable
diff --git a/system/core/Common.php b/system/core/Common.php
index 2c76519..91c585f 100644
--- a/system/core/Common.php
+++ b/system/core/Common.php
@@ -544,13 +544,18 @@
 				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 @@
 		$_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 a1c6a19..4e10f28 100644
--- a/system/core/Exceptions.php
+++ b/system/core/Exceptions.php
@@ -207,7 +207,6 @@
 		}
 		else
 		{
-			set_status_header(500);
 			$templates_path .= 'html'.DIRECTORY_SEPARATOR;
 		}
 
diff --git a/system/database/DB_driver.php b/system/database/DB_driver.php
index 848516a..7ae52a3 100644
--- a/system/database/DB_driver.php
+++ b/system/database/DB_driver.php
@@ -980,7 +980,7 @@
 	 */
 	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 826aa1e..ed6f4b6 100644
--- a/system/database/DB_forge.php
+++ b/system/database/DB_forge.php
@@ -184,7 +184,7 @@
 		{
 			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 @@
 		{
 			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/drivers/ibase/ibase_forge.php b/system/database/drivers/ibase/ibase_forge.php
index 9c358c3..b35cc37 100644
--- a/system/database/drivers/ibase/ibase_forge.php
+++ b/system/database/drivers/ibase/ibase_forge.php
@@ -111,7 +111,7 @@
 	 * @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_firebird_forge.php b/system/database/drivers/pdo/subdrivers/pdo_firebird_forge.php
index 256fa14..50df769 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 @@
 	 * @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_sqlite_forge.php b/system/database/drivers/pdo/subdrivers/pdo_sqlite_forge.php
index f6f9bb4..b124bca 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 @@
 	 * @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 @@
 	 * @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/sqlite/sqlite_forge.php b/system/database/drivers/sqlite/sqlite_forge.php
index 8a16594..3ad3477 100644
--- a/system/database/drivers/sqlite/sqlite_forge.php
+++ b/system/database/drivers/sqlite/sqlite_forge.php
@@ -75,7 +75,7 @@
 	 * @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 @@
 	 * @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 43cbe33..c45472f 100644
--- a/system/database/drivers/sqlite3/sqlite3_forge.php
+++ b/system/database/drivers/sqlite3/sqlite3_forge.php
@@ -87,7 +87,7 @@
 	 * @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 @@
 	 * @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/helpers/captcha_helper.php b/system/helpers/captcha_helper.php
index 3c1e006..f2ff4dc 100644
--- a/system/helpers/captcha_helper.php
+++ b/system/helpers/captcha_helper.php
@@ -110,7 +110,8 @@
 		$current_dir = @opendir($img_path);
 		while ($filename = @readdir($current_dir))
 		{
-			if (substr($filename, -4) === '.jpg' && (str_replace('.jpg', '', $filename) + $expiration) < $now)
+			if (in_array(substr($filename, -4), array('.jpg', '.png'))
+				&& (str_replace(array('.jpg', '.png'), '', $filename) + $expiration) < $now)
 			{
 				@unlink($img_path.$filename);
 			}
diff --git a/system/helpers/inflector_helper.php b/system/helpers/inflector_helper.php
index c064d8d..6dc3b50 100644
--- a/system/helpers/inflector_helper.php
+++ b/system/helpers/inflector_helper.php
@@ -238,8 +238,37 @@
 		return ! in_array(
 			strtolower($word),
 			array(
-				'equipment', 'information', 'rice', 'money',
-				'species', 'series', 'fish', 'meta'
+				'audio',
+				'bison',
+				'chassis',
+				'compensation',
+				'coreopsis',
+				'data',
+				'deer',
+				'education',
+				'emoji',
+				'equipment',
+				'fish',
+				'furniture',
+				'gold',
+				'information',
+				'knowledge',
+				'love',
+				'rain',
+				'money',
+				'moose',
+				'nutrition',
+				'offspring',
+				'plankton',
+				'pokemon',
+				'police',
+				'rice',
+				'series',
+				'sheep',
+				'species',
+				'swine',
+				'traffic',
+				'wheat',				
 			)
 		);
 	}
diff --git a/system/libraries/Encryption.php b/system/libraries/Encryption.php
index 06284c2..545081b 100644
--- a/system/libraries/Encryption.php
+++ b/system/libraries/Encryption.php
@@ -907,7 +907,7 @@
 	 * Byte-safe strlen()
 	 *
 	 * @param	string	$str
-	 * @return	integer
+	 * @return	int
 	 */
 	protected static function strlen($str)
 	{
diff --git a/system/libraries/Session/Session.php b/system/libraries/Session/Session.php
index 3b391a8..5aac12f 100644
--- a/system/libraries/Session/Session.php
+++ b/system/libraries/Session/Session.php
@@ -57,6 +57,7 @@
 
 	protected $_driver = 'files';
 	protected $_config;
+	protected $_sid_regexp;
 
 	// ------------------------------------------------------------------------
 
@@ -99,6 +100,7 @@
 
 		// Configuration ...
 		$this->_configure($params);
+		$this->_config['_sid_regexp'] = $this->_sid_regexp;
 
 		$class = new $class($this->_config);
 		if ($class instanceof SessionHandlerInterface)
@@ -131,7 +133,7 @@
 		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,36 @@
 		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);
+
+		if (PHP_VERSION_ID < 70100)
+		{
+			if ((int) ini_get('session.hash_function') === 0)
+			{
+				ini_set('session.hash_function', 1);
+				ini_set('session.hash_bits_per_character', $bits_per_character = 4);
+			}
+			else
+			{
+				$bits_per_character = (int) ini_get('session.hash_bits_per_character');
+			}
+		}
+		elseif ((int) ini_get('session.sid_length') < 40 && ($bits_per_character = (int) ini_get('session.sid_bits_per_character')) === 4)
+		{
+			ini_set('session.sid_length', 40);
+		}
+
+		switch ($bits_per_character)
+		{
+			case 4:
+				$this->_sid_regexp = '[0-9a-f]{40,}';
+				break;
+			case 5:
+				$this->_sid_regexp = '[0-9a-v]{40,}';
+				break;
+			case 6:
+				$this->_sid_regexp = '[0-9a-zA-Z,-]{40,}';
+				break;
+		}
 	}
 
 	// ------------------------------------------------------------------------
diff --git a/system/libraries/Session/drivers/Session_files_driver.php b/system/libraries/Session/drivers/Session_files_driver.php
index bf4df8b..37315d3 100644
--- a/system/libraries/Session/drivers/Session_files_driver.php
+++ b/system/libraries/Session/drivers/Session_files_driver.php
@@ -76,6 +76,20 @@
 	 */
 	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 @@
 			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 @@
 		}
 
 		$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 @@
 
 		$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 @@
 		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/Table.php b/system/libraries/Table.php
index 3bce294..f2fa434 100644
--- a/system/libraries/Table.php
+++ b/system/libraries/Table.php
@@ -277,6 +277,7 @@
 	public function set_caption($caption)
 	{
 		$this->caption = $caption;
+		return $this;
 	}
 
 	// --------------------------------------------------------------------
diff --git a/system/libraries/Xmlrpc.php b/system/libraries/Xmlrpc.php
index 181a104..7186646 100644
--- a/system/libraries/Xmlrpc.php
+++ b/system/libraries/Xmlrpc.php
@@ -735,6 +735,8 @@
 			.'Content-Length: '.strlen($msg->payload).$r.$r
 			.$msg->payload;
 
+		stream_set_timeout($fp, $this->timeout); // set timeout for subsequent operations
+
 		for ($written = $timestamp = 0, $length = strlen($op); $written < $length; $written += $result)
 		{
 			if (($result = fwrite($fp, substr($op, $written))) === FALSE)
@@ -753,9 +755,6 @@
 					$result = FALSE;
 					break;
 				}
-
-				usleep(250000);
-				continue;
 			}
 			else
 			{
diff --git a/user_guide_src/source/changelog.rst b/user_guide_src/source/changelog.rst
index a0ed34a..1e59d4c 100644
--- a/user_guide_src/source/changelog.rst
+++ b/user_guide_src/source/changelog.rst
@@ -14,6 +14,10 @@
 -  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
 -------------------
@@ -32,6 +36,13 @@
 -  Fixed a bug (#4808) - :doc:`Database <database/index>` method ``is_write_type()`` only looked at the first line of a queries using ``RETURNING`` with the 'postgre', 'pdo/pgsql', 'odbc' and 'pdo/odbc' drivers.
 -  Fixed a bug where :doc:`Query Builder <database/query_builder>` method ``insert_batch()`` tried to execute an unsupported SQL query with the 'ibase' and 'pdo/firebird' drivers.
 -  Fixed a bug (#4809) - :doc:`Database <database/index>` driver 'pdo/mysql' didn't turn off ``AUTOCOMMIT`` when starting a transaction.
+-  Fixed a bug (#4822) - :doc:`CAPTCHA Helper <helpers/captcha_helper>` didn't clear expired PNG images.
+-  Fixed a bug (#4823) - :doc:`Session Library <libraries/sessions>` 'files' driver could enter an infinite loop if ``mbstring.func_override`` is enabled.
+-  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
 =============
@@ -41,7 +52,7 @@
 -  **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/general/cli.rst b/user_guide_src/source/general/cli.rst
index b45be1a..764a6b8 100644
--- a/user_guide_src/source/general/cli.rst
+++ b/user_guide_src/source/general/cli.rst
@@ -47,11 +47,11 @@
 
 Then save the file to your *application/controllers/* folder.
 
-Now normally you would visit the your site using a URL similar to this::
+Now normally you would visit the site using a URL similar to this::
 
 	example.com/index.php/tools/message/to
 
-Instead, we are going to open Terminal in Mac/Linux or go to Run > "cmd"
+Instead, we are going to open the terminal in Mac/Linux or go to Run > "cmd"
 in Windows and navigate to our CodeIgniter project.
 
 .. code-block:: bash
@@ -75,4 +75,4 @@
 
 That, in a nutshell, is all there is to know about controllers on the
 command line. Remember that this is just a normal controller, so routing
-and ``_remap()`` works fine.
\ No newline at end of file
+and ``_remap()`` works fine.
diff --git a/user_guide_src/source/libraries/form_validation.rst b/user_guide_src/source/libraries/form_validation.rst
index 5b9a742..7792369 100644
--- a/user_guide_src/source/libraries/form_validation.rst
+++ b/user_guide_src/source/libraries/form_validation.rst
@@ -1027,11 +1027,12 @@
 
 .. php:class:: CI_Form_validation
 
-	.. php:method:: set_rules($field[, $label = ''[, $rules = '']])
+	.. php:method:: set_rules($field[, $label = ''[, $rules = ''[, $errors = array()]]])
 
 		:param	string	$field: Field name
 		:param	string	$label: Field label
 		:param	mixed	$rules: Validation rules, as a string list separated by a pipe "|", or as an array or rules
+		:param	array	$errors: A list of custom error messages
 		:returns:	CI_Form_validation instance (method chaining)
 		:rtype:	CI_Form_validation
 
diff --git a/user_guide_src/source/libraries/sessions.rst b/user_guide_src/source/libraries/sessions.rst
index 082828c..a95cd5a 100644
--- a/user_guide_src/source/libraries/sessions.rst
+++ b/user_guide_src/source/libraries/sessions.rst
@@ -594,7 +594,7 @@
 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 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
diff --git a/user_guide_src/source/libraries/xmlrpc.rst b/user_guide_src/source/libraries/xmlrpc.rst
index 4d7ed66..2fe07c4 100644
--- a/user_guide_src/source/libraries/xmlrpc.rst
+++ b/user_guide_src/source/libraries/xmlrpc.rst
@@ -490,6 +490,10 @@
 
 			$this->xmlrpc->timeout(6);
 
+		This timeout period will be used both for an initial connection to 
+                the remote server, as well as for getting a response from it.
+                Make sure you set the timeout before calling ``send_request()``.
+
 	.. php:method:: method($function)
 
 		:param	string	$function: Method name
@@ -575,4 +579,4 @@
 				'struct'
 			);
 
-			return $this->xmlrpc->send_response($response);
\ No newline at end of file
+			return $this->xmlrpc->send_response($response);