Merge pull request #3134 from kdazzle/patch-1

Return 403 instead of 500 if no CSRF token given
diff --git a/application/config/config.php b/application/config/config.php
index d269b6e..b6b3c9f 100644
--- a/application/config/config.php
+++ b/application/config/config.php
@@ -121,7 +121,6 @@
 */
 $config['enable_hooks'] = FALSE;
 
-
 /*
 |--------------------------------------------------------------------------
 | Class Extension Prefix
@@ -136,6 +135,27 @@
 */
 $config['subclass_prefix'] = 'MY_';
 
+/*
+|--------------------------------------------------------------------------
+| Composer auto-loading
+|--------------------------------------------------------------------------
+|
+| Enabling this setting will tell CodeIgniter to look for a Composer
+| package auto-loader script in application/vendor/autoload.php.
+|
+|	$config['composer_autoload'] = TRUE;
+|
+| Or if you have your vendor/ directory located somewhere else, you
+| can opt to set a specific path as well:
+|
+|	$config['composer_autoload'] = '/path/to/vendor/autoload.php';
+|
+| For more information about Composer, please visit http://getcomposer.org/
+|
+| Note: This will NOT disable or override the CodeIgniter-specific
+|	autoloading (application/config/autoload.php)
+*/
+$config['composer_autoload'] = FALSE;
 
 /*
 |--------------------------------------------------------------------------
diff --git a/system/core/CodeIgniter.php b/system/core/CodeIgniter.php
index 1c6e76b..5ff788a 100644
--- a/system/core/CodeIgniter.php
+++ b/system/core/CodeIgniter.php
@@ -249,7 +249,7 @@
 	require_once(BASEPATH.'core/compat/mbstring.php');
 	require_once(BASEPATH.'core/compat/hash.php');
 	require_once(BASEPATH.'core/compat/password.php');
-	require_once(BASEPATH.'core/compat/array.php');
+	require_once(BASEPATH.'core/compat/standard.php');
 
 /*
  * ------------------------------------------------------
@@ -449,6 +449,23 @@
 
 /*
  * ------------------------------------------------------
+ *  Should we use a Composer autoloader?
+ * ------------------------------------------------------
+ */
+	if (($composer_autoload = config_item('composer_autoload')) !== FALSE)
+	{
+		if ($composer_autoload === TRUE && file_exists(APPPATH.'vendor/autoload.php'))
+		{
+			require_once(APPPATH.'vendor/autoload.php');
+		}
+		elseif (file_exists($composer_autoload))
+		{
+			require_once($composer_autoload);
+		}
+	}
+
+/*
+ * ------------------------------------------------------
  *  Is there a "pre_controller" hook?
  * ------------------------------------------------------
  */
diff --git a/system/core/compat/password.php b/system/core/compat/password.php
index d5a017d..a8bc756 100644
--- a/system/core/compat/password.php
+++ b/system/core/compat/password.php
@@ -83,6 +83,9 @@
 	 */
 	function password_hash($password, $algo, array $options = array())
 	{
+		static $func_override;
+		isset($func_override) OR $func_override = (extension_loaded('mbstring') && ini_get('mbstring.func_override'));
+
 		if ($algo !== 1)
 		{
 			trigger_error('password_hash(): Unknown hashing algorithm: '.(int) $algo, E_USER_WARNING);
@@ -95,9 +98,9 @@
 			return NULL;
 		}
 
-		if (isset($options['salt']) && strlen($options['salt']) < 22)
+		if (isset($options['salt']) && ($saltlen = ($func_override ? mb_strlen($options['salt'], '8bit') : strlen($options['salt']))) < 22)
 		{
-			trigger_error('password_hash(): Provided salt is too short: '.strlen($options['salt']).' expecting 22', E_USER_WARNING);
+			trigger_error('password_hash(): Provided salt is too short: '.$saltlen.' expecting 22', E_USER_WARNING);
 			return NULL;
 		}
 		elseif ( ! isset($options['salt']))
@@ -119,7 +122,7 @@
 				}
 
 				$options['salt'] = '';
-				for ($read = 0; $read < 16; $read = strlen($options['salt']))
+				for ($read = 0; $read < 16; $read = ($func_override) ? mb_strlen($options['salt'], '8bit') : strlen($options['salt']))
 				{
 					if (($read = fread($fp, 16 - $read)) === FALSE)
 					{
diff --git a/system/core/compat/array.php b/system/core/compat/standard.php
similarity index 64%
rename from system/core/compat/array.php
rename to system/core/compat/standard.php
index 07dae21..afe9e98 100644
--- a/system/core/compat/array.php
+++ b/system/core/compat/standard.php
@@ -27,14 +27,13 @@
 defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
- * PHP ext/standard/array compatibility package
+ * PHP ext/standard compatibility package
  *
  * @package		CodeIgniter
  * @subpackage	CodeIgniter
  * @category	Compatibility
  * @author		Andrey Andreev
  * @link		http://codeigniter.com/user_guide/
- * @link		http://php.net/book.array
  */
 
 // ------------------------------------------------------------------------
@@ -125,6 +124,54 @@
 
 // ------------------------------------------------------------------------
 
+if (is_php('5.4'))
+{
+	return;
+}
+
+// ------------------------------------------------------------------------
+
+if ( ! function_exists('hex2bin'))
+{
+	/**
+	 * hex2bin()
+	 *
+	 * @link	http://php.net/hex2bin
+	 * @param	string	$data
+	 * @return	string
+	 */
+	function hex2bin($data)
+	{
+		if (in_array($type = gettype($data), array('array', 'double', 'object'), TRUE))
+		{
+			if ($type === 'object' && method_exists($data, '__toString'))
+			{
+				$data = (string) $data;
+			}
+			else
+			{
+				trigger_error('hex2bin() expects parameter 1 to be string, '.$type.' given', E_USER_WARNING);
+				return NULL;
+			}
+		}
+
+		if (strlen($data) % 2 !== 0)
+		{
+			trigger_error('Hexadecimal input string must have an even length', E_USER_WARNING);
+			return FALSE;
+		}
+		elseif ( ! preg_match('/^[0-9a-f]*$/i', $data))
+		{
+			trigger_error('Input string must be hexadecimal string', E_USER_WARNING);
+			return FALSE;
+		}
+
+		return pack('H*', $data);
+	}
+}
+
+// ------------------------------------------------------------------------
+
 if (is_php('5.3'))
 {
 	return;
@@ -242,5 +289,93 @@
 	}
 }
 
-/* End of file array.php */
-/* Location: ./system/core/compat/array.php */
\ No newline at end of file
+// ------------------------------------------------------------------------
+
+if ( ! function_exists('quoted_printable_encode'))
+{
+	/**
+	 * quoted_printable_encode()
+	 *
+	 * @link	http://php.net/quoted_printable_encode
+	 * @param	string	$str
+	 * @return	string
+	 */
+	function quoted_printable_encode($str)
+	{
+		if (strlen($str) === 0)
+		{
+			return '';
+		}
+		elseif (in_array($type = gettype($str), array('array', 'object'), TRUE))
+		{
+			if ($type === 'object' && method_exists($str, '__toString'))
+			{
+				$str = (string) $str;
+			}
+			else
+			{
+				trigger_error('quoted_printable_encode() expects parameter 1 to be string, '.$type.' given', E_USER_WARNING);
+				return NULL;
+			}
+		}
+
+		if (function_exists('imap_8bit'))
+		{
+			return imap_8bit($str);
+		}
+
+		$i = $lp = 0;
+		$output = '';
+		$hex = '0123456789ABCDEF';
+		$length = (extension_loaded('mbstring') && ini_get('mbstring.func_overload'))
+			? mb_strlen($str, '8bit')
+			: strlen($str);
+
+		while ($length--)
+		{
+			if ((($c = $str[$i++]) === "\015") && isset($str[$i]) && ($str[$i] === "\012") && $length > 0)
+			{
+				$output .= "\015".$str[$i++];
+				$length--;
+				$lp = 0;
+				continue;
+			}
+
+			if (
+				ctype_cntrl($c)
+				OR (ord($c) === 0x7f)
+				OR (ord($c) & 0x80)
+				OR ($c === '=')
+				OR ($c === ' ' && isset($str[$i]) && $str[$i] === "\015")
+			)
+			{
+				if (
+					(($lp += 3) > 75 && ord($c) <= 0x7f)
+					OR (ord($c) > 0x7f && ord($c) <= 0xdf && ($lp + 3) > 75)
+					OR (ord($c) > 0xdf && ord($c) <= 0xef && ($lp + 6) > 75)
+					OR (ord($c) > 0xef && ord($c) <= 0xf4 && ($lp + 9) > 75)
+				)
+				{
+					$output .= "=\015\012";
+					$lp = 3;
+				}
+
+				$output .= '='.$hex[ord($c) >> 4].$hex[ord($c) & 0xf];
+				continue;
+			}
+
+			if ((++$lp) > 75)
+			{
+				$output .= "=\015\012";
+				$lp = 1;
+			}
+
+			$output .= $c;
+		}
+
+		return $output;
+	}
+}
+
+/* End of file standard.php */
+/* Location: ./system/core/compat/standard.php */
\ No newline at end of file
diff --git a/system/libraries/Encryption.php b/system/libraries/Encryption.php
index b85d7da..1a61967 100644
--- a/system/libraries/Encryption.php
+++ b/system/libraries/Encryption.php
@@ -123,6 +123,13 @@
 		'sha512' => 64
 	);
 
+	/**
+	 * mbstring.func_override flag
+	 *
+	 * @var	bool
+	 */
+	protected static $func_override;
+
 	// --------------------------------------------------------------------
 
 	/**
@@ -145,8 +152,10 @@
 			return show_error('Encryption: Unable to find an available encryption driver.');
 		}
 
+		isset(self::$func_override) OR self::$func_override = (extension_loaded('mbstring') && ini_get('mbstring.func_override'));
 		$this->initialize($params);
-		if ( ! isset($this->_key) && strlen($key = config_item('encryption_key')) > 0)
+
+		if ( ! isset($this->_key) && self::strlen($key = config_item('encryption_key')) > 0)
 		{
 			$this->_key = $key;
 		}
@@ -337,7 +346,7 @@
 			return FALSE;
 		}
 
-		isset($params['key']) OR $params['key'] = $this->hkdf($this->_key, 'sha512', NULL, strlen($this->_key), 'encryption');
+		isset($params['key']) OR $params['key'] = $this->hkdf($this->_key, 'sha512', NULL, self::strlen($this->_key), 'encryption');
 
 		if (($data = $this->{'_'.$this->_driver.'_encrypt'}($data, $params)) === FALSE)
 		{
@@ -392,7 +401,7 @@
 		if (in_array(strtolower(mcrypt_enc_get_modes_name($params['handle'])), array('cbc', 'ecb'), TRUE))
 		{
 			$block_size = mcrypt_enc_get_block_size($params['handle']);
-			$pad = $block_size - (strlen($data) % $block_size);
+			$pad = $block_size - (self::strlen($data) % $block_size);
 			$data .= str_repeat(chr($pad), $pad);
 		}
 
@@ -480,13 +489,13 @@
 				? $this->_digests[$params['hmac_digest']] * 2
 				: $this->_digests[$params['hmac_digest']];
 
-			if (strlen($data) <= $digest_size)
+			if (self::strlen($data) <= $digest_size)
 			{
 				return FALSE;
 			}
 
-			$hmac_input = substr($data, 0, $digest_size);
-			$data = substr($data, $digest_size);
+			$hmac_input = self::substr($data, 0, $digest_size);
+			$data = self::substr($data, $digest_size);
 
 			isset($params['hmac_key']) OR $params['hmac_key'] = $this->hkdf($this->_key, 'sha512', NULL, NULL, 'authentication');
 			$hmac_check = hash_hmac($params['hmac_digest'], $data, $params['hmac_key'], ! $params['base64']);
@@ -509,7 +518,7 @@
 			$data = base64_decode($data);
 		}
 
-		isset($params['key']) OR $params['key'] = $this->hkdf($this->_key, 'sha512', NULL, strlen($this->_key), 'encryption');
+		isset($params['key']) OR $params['key'] = $this->hkdf($this->_key, 'sha512', NULL, self::strlen($this->_key), 'encryption');
 
 		return $this->{'_'.$this->_driver.'_decrypt'}($data, $params);
 	}
@@ -536,8 +545,8 @@
 		{
 			if (mcrypt_enc_get_modes_name($params['handle']) !== 'ECB')
 			{
-				$iv = substr($data, 0, $iv_size);
-				$data = substr($data, $iv_size);
+				$iv = self::substr($data, 0, $iv_size);
+				$data = self::substr($data, $iv_size);
 			}
 			else
 			{
@@ -564,7 +573,7 @@
 		// Remove PKCS#7 padding, if necessary
 		if (in_array(strtolower(mcrypt_enc_get_modes_name($params['handle'])), array('cbc', 'ecb'), TRUE))
 		{
-			$data = substr($data, 0, -ord($data[strlen($data)-1]));
+			$data = self::substr($data, 0, -ord($data[self::strlen($data)-1]));
 		}
 
 		mcrypt_generic_deinit($params['handle']);
@@ -589,8 +598,8 @@
 	{
 		if ($iv_size = openssl_cipher_iv_length($params['handle']))
 		{
-			$iv = substr($data, 0, $iv_size);
-			$data = substr($data, $iv_size);
+			$iv = self::substr($data, 0, $iv_size);
+			$data = self::substr($data, $iv_size);
 		}
 		else
 		{
@@ -827,17 +836,17 @@
 			return FALSE;
 		}
 
-		strlen($salt) OR $salt = str_repeat("\0", $this->_digests[$digest]);
+		self::strlen($salt) OR $salt = str_repeat("\0", $this->_digests[$digest]);
 
 		$prk = hash_hmac($digest, $key, $salt, TRUE);
 		$key = '';
-		for ($key_block = '', $block_index = 1; strlen($key) < $length; $block_index++)
+		for ($key_block = '', $block_index = 1; self::strlen($key) < $length; $block_index++)
 		{
 			$key_block = hash_hmac($digest, $key_block.$info.chr($block_index), $prk, TRUE);
 			$key .= $key_block;
 		}
 
-		return substr($key, 0, $length);
+		return self::substr($key, 0, $length);
 	}
 
 	// --------------------------------------------------------------------
@@ -863,6 +872,45 @@
 		return NULL;
 	}
 
+	// --------------------------------------------------------------------
+
+	/**
+	 * Byte-safe strlen()
+	 *
+	 * @param	string	$str
+	 * @return	integer
+	 */
+	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);
+	}
 }
 
 /* End of file Encryption.php */
diff --git a/tests/Bootstrap.php b/tests/Bootstrap.php
index cc84abf..713c0fd 100644
--- a/tests/Bootstrap.php
+++ b/tests/Bootstrap.php
@@ -69,7 +69,7 @@
 include_once SYSTEM_PATH.'core/compat/mbstring.php';
 include_once SYSTEM_PATH.'core/compat/hash.php';
 include_once SYSTEM_PATH.'core/compat/password.php';
-include_once SYSTEM_PATH.'core/compat/array.php';
+include_once SYSTEM_PATH.'core/compat/standard.php';
 
 include_once $dir.'/mocks/autoloader.php';
 spl_autoload_register('autoload');
diff --git a/tests/codeigniter/core/compat/array_test.php b/tests/codeigniter/core/compat/array_test.php
deleted file mode 100644
index 9d2deab..0000000
--- a/tests/codeigniter/core/compat/array_test.php
+++ /dev/null
@@ -1,429 +0,0 @@
-<?php
-
-class array_test extends CI_TestCase {
-
-	public function test_bootstrap()
-	{
-		if (is_php('5.5'))
-		{
-			return $this->markTestSkipped('All array functions are already available on PHP 5.5');
-		}
-		elseif ( ! is_php('5.3'))
-		{
-			$this->assertTrue(function_exists('array_replace'));
-			$this->assertTrue(function_exists('array_replace_recursive'));
-		}
-
-		$this->assertTrue(function_exists('array_column'));
-	}
-
-	// ------------------------------------------------------------------------
-
-	/**
-	 * array_column() test
-	 *
-	 * Borrowed from PHP's own tests
-	 *
-	 * @depends	test_bootstrap
-	 */
-	public function test_array_column()
-	{
-		// Basic tests
-
-		$input = array(
-			array(
-				'id' => 1,
-				'first_name' => 'John',
-				'last_name' => 'Doe'
-			),
-			array(
-				'id' => 2,
-				'first_name' => 'Sally',
-				'last_name' => 'Smith'
-			),
-			array(
-				'id' => 3,
-				'first_name' => 'Jane',
-				'last_name' => 'Jones'
-			)
-		);
-
-		// Ensure internal array position doesn't break it
-		next($input);
-
-		$this->assertEquals(
-			array('John', 'Sally', 'Jane'),
-			array_column($input, 'first_name')
-		);
-
-		$this->assertEquals(
-			array(1, 2, 3),
-			array_column($input, 'id')
-		);
-
-		$this->assertEquals(
-			array(
-				1 => 'Doe',
-				2 => 'Smith',
-				3 => 'Jones'
-			),
-			array_column($input, 'last_name', 'id')
-		);
-
-		$this->assertEquals(
-			array(
-				'John' => 'Doe',
-				'Sally' => 'Smith',
-				'Jane' => 'Jones'
-			),
-			array_column($input, 'last_name', 'first_name')
-		);
-
-		// Object key search
-
-		$f = new Foo();
-		$b = new Bar();
-
-		$this->assertEquals(
-			array('Doe', 'Smith', 'Jones'),
-			array_column($input, $f)
-		);
-
-		$this->assertEquals(
-			array(
-				'John' => 'Doe',
-				'Sally' => 'Smith',
-				'Jane' => 'Jones'
-			),
-			array_column($input, $f, $b)
-		);
-
-		// NULL parameters
-
-		$input = array(
-			456 => array(
-				'id' => '3',
-				'title' => 'Foo',
-				'date' => '2013-03-25'
-			),
-			457 => array(
-				'id' => '5',
-				'title' => 'Bar',
-				'date' => '2012-05-20'
-			)
-		);
-
-		$this->assertEquals(
-			array(
-				3 => array(
-					'id' => '3',
-					'title' => 'Foo',
-					'date' => '2013-03-25'
-				),
-				5 => array(
-					'id' => '5',
-					'title' => 'Bar',
-					'date' => '2012-05-20'
-				)
-			),
-			array_column($input, NULL, 'id')
-		);
-
-		$this->assertEquals(
-			array(
-				array(
-					'id' => '3',
-					'title' => 'Foo',
-					'date' => '2013-03-25'
-				),
-				array(
-					'id' => '5',
-					'title' => 'Bar',
-					'date' => '2012-05-20'
-				)
-			),
-			array_column($input, NULL, 'foo')
-		);
-
-		$this->assertEquals(
-			array(
-				array(
-					'id' => '3',
-					'title' => 'Foo',
-					'date' => '2013-03-25'
-				),
-				array(
-					'id' => '5',
-					'title' => 'Bar',
-					'date' => '2012-05-20'
-				)
-			),
-			array_column($input, NULL)
-		);
-
-		// Data types
-
-		$fh = fopen(__FILE__, 'r', TRUE);
-		$stdClass = new stdClass();
-		$input = array(
-			array(
-				'id' => 1,
-				'value' => $stdClass
-			),
-			array(
-				'id' => 2,
-				'value' => 34.2345
-			),
-			array(
-				'id' => 3,
-				'value' => TRUE
-			),
-			array(
-				'id' => 4,
-				'value' => FALSE
-			),
-			array(
-				'id' => 5,
-				'value' => NULL
-			),
-			array(
-				'id' => 6,
-				'value' => 1234
-			),
-			array(
-				'id' => 7,
-				'value' => 'Foo'
-			),
-			array(
-				'id' => 8,
-				'value' => $fh
-			)
-		);
-
-		$this->assertEquals(
-			array(
-				$stdClass,
-				34.2345,
-				TRUE,
-				FALSE,
-				NULL,
-				1234,
-				'Foo',
-				$fh
-			),
-			array_column($input, 'value')
-		);
-
-		$this->assertEquals(
-			array(
-				1 => $stdClass,
-				2 => 34.2345,
-				3 => TRUE,
-				4 => FALSE,
-				5 => NULL,
-				6 => 1234,
-				7 => 'Foo',
-				8 => $fh
-			),
-			array_column($input, 'value', 'id')
-		);
-
-		// Numeric column keys
-
-		$input = array(
-			array('aaa', '111'),
-			array('bbb', '222'),
-			array('ccc', '333', -1 => 'ddd')
-		);
-
-		$this->assertEquals(
-			array('111', '222', '333'),
-			array_column($input, 1)
-		);
-
-		$this->assertEquals(
-			array(
-				'aaa' => '111',
-				'bbb' => '222',
-				'ccc' => '333'
-			),
-			array_column($input, 1, 0)
-		);
-
-		$this->assertEquals(
-			array(
-				'aaa' => '111',
-				'bbb' => '222',
-				'ccc' => '333'
-			),
-			array_column($input, 1, 0.123)
-		);
-
-		$this->assertEquals(
-			array(
-				0 => '111',
-				1 => '222',
-				'ddd' => '333'
-			),
-			array_column($input, 1, -1)
-		);
-
-		// Non-existing columns
-
-		$this->assertEquals(array(), array_column($input, 2));
-		$this->assertEquals(array(), array_column($input, 'foo'));
-		$this->assertEquals(
-			array('aaa', 'bbb', 'ccc'),
-			array_column($input, 0, 'foo')
-		);
-		$this->assertEquals(array(), array_column($input, 3.14));
-
-		// One-dimensional array
-		$this->assertEquals(array(), array_column(array('foo', 'bar', 'baz'), 1));
-
-		// Columns not present in all rows
-
-		$input = array(
-			array('a' => 'foo', 'b' => 'bar', 'e' => 'bbb'),
-			array('a' => 'baz', 'c' => 'qux', 'd' => 'aaa'),
-			array('a' => 'eee', 'b' => 'fff', 'e' => 'ggg')
-		);
-
-		$this->assertEquals(
-			array('qux'),
-			array_column($input, 'c')
-		);
-
-		$this->assertEquals(
-			array('baz' => 'qux'),
-			array_column($input, 'c', 'a')
-		);
-
-		$this->assertEquals(
-			array(
-				0 => 'foo',
-				'aaa' => 'baz',
-				1 => 'eee'
-			),
-			array_column($input, 'a', 'd')
-		);
-
-		$this->assertEquals(
-			array(
-				'bbb' => 'foo',
-				0 => 'baz',
-				'ggg' => 'eee'
-			),
-			array_column($input, 'a', 'e')
-		);
-
-		$this->assertEquals(
-			array('bar', 'fff'),
-			array_column($input, 'b')
-		);
-
-		$this->assertEquals(
-			array(
-				'foo' => 'bar',
-				'eee' => 'fff'
-			),
-			array_column($input, 'b', 'a')
-		);
-	}
-
-	// ------------------------------------------------------------------------
-
-	/**
-	 * array_replace(), array_replace_recursive() tests
-	 *
-	 * Borrowed from PHP's own tests
-	 *
-	 * @depends	test_bootstrap
-	 */
-	public function test_array_replace_recursive()
-	{
-		if (is_php('5.3'))
-		{
-			return $this->markTestSkipped('array_replace() and array_replace_recursive() are already available on PHP 5.3');
-		}
-
-		$array1 = array(
-			0 => 'dontclobber',
-			'1' => 'unclobbered',
-			'test2' => 0.0,
-			'test3' => array(
-				'testarray2' => TRUE,
-				1 => array(
-					'testsubarray1' => 'dontclobber2',
-					'testsubarray2' => 'dontclobber3'
-				)
-			)
-		);
-
-		$array2 = array(
-			1 => 'clobbered',
-			'test3' => array(
-				'testarray2' => FALSE
-			),
-			'test4' => array(
-				'clobbered3' => array(0, 1, 2)
-			)
-		);
-
-		// array_replace()
-		$this->assertEquals(
-			array(
-				0 => 'dontclobber',
-				1 => 'clobbered',
-				'test2' => 0.0,
-				'test3' => array(
-					'testarray2' => FALSE
-				),
-				'test4' => array(
-					'clobbered3' => array(0, 1, 2)
-				)
-			),
-			array_replace($array1, $array2)
-		);
-
-		// array_replace_recursive()
-		$this->assertEquals(
-			array(
-				0 => 'dontclobber',
-				1 => 'clobbered',
-				'test2' => 0.0,
-				'test3' => array(
-					'testarray2' => FALSE,
-					1 => array(
-						'testsubarray1' => 'dontclobber2',
-						'testsubarray2' => 'dontclobber3'
-					)
-				),
-				'test4' => array(
-					'clobbered3' => array(0, 1, 2)
-				)
-			),
-			array_replace_recursive($array1, $array2)
-		);
-	}
-}
-
-// ------------------------------------------------------------------------
-
-// These are necessary for the array_column() tests
-
-class Foo {
-
-	public function __toString()
-	{
-		return 'last_name';
-	}
-}
-
-class Bar {
-
-	public function __toString()
-	{
-		return 'first_name';
-	}
-}
\ No newline at end of file
diff --git a/tests/codeigniter/core/compat/standard_test.php b/tests/codeigniter/core/compat/standard_test.php
new file mode 100644
index 0000000..a3a6d95
--- /dev/null
+++ b/tests/codeigniter/core/compat/standard_test.php
@@ -0,0 +1,576 @@
+<?php
+
+class standard_test extends CI_TestCase {
+
+	public function test_bootstrap()
+	{
+		if (is_php('5.5'))
+		{
+			return $this->markTestSkipped('All array functions are already available on PHP 5.5');
+		}
+
+		$this->assertTrue(function_exists('array_column'));
+
+		if ( ! is_php('5.4'))
+		{
+			$this->assertTrue(function_exists('hex2bin'));
+		}
+
+		if ( ! is_php('5.3'))
+		{
+			$this->assertTrue(function_exists('array_replace'));
+			$this->assertTrue(function_exists('array_replace_recursive'));
+			$this->assertTrue(function_exists('quoted_printable_encode'));
+		}
+	}
+
+	// ------------------------------------------------------------------------
+
+	/**
+	 * array_column() test
+	 *
+	 * Borrowed from PHP's own tests
+	 *
+	 * @depends	test_bootstrap
+	 */
+	public function test_array_column()
+	{
+		// Basic tests
+
+		$input = array(
+			array(
+				'id' => 1,
+				'first_name' => 'John',
+				'last_name' => 'Doe'
+			),
+			array(
+				'id' => 2,
+				'first_name' => 'Sally',
+				'last_name' => 'Smith'
+			),
+			array(
+				'id' => 3,
+				'first_name' => 'Jane',
+				'last_name' => 'Jones'
+			)
+		);
+
+		// Ensure internal array position doesn't break it
+		next($input);
+
+		$this->assertEquals(
+			array('John', 'Sally', 'Jane'),
+			array_column($input, 'first_name')
+		);
+
+		$this->assertEquals(
+			array(1, 2, 3),
+			array_column($input, 'id')
+		);
+
+		$this->assertEquals(
+			array(
+				1 => 'Doe',
+				2 => 'Smith',
+				3 => 'Jones'
+			),
+			array_column($input, 'last_name', 'id')
+		);
+
+		$this->assertEquals(
+			array(
+				'John' => 'Doe',
+				'Sally' => 'Smith',
+				'Jane' => 'Jones'
+			),
+			array_column($input, 'last_name', 'first_name')
+		);
+
+		// Object key search
+
+		$f = new Foo();
+		$b = new Bar();
+
+		$this->assertEquals(
+			array('Doe', 'Smith', 'Jones'),
+			array_column($input, $f)
+		);
+
+		$this->assertEquals(
+			array(
+				'John' => 'Doe',
+				'Sally' => 'Smith',
+				'Jane' => 'Jones'
+			),
+			array_column($input, $f, $b)
+		);
+
+		// NULL parameters
+
+		$input = array(
+			456 => array(
+				'id' => '3',
+				'title' => 'Foo',
+				'date' => '2013-03-25'
+			),
+			457 => array(
+				'id' => '5',
+				'title' => 'Bar',
+				'date' => '2012-05-20'
+			)
+		);
+
+		$this->assertEquals(
+			array(
+				3 => array(
+					'id' => '3',
+					'title' => 'Foo',
+					'date' => '2013-03-25'
+				),
+				5 => array(
+					'id' => '5',
+					'title' => 'Bar',
+					'date' => '2012-05-20'
+				)
+			),
+			array_column($input, NULL, 'id')
+		);
+
+		$this->assertEquals(
+			array(
+				array(
+					'id' => '3',
+					'title' => 'Foo',
+					'date' => '2013-03-25'
+				),
+				array(
+					'id' => '5',
+					'title' => 'Bar',
+					'date' => '2012-05-20'
+				)
+			),
+			array_column($input, NULL, 'foo')
+		);
+
+		$this->assertEquals(
+			array(
+				array(
+					'id' => '3',
+					'title' => 'Foo',
+					'date' => '2013-03-25'
+				),
+				array(
+					'id' => '5',
+					'title' => 'Bar',
+					'date' => '2012-05-20'
+				)
+			),
+			array_column($input, NULL)
+		);
+
+		// Data types
+
+		$fh = fopen(__FILE__, 'r', TRUE);
+		$stdClass = new stdClass();
+		$input = array(
+			array(
+				'id' => 1,
+				'value' => $stdClass
+			),
+			array(
+				'id' => 2,
+				'value' => 34.2345
+			),
+			array(
+				'id' => 3,
+				'value' => TRUE
+			),
+			array(
+				'id' => 4,
+				'value' => FALSE
+			),
+			array(
+				'id' => 5,
+				'value' => NULL
+			),
+			array(
+				'id' => 6,
+				'value' => 1234
+			),
+			array(
+				'id' => 7,
+				'value' => 'Foo'
+			),
+			array(
+				'id' => 8,
+				'value' => $fh
+			)
+		);
+
+		$this->assertEquals(
+			array(
+				$stdClass,
+				34.2345,
+				TRUE,
+				FALSE,
+				NULL,
+				1234,
+				'Foo',
+				$fh
+			),
+			array_column($input, 'value')
+		);
+
+		$this->assertEquals(
+			array(
+				1 => $stdClass,
+				2 => 34.2345,
+				3 => TRUE,
+				4 => FALSE,
+				5 => NULL,
+				6 => 1234,
+				7 => 'Foo',
+				8 => $fh
+			),
+			array_column($input, 'value', 'id')
+		);
+
+		// Numeric column keys
+
+		$input = array(
+			array('aaa', '111'),
+			array('bbb', '222'),
+			array('ccc', '333', -1 => 'ddd')
+		);
+
+		$this->assertEquals(
+			array('111', '222', '333'),
+			array_column($input, 1)
+		);
+
+		$this->assertEquals(
+			array(
+				'aaa' => '111',
+				'bbb' => '222',
+				'ccc' => '333'
+			),
+			array_column($input, 1, 0)
+		);
+
+		$this->assertEquals(
+			array(
+				'aaa' => '111',
+				'bbb' => '222',
+				'ccc' => '333'
+			),
+			array_column($input, 1, 0.123)
+		);
+
+		$this->assertEquals(
+			array(
+				0 => '111',
+				1 => '222',
+				'ddd' => '333'
+			),
+			array_column($input, 1, -1)
+		);
+
+		// Non-existing columns
+
+		$this->assertEquals(array(), array_column($input, 2));
+		$this->assertEquals(array(), array_column($input, 'foo'));
+		$this->assertEquals(
+			array('aaa', 'bbb', 'ccc'),
+			array_column($input, 0, 'foo')
+		);
+		$this->assertEquals(array(), array_column($input, 3.14));
+
+		// One-dimensional array
+		$this->assertEquals(array(), array_column(array('foo', 'bar', 'baz'), 1));
+
+		// Columns not present in all rows
+
+		$input = array(
+			array('a' => 'foo', 'b' => 'bar', 'e' => 'bbb'),
+			array('a' => 'baz', 'c' => 'qux', 'd' => 'aaa'),
+			array('a' => 'eee', 'b' => 'fff', 'e' => 'ggg')
+		);
+
+		$this->assertEquals(
+			array('qux'),
+			array_column($input, 'c')
+		);
+
+		$this->assertEquals(
+			array('baz' => 'qux'),
+			array_column($input, 'c', 'a')
+		);
+
+		$this->assertEquals(
+			array(
+				0 => 'foo',
+				'aaa' => 'baz',
+				1 => 'eee'
+			),
+			array_column($input, 'a', 'd')
+		);
+
+		$this->assertEquals(
+			array(
+				'bbb' => 'foo',
+				0 => 'baz',
+				'ggg' => 'eee'
+			),
+			array_column($input, 'a', 'e')
+		);
+
+		$this->assertEquals(
+			array('bar', 'fff'),
+			array_column($input, 'b')
+		);
+
+		$this->assertEquals(
+			array(
+				'foo' => 'bar',
+				'eee' => 'fff'
+			),
+			array_column($input, 'b', 'a')
+		);
+	}
+
+	// ------------------------------------------------------------------------
+
+	/**
+	 * hex2bin() tests
+	 *
+	 * @depends	test_bootstrap
+	 */
+	public function test_hex2bin()
+	{
+		if (is_php('5.4'))
+		{
+			return $this->markTestSkipped('hex2bin() is already available on PHP 5.4');
+		}
+
+		$this->assertEquals("\x03\x04", hex2bin("0304"));
+		$this->assertEquals('', hex2bin(''));
+		$this->assertEquals("\x01\x02\x03", hex2bin(new FooHex()));
+	}
+
+	// ------------------------------------------------------------------------
+
+	/**
+	 * array_replace(), array_replace_recursive() tests
+	 *
+	 * Borrowed from PHP's own tests
+	 *
+	 * @depends	test_bootstrap
+	 */
+	public function test_array_replace_recursive()
+	{
+		if (is_php('5.3'))
+		{
+			return $this->markTestSkipped('array_replace() and array_replace_recursive() are already available on PHP 5.3');
+		}
+
+		$array1 = array(
+			0 => 'dontclobber',
+			'1' => 'unclobbered',
+			'test2' => 0.0,
+			'test3' => array(
+				'testarray2' => TRUE,
+				1 => array(
+					'testsubarray1' => 'dontclobber2',
+					'testsubarray2' => 'dontclobber3'
+				)
+			)
+		);
+
+		$array2 = array(
+			1 => 'clobbered',
+			'test3' => array(
+				'testarray2' => FALSE
+			),
+			'test4' => array(
+				'clobbered3' => array(0, 1, 2)
+			)
+		);
+
+		// array_replace()
+		$this->assertEquals(
+			array(
+				0 => 'dontclobber',
+				1 => 'clobbered',
+				'test2' => 0.0,
+				'test3' => array(
+					'testarray2' => FALSE
+				),
+				'test4' => array(
+					'clobbered3' => array(0, 1, 2)
+				)
+			),
+			array_replace($array1, $array2)
+		);
+
+		// array_replace_recursive()
+		$this->assertEquals(
+			array(
+				0 => 'dontclobber',
+				1 => 'clobbered',
+				'test2' => 0.0,
+				'test3' => array(
+					'testarray2' => FALSE,
+					1 => array(
+						'testsubarray1' => 'dontclobber2',
+						'testsubarray2' => 'dontclobber3'
+					)
+				),
+				'test4' => array(
+					'clobbered3' => array(0, 1, 2)
+				)
+			),
+			array_replace_recursive($array1, $array2)
+		);
+	}
+
+	// ------------------------------------------------------------------------
+
+	/**
+	 * quoted_printable_encode() tests
+	 *
+	 * Borrowed from PHP's own tests
+	 *
+	 * @depends	test_bootstrap
+	 */
+	public function test_quoted_printable_encode()
+	{
+		if (is_php('5.3'))
+		{
+			return $this->markTestSkipped('quoted_printable_encode() is already available on PHP 5.3');
+		}
+
+
+		// These are actually imap_8bit() tests:
+		$this->assertEquals("String with CRLF at end=20\r\n", quoted_printable_encode("String with CRLF at end \r\n"));
+		// ext/imap/tests/imap_8bit_basic.phpt says for this line:
+		// NB this appears to be a bug in cclient; a space at end of string should be encoded as =20
+		$this->assertEquals("String with space at end ", quoted_printable_encode("String with space at end "));
+		$this->assertEquals("String with tabs =09=09 in middle", quoted_printable_encode("String with tabs \t\t in middle"));
+		$this->assertEquals("String with tab at end =09", quoted_printable_encode("String with tab at end \t"));
+		$this->assertEquals("=00=01=02=03=04=FE=FF=0A=0D", quoted_printable_encode("\x00\x01\x02\x03\x04\xfe\xff\x0a\x0d"));
+
+		// And these are from ext/standard/tests/strings/quoted_printable_encode_002.phpt:
+		$this->assertEquals(
+			"=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=\r\n"
+			."=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=\r\n"
+			."=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=\r\n"
+			."=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=\r\n"
+			."=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=\r\n"
+			."=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=\r\n"
+			."=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=\r\n"
+			."=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00=00",
+			$d = quoted_printable_encode(str_repeat("\0", 200))
+		);
+		$this->assertEquals(str_repeat("\x0", 200), quoted_printable_decode($d));
+		$this->assertEquals(
+			"=D1=81=D1=82=D1=80=D0=BE=D0=BA=D0=B0 =D0=B2 =D1=8E=D0=BD=D0=B8=D0=BA=D0=\r\n"
+			."=BE=D0=B4=D0=B5=D1=81=D1=82=D1=80=D0=BE=D0=BA=D0=B0 =D0=B2 =D1=8E=D0=BD=\r\n"
+			."=D0=B8=D0=BA=D0=BE=D0=B4=D0=B5=D1=81=D1=82=D1=80=D0=BE=D0=BA=D0=B0 =D0=\r\n"
+			."=B2 =D1=8E=D0=BD=D0=B8=D0=BA=D0=BE=D0=B4=D0=B5=D1=81=D1=82=D1=80=D0=BE=\r\n"
+			."=D0=BA=D0=B0 =D0=B2 =D1=8E=D0=BD=D0=B8=D0=BA=D0=BE=D0=B4=D0=B5=D1=81=D1=\r\n"
+			."=82=D1=80=D0=BE=D0=BA=D0=B0 =D0=B2 =D1=8E=D0=BD=D0=B8=D0=BA=D0=BE=D0=B4=\r\n"
+			."=D0=B5=D1=81=D1=82=D1=80=D0=BE=D0=BA=D0=B0 =D0=B2 =D1=8E=D0=BD=D0=B8=D0=\r\n"
+			."=BA=D0=BE=D0=B4=D0=B5=D1=81=D1=82=D1=80=D0=BE=D0=BA=D0=B0 =D0=B2 =D1=8E=\r\n"
+			."=D0=BD=D0=B8=D0=BA=D0=BE=D0=B4=D0=B5=D1=81=D1=82=D1=80=D0=BE=D0=BA=D0=B0 =\r\n"
+			."=D0=B2 =D1=8E=D0=BD=D0=B8=D0=BA=D0=BE=D0=B4=D0=B5=D1=81=D1=82=D1=80=D0=\r\n"
+			."=BE=D0=BA=D0=B0 =D0=B2 =D1=8E=D0=BD=D0=B8=D0=BA=D0=BE=D0=B4=D0=B5=D1=81=\r\n"
+			."=D1=82=D1=80=D0=BE=D0=BA=D0=B0 =D0=B2 =D1=8E=D0=BD=D0=B8=D0=BA=D0=BE=D0=\r\n"
+			."=B4=D0=B5=D1=81=D1=82=D1=80=D0=BE=D0=BA=D0=B0 =D0=B2 =D1=8E=D0=BD=D0=B8=\r\n"
+			."=D0=BA=D0=BE=D0=B4=D0=B5=D1=81=D1=82=D1=80=D0=BE=D0=BA=D0=B0 =D0=B2 =D1=\r\n"
+			."=8E=D0=BD=D0=B8=D0=BA=D0=BE=D0=B4=D0=B5=D1=81=D1=82=D1=80=D0=BE=D0=BA=D0=\r\n"
+			."=B0 =D0=B2 =D1=8E=D0=BD=D0=B8=D0=BA=D0=BE=D0=B4=D0=B5=D1=81=D1=82=D1=80=\r\n"
+			."=D0=BE=D0=BA=D0=B0 =D0=B2 =D1=8E=D0=BD=D0=B8=D0=BA=D0=BE=D0=B4=D0=B5=D1=\r\n"
+			."=81=D1=82=D1=80=D0=BE=D0=BA=D0=B0 =D0=B2 =D1=8E=D0=BD=D0=B8=D0=BA=D0=BE=\r\n"
+			."=D0=B4=D0=B5=D1=81=D1=82=D1=80=D0=BE=D0=BA=D0=B0 =D0=B2 =D1=8E=D0=BD=D0=\r\n"
+			."=B8=D0=BA=D0=BE=D0=B4=D0=B5=D1=81=D1=82=D1=80=D0=BE=D0=BA=D0=B0 =D0=B2 =\r\n"
+			."=D1=8E=D0=BD=D0=B8=D0=BA=D0=BE=D0=B4=D0=B5=D1=81=D1=82=D1=80=D0=BE=D0=BA=\r\n"
+			."=D0=B0 =D0=B2 =D1=8E=D0=BD=D0=B8=D0=BA=D0=BE=D0=B4=D0=B5=D1=81=D1=82=D1=\r\n"
+			."=80=D0=BE=D0=BA=D0=B0 =D0=B2 =D1=8E=D0=BD=D0=B8=D0=BA=D0=BE=D0=B4=D0=B5=\r\n"
+			."=D1=81=D1=82=D1=80=D0=BE=D0=BA=D0=B0 =D0=B2 =D1=8E=D0=BD=D0=B8=D0=BA=D0=\r\n"
+			."=BE=D0=B4=D0=B5=D1=81=D1=82=D1=80=D0=BE=D0=BA=D0=B0 =D0=B2 =D1=8E=D0=BD=\r\n"
+			."=D0=B8=D0=BA=D0=BE=D0=B4=D0=B5=D1=81=D1=82=D1=80=D0=BE=D0=BA=D0=B0 =D0=\r\n"
+			."=B2 =D1=8E=D0=BD=D0=B8=D0=BA=D0=BE=D0=B4=D0=B5=D1=81=D1=82=D1=80=D0=BE=\r\n"
+			."=D0=BA=D0=B0 =D0=B2 =D1=8E=D0=BD=D0=B8=D0=BA=D0=BE=D0=B4=D0=B5=D1=81=D1=\r\n"
+			."=82=D1=80=D0=BE=D0=BA=D0=B0 =D0=B2 =D1=8E=D0=BD=D0=B8=D0=BA=D0=BE=D0=B4=\r\n"
+			."=D0=B5=D1=81=D1=82=D1=80=D0=BE=D0=BA=D0=B0 =D0=B2 =D1=8E=D0=BD=D0=B8=D0=\r\n"
+			."=BA=D0=BE=D0=B4=D0=B5=D1=81=D1=82=D1=80=D0=BE=D0=BA=D0=B0 =D0=B2 =D1=8E=\r\n"
+			."=D0=BD=D0=B8=D0=BA=D0=BE=D0=B4=D0=B5=D1=81=D1=82=D1=80=D0=BE=D0=BA=D0=B0 =\r\n"
+			."=D0=B2 =D1=8E=D0=BD=D0=B8=D0=BA=D0=BE=D0=B4=D0=B5=D1=81=D1=82=D1=80=D0=\r\n"
+			."=BE=D0=BA=D0=B0 =D0=B2 =D1=8E=D0=BD=D0=B8=D0=BA=D0=BE=D0=B4=D0=B5=D1=81=\r\n"
+			."=D1=82=D1=80=D0=BE=D0=BA=D0=B0 =D0=B2 =D1=8E=D0=BD=D0=B8=D0=BA=D0=BE=D0=\r\n"
+			."=B4=D0=B5=D1=81=D1=82=D1=80=D0=BE=D0=BA=D0=B0 =D0=B2 =D1=8E=D0=BD=D0=B8=\r\n"
+			."=D0=BA=D0=BE=D0=B4=D0=B5=D1=81=D1=82=D1=80=D0=BE=D0=BA=D0=B0 =D0=B2 =D1=\r\n"
+			."=8E=D0=BD=D0=B8=D0=BA=D0=BE=D0=B4=D0=B5=D1=81=D1=82=D1=80=D0=BE=D0=BA=D0=\r\n"
+			."=B0 =D0=B2 =D1=8E=D0=BD=D0=B8=D0=BA=D0=BE=D0=B4=D0=B5=D1=81=D1=82=D1=80=\r\n"
+			."=D0=BE=D0=BA=D0=B0 =D0=B2 =D1=8E=D0=BD=D0=B8=D0=BA=D0=BE=D0=B4=D0=B5=D1=\r\n"
+			."=81=D1=82=D1=80=D0=BE=D0=BA=D0=B0 =D0=B2 =D1=8E=D0=BD=D0=B8=D0=BA=D0=BE=\r\n"
+			."=D0=B4=D0=B5=D1=81=D1=82=D1=80=D0=BE=D0=BA=D0=B0 =D0=B2 =D1=8E=D0=BD=D0=\r\n"
+			."=B8=D0=BA=D0=BE=D0=B4=D0=B5=D1=81=D1=82=D1=80=D0=BE=D0=BA=D0=B0 =D0=B2 =\r\n"
+			."=D1=8E=D0=BD=D0=B8=D0=BA=D0=BE=D0=B4=D0=B5=D1=81=D1=82=D1=80=D0=BE=D0=BA=\r\n"
+			."=D0=B0 =D0=B2 =D1=8E=D0=BD=D0=B8=D0=BA=D0=BE=D0=B4=D0=B5=D1=81=D1=82=D1=\r\n"
+			."=80=D0=BE=D0=BA=D0=B0 =D0=B2 =D1=8E=D0=BD=D0=B8=D0=BA=D0=BE=D0=B4=D0=B5=\r\n"
+			."=D1=81=D1=82=D1=80=D0=BE=D0=BA=D0=B0 =D0=B2 =D1=8E=D0=BD=D0=B8=D0=BA=D0=\r\n"
+			."=BE=D0=B4=D0=B5=D1=81=D1=82=D1=80=D0=BE=D0=BA=D0=B0 =D0=B2 =D1=8E=D0=BD=\r\n"
+			."=D0=B8=D0=BA=D0=BE=D0=B4=D0=B5=D1=81=D1=82=D1=80=D0=BE=D0=BA=D0=B0 =D0=\r\n"
+			."=B2 =D1=8E=D0=BD=D0=B8=D0=BA=D0=BE=D0=B4=D0=B5=D1=81=D1=82=D1=80=D0=BE=\r\n"
+			."=D0=BA=D0=B0 =D0=B2 =D1=8E=D0=BD=D0=B8=D0=BA=D0=BE=D0=B4=D0=B5=D1=81=D1=\r\n"
+			."=82=D1=80=D0=BE=D0=BA=D0=B0 =D0=B2 =D1=8E=D0=BD=D0=B8=D0=BA=D0=BE=D0=B4=\r\n"
+			."=D0=B5=D1=81=D1=82=D1=80=D0=BE=D0=BA=D0=B0 =D0=B2 =D1=8E=D0=BD=D0=B8=D0=\r\n"
+			."=BA=D0=BE=D0=B4=D0=B5=D1=81=D1=82=D1=80=D0=BE=D0=BA=D0=B0 =D0=B2 =D1=8E=\r\n"
+			."=D0=BD=D0=B8=D0=BA=D0=BE=D0=B4=D0=B5=D1=81=D1=82=D1=80=D0=BE=D0=BA=D0=B0 =\r\n"
+			."=D0=B2 =D1=8E=D0=BD=D0=B8=D0=BA=D0=BE=D0=B4=D0=B5=D1=81=D1=82=D1=80=D0=\r\n"
+			."=BE=D0=BA=D0=B0 =D0=B2 =D1=8E=D0=BD=D0=B8=D0=BA=D0=BE=D0=B4=D0=B5=D1=81=\r\n"
+			."=D1=82=D1=80=D0=BE=D0=BA=D0=B0 =D0=B2 =D1=8E=D0=BD=D0=B8=D0=BA=D0=BE=D0=\r\n"
+			."=B4=D0=B5=D1=81=D1=82=D1=80=D0=BE=D0=BA=D0=B0 =D0=B2 =D1=8E=D0=BD=D0=B8=\r\n"
+			."=D0=BA=D0=BE=D0=B4=D0=B5=D1=81=D1=82=D1=80=D0=BE=D0=BA=D0=B0 =D0=B2 =D1=\r\n"
+			."=8E=D0=BD=D0=B8=D0=BA=D0=BE=D0=B4=D0=B5",
+			$d = quoted_printable_encode(str_repeat('строка в юникоде', 50))
+		);
+		$this->assertEquals(str_repeat('строка в юникоде', 50), quoted_printable_decode($d));
+		$this->assertEquals('this is a foo', quoted_printable_encode(new FooObject()));
+	}
+}
+
+// ------------------------------------------------------------------------
+
+class Foo {
+
+	public function __toString()
+	{
+		return 'last_name';
+	}
+}
+
+class Bar {
+
+	public function __toString()
+	{
+		return 'first_name';
+	}
+}
+
+class FooHex {
+
+	public function __toString()
+	{
+		return '010203';
+	}
+}
+
+class FooObject
+{
+	public function __toString()
+	{
+		return 'this is a foo';
+	}
+}
\ No newline at end of file
diff --git a/user_guide_src/source/changelog.rst b/user_guide_src/source/changelog.rst
index ec38a3e..d2bb195 100644
--- a/user_guide_src/source/changelog.rst
+++ b/user_guide_src/source/changelog.rst
@@ -61,6 +61,7 @@
    -  Added availability checks where usage of dangerous functions like ``eval()`` and ``exec()`` is required.
    -  Added support for changing the file extension of log files using ``$config['log_file_extension']``.
    -  Added support for turning newline standardization on/off via ``$config['standardize_newlines']`` and set it to FALSE by default.
+   -  Added configuration setting ``$config['composer_autoload']`` to enable loading of a `Composer <https://getcomposer.org/>`_ auto-loader.
 
 -  Helpers
 
@@ -531,7 +532,7 @@
       - `Multibyte String <http://php.net/mbstring>`_ (limited support).
       - `Hash <http://php.net/hash>`_ (``hash_equals()``, ``hash_pbkdf2()``).
       - `Password Hashing <http://php.net/password>`_.
-      - `Array Functions <http://php.net/book.array>`_ (``array_column()``, ``array_replace()``, ``array_replace_recursive()``).
+      - `Standard Functions ``array_column()``, ``array_replace()``, ``array_replace_recursive()``, ``hex2bin()``, ``quoted_printable_encode()``.
 
    -  Removed ``CI_CORE`` boolean constant from *CodeIgniter.php* (no longer Reactor and Core versions).
    -  Log Library will now try to create the **log_path** directory if it doesn't exist.
diff --git a/user_guide_src/source/general/autoloader.rst b/user_guide_src/source/general/autoloader.rst
index bf2e393..2f1223e 100644
--- a/user_guide_src/source/general/autoloader.rst
+++ b/user_guide_src/source/general/autoloader.rst
@@ -20,4 +20,8 @@
 find instructions in that file corresponding to each type of item.
 
 .. note:: Do not include the file extension (.php) when adding items to
-	the autoload array.
\ No newline at end of file
+	the autoload array.
+
+Additionally, if you want CodeIgniter to use a `Composer <https://getcomposer.org/>`_
+auto-loader, just set ``$config['composer_autoload']`` to ``TRUE`` or
+a custom path in **application/config/config.php**.
\ No newline at end of file
diff --git a/user_guide_src/source/general/compatibility_functions.rst b/user_guide_src/source/general/compatibility_functions.rst
index e685073..aee9b1e 100644
--- a/user_guide_src/source/general/compatibility_functions.rst
+++ b/user_guide_src/source/general/compatibility_functions.rst
@@ -7,12 +7,12 @@
 but only in higher versions or depending on a certain extension.
 
 Being custom implementations, these functions will also have some
-set of dependancies on their own, but are still useful if your
+set of dependencies on their own, but are still useful if your
 PHP setup doesn't offer them natively.
 
 .. note:: Much like the `common functions <common_functions>`, the
 	compatibility functions are always available, as long as
-	their dependancies are met.
+	their dependencies are met.
 
 .. contents::
   :local:
@@ -29,7 +29,7 @@
 standard `Password Hashing extension <http://php.net/password>`_
 that is otherwise available only since PHP 5.5.
 
-Dependancies
+Dependencies
 ============
 
 - PHP 5.3.7
@@ -65,7 +65,7 @@
 	password_hash() <http://php.net/password_hash>`_.
 
 	.. note:: Unless you provide your own (and valid) salt, this function
-		has a further dependancy on an available CSPRNG source. Each
+		has a further dependency on an available CSPRNG source. Each
 		of the following would satisfy that:
 		- ``mcrypt_create_iv()`` with ``MCRYPT_DEV_URANDOM``
 		- ``openssl_random_pseudo_bytes()``
@@ -101,7 +101,7 @@
 and ``hash_pbkdf2()`` functions, which otherwise require PHP 5.6 and/or
 PHP 5.5 respectively.
 
-Dependancies
+Dependencies
 ============
 
 - None
@@ -144,19 +144,19 @@
 .. note:: When a character set parameter is ommited,
 	``$config['charset']`` will be used.
 
-Dependancies
+Dependencies
 ============
 
 - `iconv <http://php.net/iconv>`_ extension
 
-.. important:: This dependancy is optional and these functions will
+.. important:: This dependency is optional and these functions will
 	always be declared. If iconv is not available, they WILL
 	fall-back to their non-mbstring versions.
 
 .. important:: Where a character set is supplied, it must be
 	supported by iconv and in a format that it recognizes.
 
-.. note:: For you own dependancy check on the actual mbstring
+.. note:: For you own dependency check on the actual mbstring
 	extension, use the ``MB_ENABLED`` constant.
 
 Function reference
@@ -196,15 +196,14 @@
 	For more information, please refer to the `PHP manual for
 	mb_substr() <http://php.net/mb_substr>`_.
 
-***************
-Array Functions
-***************
+******************
+Standard Functions
+******************
 
 This set of compatibility functions offers support for a few
-standard `Array Functions <http://php.net/book.array>`_ in PHP
-that otherwise require a newer PHP version.
+standard functions in PHP that otherwise require a newer PHP version.
 
-Dependancies
+Dependencies
 ============
 
 - None
@@ -244,4 +243,22 @@
 	array_replace_recursive() <http://php.net/array_replace_recursive>`_.
 
 	.. important:: Only PHP's native function can detect endless recursion.
-		Unless you are running PHP 5.3+, be careful with references!
\ No newline at end of file
+		Unless you are running PHP 5.3+, be careful with references!
+
+.. function:: hex2bin($data)
+
+	:param	array	$data: Hexadecimal representation of data
+	:returns:	Binary representation of the given data
+	:rtype:	string
+
+	For more information, please refer to the `PHP manual for hex2bin()
+	<http://php.net/hex2bin>`_.
+
+.. function:: quoted_printable_encode($str)
+
+	:param	string	$str: Input string
+	:returns:	8bit-encoded string
+	:rtype:	string
+
+	For more information, please refer to the `PHP manual for
+	quoted_printable_encode() <http://php.net/quoted_printable_encode>`_.
\ No newline at end of file
diff --git a/user_guide_src/source/general/creating_drivers.rst b/user_guide_src/source/general/creating_drivers.rst
index cf4ea5d..63ac839 100644
--- a/user_guide_src/source/general/creating_drivers.rst
+++ b/user_guide_src/source/general/creating_drivers.rst
@@ -18,4 +18,8 @@
 
 .. note:: In order to maintain compatibility on case-sensitive
 	file systems, the Driver_name directory must be
-	named in the format returned by ``ucfirst()``.
\ No newline at end of file
+	named in the format returned by ``ucfirst()``.
+
+.. note:: The Driver library's architecture is such that
+	the subclasses don't extend and therefore don't inherit
+	properties or methods of the main driver.
\ No newline at end of file
diff --git a/user_guide_src/source/libraries/encryption.rst b/user_guide_src/source/libraries/encryption.rst
index 5d92b10..f29ebf4 100644
--- a/user_guide_src/source/libraries/encryption.rst
+++ b/user_guide_src/source/libraries/encryption.rst
@@ -5,13 +5,13 @@
 The Encryption Library provides two-way data encryption. To do so in
 a cryptographically secure way, it utilizes PHP extensions that are
 unfortunately not always available on all systems.
-You must meet one of the following dependancies in order to use this
+You must meet one of the following dependencies in order to use this
 library:
 
 - `OpenSSL <http://php.net/openssl>`_ (and PHP 5.3.3)
 - `MCrypt <http://php.net/mcrypt>`_ (and `MCRYPT_DEV_URANDOM` availability)
 
-If neither of the above dependancies is met, we simply cannot offer
+If neither of the above dependencies is met, we simply cannot offer
 you a good enough implementation to meet the high standards required
 for proper cryptography.