Merge branch 'develop' into feature/session
diff --git a/application/config/config.php b/application/config/config.php
index d269b6e..e8d30b6 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;
 
 /*
 |--------------------------------------------------------------------------
@@ -244,6 +264,18 @@
 
 /*
 |--------------------------------------------------------------------------
+| Log File Permissions
+|--------------------------------------------------------------------------
+|
+| The file system permissions to be applied on newly created log files.
+|
+| IMPORTANT: This MUST be an integer (no quotes) and you MUST use octal
+|            integer notation (i.e. 0700, 0644, etc.)
+*/
+$config['log_file_permissions'] = 0644;
+
+/*
+|--------------------------------------------------------------------------
 | Date Format for Logs
 |--------------------------------------------------------------------------
 |
diff --git a/application/config/constants.php b/application/config/constants.php
index 239fd46..c19f044 100644
--- a/application/config/constants.php
+++ b/application/config/constants.php
@@ -42,7 +42,7 @@
 define('FILE_READ_MODE', 0644);
 define('FILE_WRITE_MODE', 0666);
 define('DIR_READ_MODE', 0755);
-define('DIR_WRITE_MODE', 0777);
+define('DIR_WRITE_MODE', 0755);
 
 /*
 |--------------------------------------------------------------------------
diff --git a/application/config/mimes.php b/application/config/mimes.php
index 8123557..bab431f 100644
--- a/application/config/mimes.php
+++ b/application/config/mimes.php
@@ -58,7 +58,7 @@
 	'smil'	=>	'application/smil',
 	'mif'	=>	'application/vnd.mif',
 	'xls'	=>	array('application/vnd.ms-excel', 'application/msexcel', 'application/x-msexcel', 'application/x-ms-excel', 'application/x-excel', 'application/x-dos_ms_excel', 'application/xls', 'application/x-xls', 'application/excel', 'application/download', 'application/vnd.ms-office', 'application/msword'),
-	'ppt'	=>	array('application/powerpoint', 'application/vnd.ms-powerpoint'),
+	'ppt'	=>	array('application/powerpoint', 'application/vnd.ms-powerpoint', 'application/vnd.ms-office', 'application/msword'),
 	'pptx'	=> 	array('application/vnd.openxmlformats-officedocument.presentationml.presentation', 'application/x-zip', 'application/zip'),
 	'wbxml'	=>	'application/wbxml',
 	'wmlc'	=>	'application/wmlc',
@@ -174,7 +174,9 @@
 	'7zip'	=>	array('application/x-compressed', 'application/x-zip-compressed', 'application/zip', 'multipart/x-zip'),
 	'cdr'	=>	array('application/cdr', 'application/coreldraw', 'application/x-cdr', 'application/x-coreldraw', 'image/cdr', 'image/x-cdr', 'zz-application/zz-winassoc-cdr'),
 	'wma'	=>	array('audio/x-ms-wma', 'video/x-ms-asf'),
-	'jar'	=>	array('application/java-archive', 'application/x-java-application', 'application/x-jar', 'application/x-compressed')
+	'jar'	=>	array('application/java-archive', 'application/x-java-application', 'application/x-jar', 'application/x-compressed'),
+	'svg'	=>	array('image/svg+xml', 'application/xml', 'text/xml'),
+	'vcf'	=>	'text/x-vcard'
 );
 
 /* End of file mimes.php */
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/Common.php b/system/core/Common.php
index 752a2e7..504e225 100644
--- a/system/core/Common.php
+++ b/system/core/Common.php
@@ -289,7 +289,7 @@
 			$_config[0] =& get_config();
 		}
 
-		return isset($_config[0][$item]) ? $_config[0][$item] : FALSE;
+		return isset($_config[0][$item]) ? $_config[0][$item] : NULL;
 	}
 }
 
@@ -690,16 +690,20 @@
 if ( ! function_exists('html_escape'))
 {
 	/**
-	 * Returns HTML escaped variable
+	 * Returns HTML escaped variable.
 	 *
-	 * @param	mixed
-	 * @return	mixed
+	 * @param	mixed	$var		The input string or array of strings to be escaped.
+	 * @param	bool	$double_encode	$double_encode set to FALSE prevents escaping twice.
+	 * @return	mixed			The escaped string or array of strings as a result.
 	 */
-	function html_escape($var)
+	function html_escape($var, $double_encode = TRUE)
 	{
-		return is_array($var)
-			? array_map('html_escape', $var)
-			: htmlspecialchars($var, ENT_QUOTES, config_item('charset'));
+		if (is_array($var))
+		{
+			return array_map('html_escape', $var, array_fill(0, count($var), $double_encode));
+		}
+
+		return htmlspecialchars($var, ENT_QUOTES, config_item('charset'), $double_encode);
 	}
 }
 
diff --git a/system/core/Config.php b/system/core/Config.php
index ad0e5f9..db406df 100644
--- a/system/core/Config.php
+++ b/system/core/Config.php
@@ -104,10 +104,11 @@
 	public function load($file = '', $use_sections = FALSE, $fail_gracefully = FALSE)
 	{
 		$file = ($file === '') ? 'config' : str_replace('.php', '', $file);
-		$found = $loaded = FALSE;
+		$loaded = FALSE;
 
 		foreach ($this->_config_paths as $path)
 		{
+			$found = FALSE;
 			foreach (array(ENVIRONMENT.'/'.$file, $file) as $location)
 			{
 				$file_path = $path.'config/'.$location.'.php';
diff --git a/system/core/Exceptions.php b/system/core/Exceptions.php
index cb4bc3c..49c2217 100644
--- a/system/core/Exceptions.php
+++ b/system/core/Exceptions.php
@@ -145,9 +145,11 @@
 	 */
 	public function show_error($heading, $message, $template = 'error_general', $status_code = 500)
 	{
-		$templates_path = config_item('error_views_path')
-			? config_item('error_views_path')
-			: VIEWPATH.'errors'.DIRECTORY_SEPARATOR;
+		$templates_path = config_item('error_views_path');
+		if (empty($templates_path))
+		{
+			$templates_path = VIEWPATH.'errors'.DIRECTORY_SEPARATOR;
+		}
 
 		if (is_cli())
 		{
@@ -185,9 +187,11 @@
 	 */
 	public function show_php_error($severity, $message, $filepath, $line)
 	{
-		$templates_path = config_item('error_views_path')
-			? config_item('error_views_path')
-			: VIEWPATH.'errors'.DIRECTORY_SEPARATOR;
+		$templates_path = config_item('error_views_path');
+		if (empty($templates_path))
+		{
+			$templates_path = VIEWPATH.'errors'.DIRECTORY_SEPARATOR;
+		}
 
 		$severity = isset($this->levels[$severity]) ? $this->levels[$severity] : $severity;
 
diff --git a/system/core/Hooks.php b/system/core/Hooks.php
index fd1a2ba..26ced08 100644
--- a/system/core/Hooks.php
+++ b/system/core/Hooks.php
@@ -127,7 +127,7 @@
 			return FALSE;
 		}
 
-		if (is_array($this->hooks[$which]))
+		if (is_array($this->hooks[$which]) && ! isset($this->hooks[$which]['function']))
 		{
 			foreach ($this->hooks[$which] as $val)
 			{
diff --git a/system/core/Input.php b/system/core/Input.php
index 544b7c0..9ae2f6d 100644
--- a/system/core/Input.php
+++ b/system/core/Input.php
@@ -353,7 +353,7 @@
 			$path = config_item('cookie_path');
 		}
 
-		if ($secure === FALSE && config_item('cookie_secure') !== FALSE)
+		if ($secure === FALSE && config_item('cookie_secure') === TRUE)
 		{
 			$secure = config_item('cookie_secure');
 		}
@@ -766,7 +766,7 @@
 	 *
 	 * @param	string		$index		Header name
 	 * @param	bool		$xss_clean	Whether to apply XSS filtering
-	 * @return	string|bool	The requested header on success or FALSE on failure
+	 * @return	string|null	The requested header on success or NULL on failure
 	 */
 	public function get_request_header($index, $xss_clean = FALSE)
 	{
diff --git a/system/core/Log.php b/system/core/Log.php
index a949c3f..1dca1bf 100644
--- a/system/core/Log.php
+++ b/system/core/Log.php
@@ -45,32 +45,39 @@
 	protected $_log_path;
 
 	/**
+	 * File permissions
+	 *
+	 * @var	int
+	 */
+	protected $_file_permissions = 0644;
+
+	/**
 	 * Level of logging
 	 *
 	 * @var int
 	 */
-	protected $_threshold		= 1;
+	protected $_threshold = 1;
 
 	/**
 	 * Highest level of logging
 	 *
 	 * @var int
 	 */
-	protected $_threshold_max	= 0;
+	protected $_threshold_max = 0;
 
 	/**
 	 * Array of threshold levels to log
 	 *
 	 * @var array
 	 */
-	protected $_threshold_array	= array();
+	protected $_threshold_array = array();
 
 	/**
 	 * Format of timestamp for log files
 	 *
 	 * @var string
 	 */
-	protected $_date_fmt		= 'Y-m-d H:i:s';
+	protected $_date_fmt = 'Y-m-d H:i:s';
 
 	/**
 	 * Filename extension
@@ -84,14 +91,14 @@
 	 *
 	 * @var bool
 	 */
-	protected $_enabled		= TRUE;
+	protected $_enabled = TRUE;
 
 	/**
 	 * Predefined logging levels
 	 *
 	 * @var array
 	 */
-	protected $_levels		= array('ERROR' => 1, 'DEBUG' => 2, 'INFO' => 3, 'ALL' => 4);
+	protected $_levels = array('ERROR' => 1, 'DEBUG' => 2, 'INFO' => 3, 'ALL' => 4);
 
 	// --------------------------------------------------------------------
 
@@ -108,7 +115,7 @@
 		$this->_file_ext = (isset($config['log_file_extension']) && $config['log_file_extension'] !== '')
 			? ltrim($config['log_file_extension'], '.') : 'php';
 
-		file_exists($this->_log_path) OR mkdir($this->_log_path, 0777, TRUE);
+		file_exists($this->_log_path) OR mkdir($this->_log_path, 0755, TRUE);
 
 		if ( ! is_dir($this->_log_path) OR ! is_really_writable($this->_log_path))
 		{
@@ -125,10 +132,15 @@
 			$this->_threshold_array = array_flip($config['log_threshold']);
 		}
 
-		if ($config['log_date_format'] !== '')
+		if ( ! empty($config['log_date_format']))
 		{
 			$this->_date_fmt = $config['log_date_format'];
 		}
+
+		if ( ! empty($config['log_file_permissions']) && is_int($config['log_file_permissions']))
+		{
+			$this->_file_permissions = $config['log_file_permissions'];
+		}
 	}
 
 	// --------------------------------------------------------------------
@@ -192,7 +204,7 @@
 
 		if (isset($newfile) && $newfile === TRUE)
 		{
-			@chmod($filepath, 0666);
+			chmod($filepath, $this->_file_permissions);
 		}
 
 		return is_int($result);
diff --git a/system/core/Output.php b/system/core/Output.php
index 238d223..de07125 100644
--- a/system/core/Output.php
+++ b/system/core/Output.php
@@ -606,7 +606,7 @@
 
 		if (is_int($result))
 		{
-			@chmod($cache_path, 0666);
+			chmod($cache_path, 0640);
 			log_message('debug', 'Cache file written: '.$cache_path);
 
 			// Send HTTP cache-control headers to browser to match file cache settings.
diff --git a/system/core/Security.php b/system/core/Security.php
index 2cf214b..cffdb9a 100755
--- a/system/core/Security.php
+++ b/system/core/Security.php
@@ -77,7 +77,7 @@
 	 *
 	 * @var	string
 	 */
-	protected $_xss_hash =	'';
+	protected $_xss_hash;
 
 	/**
 	 * CSRF Hash
@@ -86,7 +86,7 @@
 	 *
 	 * @var	string
 	 */
-	protected $_csrf_hash =	'';
+	protected $_csrf_hash;
 
 	/**
 	 * CSRF Expire time
@@ -158,7 +158,7 @@
 	public function __construct()
 	{
 		// Is CSRF protection enabled?
-		if (config_item('csrf_protection') === TRUE)
+		if (config_item('csrf_protection'))
 		{
 			// CSRF config
 			foreach (array('csrf_expire', 'csrf_token_name', 'csrf_cookie_name') as $key)
@@ -170,9 +170,9 @@
 			}
 
 			// Append application specific cookie prefix
-			if (config_item('cookie_prefix'))
+			if ($cookie_prefix = config_item('cookie_prefix'))
 			{
-				$this->_csrf_cookie_name = config_item('cookie_prefix').$this->_csrf_cookie_name;
+				$this->_csrf_cookie_name = $cookie_prefix.$this->_csrf_cookie_name;
 			}
 
 			// Set the CSRF hash
@@ -203,9 +203,12 @@
 		if ($exclude_uris = config_item('csrf_exclude_uris'))
 		{
 			$uri = load_class('URI', 'core');
-			if (in_array($uri->uri_string(), $exclude_uris))
+			foreach ($exclude_uris as $excluded)
 			{
-				return $this;
+				if (preg_match('#^'.$excluded.'$#i'.(UTF8_ENABLED ? 'u' : ''), $uri->uri_string()))
+				{
+					return $this;
+				}
 			}
 		}
 
@@ -224,7 +227,7 @@
 		{
 			// Nothing should last forever
 			unset($_COOKIE[$this->_csrf_cookie_name]);
-			$this->_csrf_hash = '';
+			$this->_csrf_hash = NULL;
 		}
 
 		$this->_csrf_set_hash();
@@ -275,7 +278,7 @@
 	 */
 	public function csrf_show_error()
 	{
-		show_error('The action you have requested is not allowed.');
+		show_error('The action you have requested is not allowed.', 403);
 	}
 
 	// --------------------------------------------------------------------
@@ -370,7 +373,7 @@
 		 * We only convert entities that are within tags since
 		 * these are the ones that will pose security problems.
 		 */
-		$str = preg_replace_callback("/[a-z]+=([\'\"]).*?\\1/si", array($this, '_convert_attribute'), $str);
+		$str = preg_replace_callback("/[^a-z0-9>]+[a-z0-9]+=([\'\"]).*?\\1/si", array($this, '_convert_attribute'), $str);
 		$str = preg_replace_callback('/<\w+.*/si', array($this, '_decode_entity'), $str);
 
 		// Remove Invisible Characters Again!
@@ -436,7 +439,7 @@
 
 		/*
 		 * Remove disallowed Javascript in links or img tags
-		 * We used to do some version comparisons and use of stripos for PHP5,
+		 * We used to do some version comparisons and use of stripos(),
 		 * but it is dog slow compared to these simplified non-capturing
 		 * preg_match(), especially if the pattern exists in the string
 		 *
@@ -535,9 +538,12 @@
 	 */
 	public function xss_hash()
 	{
-		if ($this->_xss_hash === '')
+		if ($this->_xss_hash === NULL)
 		{
-			$this->_xss_hash = md5(uniqid(mt_rand()));
+			$rand = $this->get_random_bytes(16);
+			$this->_xss_hash = ($rand === FALSE)
+				? md5(uniqid(mt_rand(), TRUE))
+				: bin2hex($rand);
 		}
 
 		return $this->_xss_hash;
@@ -546,6 +552,48 @@
 	// --------------------------------------------------------------------
 
 	/**
+	 * Get random bytes
+	 *
+	 * @param	int	$length	Output length
+	 * @return	string
+	 */
+	public function get_random_bytes($length)
+	{
+		if (empty($length) OR ! ctype_digit((string) $length))
+		{
+			return FALSE;
+		}
+
+		// Unfortunately, none of the following PRNGs is guaranteed to exist ...
+		if (defined('MCRYPT_DEV_URANDOM') && ($output = mcrypt_create_iv($length, MCRYPT_DEV_URANDOM)) !== FALSE)
+		{
+			return $output;
+		}
+
+
+		if (is_readable('/dev/urandom') && ($fp = fopen('/dev/urandom', 'rb')) !== FALSE)
+		{
+			// Try not to waste entropy ...
+			is_php('5.4') && stream_set_chunk_size($fp, $length);
+			$output = fread($fp, $length);
+			fclose($fp);
+			if ($output !== FALSE)
+			{
+				return $output;
+			}
+		}
+
+		if (function_exists('openssl_random_pseudo_bytes'))
+		{
+			return openssl_random_pseudo_bytes($length);
+		}
+
+		return FALSE;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
 	 * HTML Entities Decode
 	 *
 	 * A replacement for html_entity_decode()
@@ -605,7 +653,7 @@
 				{
 					if (($char = array_search($matches[$i].';', $_entities, TRUE)) !== FALSE)
 					{
-						$replace[$matches[$i]] = $character;
+						$replace[$matches[$i]] = $char;
 					}
 				}
 
@@ -912,7 +960,7 @@
 	 */
 	protected function _csrf_set_hash()
 	{
-		if ($this->_csrf_hash === '')
+		if ($this->_csrf_hash === NULL)
 		{
 			// If the cookie exists we will use its value.
 			// We don't necessarily want to regenerate it with
@@ -924,8 +972,10 @@
 				return $this->_csrf_hash = $_COOKIE[$this->_csrf_cookie_name];
 			}
 
-			$this->_csrf_hash = md5(uniqid(mt_rand(), TRUE));
-			$this->csrf_set_cookie();
+			$rand = $this->get_random_bytes(16);
+			$this->_csrf_hash = ($rand === FALSE)
+				? md5(uniqid(mt_rand(), TRUE))
+				: bin2hex($rand);
 		}
 
 		return $this->_csrf_hash;
diff --git a/system/core/compat/password.php b/system/core/compat/password.php
index a9355d5..1f67a52 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']))
@@ -118,8 +121,11 @@
 					return FALSE;
 				}
 
+				// Try not to waste entropy ...
+				is_php('5.4') && stream_set_chunk_size($fp, 16);
+
 				$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)
 					{
@@ -145,7 +151,10 @@
 		}
 
 		isset($options['cost']) OR $options['cost'] = 10;
-		return crypt($password, sprintf('$2y$%02d$%s', $options['cost'], $options['salt']));
+
+		return (strlen($password = crypt($password, sprintf('$2y$%02d$%s', $options['cost'], $options['salt']))) === 60)
+			? $password
+			: 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/database/DB_cache.php b/system/database/DB_cache.php
index b855ff2..2efb42c 100644
--- a/system/database/DB_cache.php
+++ b/system/database/DB_cache.php
@@ -156,14 +156,9 @@
 		$dir_path = $this->db->cachedir.$segment_one.'+'.$segment_two.'/';
 		$filename = md5($sql);
 
-		if ( ! is_dir($dir_path))
+		if ( ! is_dir($dir_path) && ! @mkdir($dir_path, 0750))
 		{
-			if ( ! @mkdir($dir_path, 0777))
-			{
-				return FALSE;
-			}
-
-			@chmod($dir_path, 0777);
+			return FALSE;
 		}
 
 		if (write_file($dir_path.$filename, serialize($object)) === FALSE)
@@ -171,7 +166,7 @@
 			return FALSE;
 		}
 
-		@chmod($dir_path.$filename, 0666);
+		chmod($dir_path.$filename, 0640);
 		return TRUE;
 	}
 
diff --git a/system/database/DB_driver.php b/system/database/DB_driver.php
index 12ab5bb..62cea75 100644
--- a/system/database/DB_driver.php
+++ b/system/database/DB_driver.php
@@ -1440,7 +1440,7 @@
 	 */
 	protected function _has_operator($str)
 	{
-		return (bool) preg_match('/(<|>|!|=|\sIS NULL|\sIS NOT NULL|\sEXISTS|\sBETWEEN|\sLIKE|\sIN\s*\(|\s)/i', trim($str));
+		return (bool) preg_match('/(<|>|!|=|\sIS\s|\sEXISTS|\sBETWEEN|\sLIKE|\sIN\s*\(|\s)/i', trim($str));
 	}
 
 	// --------------------------------------------------------------------
@@ -1464,8 +1464,7 @@
 				'\s*(?:<|>|!)?=\s*',		// =, <=, >=, !=
 				'\s*<>?\s*',			// <, <>
 				'\s*>\s*',			// >
-				'\s+IS NULL',			// IS NULL
-				'\s+IS NOT NULL',		// IS NOT NULL
+				'\s+IS(?:\sNOT)?(?:\sNULL)?',	// IS[ NOT] NULL
 				'\s+EXISTS\s*\([^\)]+\)',	// EXISTS(sql)
 				'\s+NOT EXISTS\s*\([^\)]+\)',	// NOT EXISTS(sql)
 				'\s+BETWEEN\s+\S+\s+AND\s+\S+',	// BETWEEN value AND value
diff --git a/system/database/DB_forge.php b/system/database/DB_forge.php
index 111546e..2dd243c 100644
--- a/system/database/DB_forge.php
+++ b/system/database/DB_forge.php
@@ -929,7 +929,7 @@
 				$field['default'] = empty($this->_null) ? '' : $this->_default.$this->_null;
 
 				// Override the NULL attribute if that's our default
-				$attributes['NULL'] = NULL;
+				$attributes['NULL'] = TRUE;
 				$field['null'] = empty($this->_null) ? '' : ' '.$this->_null;
 			}
 			else
diff --git a/system/database/DB_query_builder.php b/system/database/DB_query_builder.php
index 085c615..2096ffd 100644
--- a/system/database/DB_query_builder.php
+++ b/system/database/DB_query_builder.php
@@ -635,7 +635,7 @@
 			$key = array($key => $value);
 		}
 
-		// If the escape value was not set will will base it on the global setting
+		// If the escape value was not set will base it on the global setting
 		is_bool($escape) OR $escape = $this->_protect_identifiers;
 
 		foreach ($key as $k => $v)
@@ -661,6 +661,10 @@
 				// value appears not to have been set, assign the test to IS NULL
 				$k .= ' IS NULL';
 			}
+			elseif (preg_match('/\s*(!?=|<>)\s*$/i', $k, $match, PREG_OFFSET_CAPTURE))
+			{
+				$k = substr($k, 0, $match[0][1]).($match[1][0] === '=' ? ' IS NULL' : ' IS NOT NULL');
+			}
 
 			$this->{$qb_key}[] = array('condition' => $prefix.$k.$v, 'escape' => $escape);
 			if ($this->qb_caching === TRUE)
diff --git a/system/database/drivers/cubrid/cubrid_driver.php b/system/database/drivers/cubrid/cubrid_driver.php
index 138b0ed..c5cb796 100644
--- a/system/database/drivers/cubrid/cubrid_driver.php
+++ b/system/database/drivers/cubrid/cubrid_driver.php
@@ -264,14 +264,7 @@
 	 */
 	protected function _escape_str($str)
 	{
-		if (function_exists('cubrid_real_escape_string') &&
-			(is_resource($this->conn_id)
-				OR (get_resource_type($this->conn_id) === 'Unknown' && preg_match('/Resource id #/', strval($this->conn_id)))))
-		{
-			return cubrid_real_escape_string($str, $this->conn_id);
-		}
-
-		return addslashes($str);
+		return cubrid_real_escape_string($str, $this->conn_id);
 	}
 
 	// --------------------------------------------------------------------
diff --git a/system/database/drivers/ibase/ibase_driver.php b/system/database/drivers/ibase/ibase_driver.php
index b19985c..f4e5aef 100644
--- a/system/database/drivers/ibase/ibase_driver.php
+++ b/system/database/drivers/ibase/ibase_driver.php
@@ -219,11 +219,11 @@
 	 */
 	protected function _list_tables($prefix_limit = FALSE)
 	{
-		$sql = 'SELECT "RDB$RELATION_NAME" FROM "RDB$RELATIONS" WHERE "RDB$RELATION_NAME" NOT LIKE \'RDB$%\' AND "RDB$RELATION_NAME" NOT LIKE \'MON$%\'';
+		$sql = 'SELECT TRIM("RDB$RELATION_NAME") AS TABLE_NAME FROM "RDB$RELATIONS" WHERE "RDB$RELATION_NAME" NOT LIKE \'RDB$%\' AND "RDB$RELATION_NAME" NOT LIKE \'MON$%\'';
 
 		if ($prefix_limit !== FALSE && $this->dbprefix !== '')
 		{
-			return $sql.' AND "RDB$RELATION_NAME" LIKE \''.$this->escape_like_str($this->dbprefix)."%' "
+			return $sql.' AND TRIM("RDB$RELATION_NAME") AS TABLE_NAME LIKE \''.$this->escape_like_str($this->dbprefix)."%' "
 				.sprintf($this->_like_escape_str, $this->_like_escape_chr);
 		}
 
@@ -242,7 +242,7 @@
 	 */
 	protected function _list_columns($table = '')
 	{
-		return 'SELECT "RDB$FIELD_NAME" FROM "RDB$RELATION_FIELDS" WHERE "RDB$RELATION_NAME" = '.$this->escape($table);
+		return 'SELECT TRIM("RDB$FIELD_NAME") AS COLUMN_NAME FROM "RDB$RELATION_FIELDS" WHERE "RDB$RELATION_NAME" = '.$this->escape($table);
 	}
 
 	// --------------------------------------------------------------------
diff --git a/system/database/drivers/mssql/mssql_driver.php b/system/database/drivers/mssql/mssql_driver.php
index f4a1661..8d830fb 100644
--- a/system/database/drivers/mssql/mssql_driver.php
+++ b/system/database/drivers/mssql/mssql_driver.php
@@ -143,8 +143,8 @@
 		}
 
 		// Note: Escaping is required in the event that the DB name
-		// contains reserved characters
-		if (mssql_select_db($this->escape_identifiers($database), $this->conn_id))
+		// contains reserved characters.
+		if (mssql_select_db('['.$database.']', $this->conn_id))
 		{
 			$this->database = $database;
 			return TRUE;
diff --git a/system/database/drivers/mysql/mysql_driver.php b/system/database/drivers/mysql/mysql_driver.php
index 7cbcf10..a827a6e 100644
--- a/system/database/drivers/mysql/mysql_driver.php
+++ b/system/database/drivers/mysql/mysql_driver.php
@@ -336,9 +336,7 @@
 	 */
 	protected function _escape_str($str)
 	{
-		return is_resource($this->conn_id)
-			? mysql_real_escape_string($str, $this->conn_id)
-			: addslashes($str);
+		return mysql_real_escape_string($str, $this->conn_id);
 	}
 
 	// --------------------------------------------------------------------
diff --git a/system/database/drivers/mysqli/mysqli_driver.php b/system/database/drivers/mysqli/mysqli_driver.php
index 09277fc..aa4c6b5 100644
--- a/system/database/drivers/mysqli/mysqli_driver.php
+++ b/system/database/drivers/mysqli/mysqli_driver.php
@@ -307,9 +307,7 @@
 	 */
 	protected function _escape_str($str)
 	{
-		return is_object($this->conn_id)
-			? $this->conn_id->real_escape_string($str)
-			: addslashes($str);
+		return $this->conn_id->real_escape_string($str);
 	}
 
 	// --------------------------------------------------------------------
diff --git a/system/database/drivers/pdo/pdo_result.php b/system/database/drivers/pdo/pdo_result.php
index 1b8fbc9..3f3af2e 100644
--- a/system/database/drivers/pdo/pdo_result.php
+++ b/system/database/drivers/pdo/pdo_result.php
@@ -93,7 +93,7 @@
 		{
 			// Might trigger an E_WARNING due to not all subdrivers
 			// supporting getColumnMeta()
-			$field_names[$i] = @$this->result_id->getColumnMeta();
+			$field_names[$i] = @$this->result_id->getColumnMeta($i);
 			$field_names[$i] = $field_names[$i]['name'];
 		}
 
diff --git a/system/database/drivers/sqlite3/sqlite3_driver.php b/system/database/drivers/sqlite3/sqlite3_driver.php
index a7d0d08..2b447a1 100644
--- a/system/database/drivers/sqlite3/sqlite3_driver.php
+++ b/system/database/drivers/sqlite3/sqlite3_driver.php
@@ -189,7 +189,7 @@
 	 */
 	protected function _escape_str($str)
 	{
-		return $this->conn_id->escapeString(remove_invisible_characters($str));
+		return $this->conn_id->escapeString($str);
 	}
 
 	// --------------------------------------------------------------------
diff --git a/system/helpers/captcha_helper.php b/system/helpers/captcha_helper.php
index 74ab24f..f4ed616 100644
--- a/system/helpers/captcha_helper.php
+++ b/system/helpers/captcha_helper.php
@@ -216,8 +216,22 @@
 		//  Generate the image
 		// -----------------------------------
 		$img_url = rtrim($img_url, '/').'/';
-		$img_filename = $now.'.jpg';
-		ImageJPEG($im, $img_path.$img_filename);
+
+		if (function_exists('imagejpeg'))
+		{
+			$img_filename = $now.'.jpg';
+			imagejpeg($im, $img_path.$img_filename);
+		}
+		elseif (function_exists('imagepng'))
+		{
+			$img_filename = $now.'.png';
+			imagepng($im, $img_path.$img_filename);
+		}
+		else
+		{
+			return FALSE;
+		}
+
 		$img = '<img src="'.$img_url.$img_filename.'" style="width: '.$img_width.'; height: '.$img_height .'; border: 0;" alt=" " />';
 		ImageDestroy($im);
 
diff --git a/system/helpers/file_helper.php b/system/helpers/file_helper.php
index 8cfe0f1..7d2253e 100644
--- a/system/helpers/file_helper.php
+++ b/system/helpers/file_helper.php
@@ -80,7 +80,7 @@
 
 		flock($fp, LOCK_EX);
 
-		for ($written = 0, $length = strlen($data); $written < $length; $written += $result)
+		for ($result = $written = 0, $length = strlen($data); $written < $length; $written += $result)
 		{
 			if (($result = fwrite($fp, substr($data, $written))) === FALSE)
 			{
@@ -225,7 +225,7 @@
 				$source_dir = rtrim(realpath($source_dir), DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR;
 			}
 
-			// foreach (scandir($source_dir, 1) as $file) // In addition to being PHP5+, scandir() is simply not as fast
+			// Used to be foreach (scandir($source_dir, 1) as $file), but scandir() is simply not as fast
 			while (FALSE !== ($file = readdir($fp)))
 			{
 				if (is_dir($source_dir.$file) && $file[0] !== '.' && $top_level_only === FALSE)
diff --git a/system/helpers/url_helper.php b/system/helpers/url_helper.php
index dff1a86..0846472 100644
--- a/system/helpers/url_helper.php
+++ b/system/helpers/url_helper.php
@@ -208,8 +208,12 @@
 			$window_name = $attributes['window_name'];
 			unset($attributes['window_name']);
 		}
+		else
+		{
+			$window_name = '_blank';
+		}
 
-		foreach (array('width' => '800', 'height' => '600', 'scrollbars' => 'yes', 'status' => 'yes', 'resizable' => 'yes', 'screenx' => '0', 'screeny' => '0') as $key => $val)
+		foreach (array('width' => '800', 'height' => '600', 'scrollbars' => 'yes', 'menubar' => 'no', 'status' => 'yes', 'resizable' => 'yes', 'screenx' => '0', 'screeny' => '0') as $key => $val)
 		{
 			$atts[$key] = isset($attributes[$key]) ? $attributes[$key] : $val;
 			unset($attributes[$key]);
diff --git a/system/libraries/Cache/drivers/Cache_file.php b/system/libraries/Cache/drivers/Cache_file.php
index c6aa848..2989804 100644
--- a/system/libraries/Cache/drivers/Cache_file.php
+++ b/system/libraries/Cache/drivers/Cache_file.php
@@ -92,7 +92,7 @@
 
 		if (write_file($this->_cache_path.$id, serialize($contents)))
 		{
-			@chmod($this->_cache_path.$id, 0660);
+			chmod($this->_cache_path.$id, 0640);
 			return TRUE;
 		}
 
@@ -125,7 +125,11 @@
 	{
 		$data = $this->_get($id);
 
-		if ($data === FALSE OR ! is_int($data['data']))
+		if ($data === FALSE)
+		{
+			$data = array('data' => 0, 'ttl' => 60);
+		}
+		elseif ( ! is_int($data['data']))
 		{
 			return FALSE;
 		}
@@ -149,7 +153,11 @@
 	{
 		$data = $this->_get($id);
 
-		if ($data === FALSE OR ! is_int($data['data']))
+		if ($data === FALSE)
+		{
+			$data = array('data' => 0, 'ttl' => 60);
+		}
+		elseif ( ! is_int($data['data']))
 		{
 			return FALSE;
 		}
diff --git a/system/libraries/Cache/drivers/Cache_memcached.php b/system/libraries/Cache/drivers/Cache_memcached.php
index bed606a..55b7694 100644
--- a/system/libraries/Cache/drivers/Cache_memcached.php
+++ b/system/libraries/Cache/drivers/Cache_memcached.php
@@ -49,7 +49,7 @@
 	 *
 	 * @var array
 	 */
-	protected $_memcache_conf	= array(
+	protected $_memcache_conf = array(
 		'default' => array(
 			'host'		=> '127.0.0.1',
 			'port'		=> 11211,
@@ -202,12 +202,12 @@
 	{
 		// Try to load memcached server info from the config file.
 		$CI =& get_instance();
+		$defaults = $this->_memcache_conf['default'];
 
 		if ($CI->config->load('memcached', TRUE, TRUE))
 		{
 			if (is_array($CI->config->config['memcached']))
 			{
-				$defaults = $this->_memcache_conf['default'];
 				$this->_memcache_conf = array();
 
 				foreach ($CI->config->config['memcached'] as $name => $conf)
diff --git a/system/libraries/Cache/drivers/Cache_redis.php b/system/libraries/Cache/drivers/Cache_redis.php
index 1c76426..7c9da3d 100644
--- a/system/libraries/Cache/drivers/Cache_redis.php
+++ b/system/libraries/Cache/drivers/Cache_redis.php
@@ -58,6 +58,13 @@
 	 */
 	protected $_redis;
 
+	/**
+	 * An internal cache for storing keys of serialized values.
+	 *
+	 * @var	array
+	 */
+	protected $_serialized = array();
+
 	// ------------------------------------------------------------------------
 
 	/**
@@ -68,7 +75,14 @@
 	 */
 	public function get($key)
 	{
-		return $this->_redis->get($key);
+		$value = $this->_redis->get($key);
+
+		if ($value !== FALSE && isset($this->_serialized[$key]))
+		{
+			return unserialize($value);
+		}
+
+		return $value;
 	}
 
 	// ------------------------------------------------------------------------
@@ -84,6 +98,22 @@
 	 */
 	public function save($id, $data, $ttl = 60, $raw = FALSE)
 	{
+		if (is_array($data) OR is_object($data))
+		{
+			if ( ! $this->_redis->sAdd('_ci_redis_serialized', $id))
+			{
+				return FALSE;
+			}
+
+			isset($this->_serialized[$id]) OR $this->_serialized[$id] = TRUE;
+			$data = serialize($data);
+		}
+		elseif (isset($this->_serialized[$id]))
+		{
+			$this->_serialized[$id] = NULL;
+			$this->_redis->sRemove('_ci_redis_serialized', $id);
+		}
+
 		return ($ttl)
 			? $this->_redis->setex($id, $ttl, $data)
 			: $this->_redis->set($id, $data);
@@ -99,7 +129,18 @@
 	 */
 	public function delete($key)
 	{
-		return ($this->_redis->delete($key) === 1);
+		if ($this->_redis->delete($key) !== 1)
+		{
+			return FALSE;
+		}
+
+		if (isset($this->_serialized[$key]))
+		{
+			$this->_serialized[$key] = NULL;
+			$this->_redis->sRemove('_ci_redis_serialized', $key);
+		}
+
+		return TRUE;
 	}
 
 	// ------------------------------------------------------------------------
@@ -113,9 +154,7 @@
 	 */
 	public function increment($id, $offset = 1)
 	{
-		return $this->_redis->exists($id)
-			? $this->_redis->incr($id, $offset)
-			: FALSE;
+		return $this->_redis->incr($id, $offset);
 	}
 
 	// ------------------------------------------------------------------------
@@ -129,9 +168,7 @@
 	 */
 	public function decrement($id, $offset = 1)
 	{
-		return $this->_redis->exists($id)
-			? $this->_redis->decr($id, $offset)
-			: FALSE;
+		return $this->_redis->decr($id, $offset);
 	}
 
 	// ------------------------------------------------------------------------
@@ -259,13 +296,19 @@
 			$this->_redis->auth($config['password']);
 		}
 
+		// Initialize the index of serialized values.
+		$serialized = $this->_redis->sMembers('_ci_redis_serialized');
+		if ( ! empty($serialized))
+		{
+			$this->_serialized = array_flip($serialized);
+		}
+
 		return TRUE;
 	}
 
 	// ------------------------------------------------------------------------
 
 	/**
-
 	 * Class destructor
 	 *
 	 * Closes the connection to Redis if present.
diff --git a/system/libraries/Email.php b/system/libraries/Email.php
index c39a26a..88398d3 100644
--- a/system/libraries/Email.php
+++ b/system/libraries/Email.php
@@ -1079,6 +1079,11 @@
 	 */
 	public function valid_email($email)
 	{
+		if (function_exists('idn_to_ascii') && $atpos = strpos($email, '@'))
+		{
+			$email = substr($email, 0, ++$atpos).idn_to_ascii(substr($email, $atpos));
+		}
+
 		return (bool) filter_var($email, FILTER_VALIDATE_EMAIL);
 	}
 
diff --git a/system/libraries/Encrypt.php b/system/libraries/Encrypt.php
index 2541a44..1af42ed 100644
--- a/system/libraries/Encrypt.php
+++ b/system/libraries/Encrypt.php
@@ -29,7 +29,7 @@
 /**
  * CodeIgniter Encryption Class
  *
- * Provides two-way keyed encoding using XOR Hashing and Mcrypt
+ * Provides two-way keyed encoding using Mcrypt
  *
  * @package		CodeIgniter
  * @subpackage	Libraries
@@ -111,7 +111,7 @@
 
 			$key = config_item('encryption_key');
 
-			if ($key === FALSE)
+			if ( ! strlen($key))
 			{
 				show_error('In order to use the encryption class requires that you set an encryption key in your config file.');
 			}
diff --git a/system/libraries/Encryption.php b/system/libraries/Encryption.php
index 810b7bf..1a61967 100644
--- a/system/libraries/Encryption.php
+++ b/system/libraries/Encryption.php
@@ -105,7 +105,6 @@
 			'cfb8' => 'cfb8',
 			'ctr' => 'ctr',
 			'stream' => '',
-			'gcm' => 'gcm',
 			'xts' => 'xts'
 		)
 	);
@@ -124,6 +123,13 @@
 		'sha512' => 64
 	);
 
+	/**
+	 * mbstring.func_override flag
+	 *
+	 * @var	bool
+	 */
+	protected static $func_override;
+
 	// --------------------------------------------------------------------
 
 	/**
@@ -146,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;
 		}
@@ -310,6 +318,21 @@
 	// --------------------------------------------------------------------
 
 	/**
+	 * Create a random key
+	 *
+	 * @param	int	$length	Output length
+	 * @return	string
+	 */
+	public function create_key($length)
+	{
+		return ($this->_driver === 'mcrypt')
+			? mcrypt_create_iv($length, MCRYPT_DEV_URANDOM)
+			: openssl_random_pseudo_bytes($length);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
 	 * Encrypt
 	 *
 	 * @param	string	$data	Input data
@@ -323,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)
 		{
@@ -356,16 +379,14 @@
 		{
 			return FALSE;
 		}
-		elseif ( ! isset($params['iv']))
-		{
-			// The greater-than-1 comparison is mostly a work-around for a bug,
-			// where 1 is returned for ARCFour instead of 0.
-			$params['iv'] = (($iv_size = mcrypt_enc_get_iv_size($params['handle'])) > 1)
-				? mcrypt_create_iv($iv_size, MCRYPT_DEV_URANDOM)
-				: NULL;
-		}
 
-		if (mcrypt_generic_init($params['handle'], $params['key'], $params['iv']) < 0)
+		// The greater-than-1 comparison is mostly a work-around for a bug,
+		// where 1 is returned for ARCFour instead of 0.
+		$iv = (($iv_size = mcrypt_enc_get_iv_size($params['handle'])) > 1)
+			? mcrypt_create_iv($iv_size, MCRYPT_DEV_URANDOM)
+			: NULL;
+
+		if (mcrypt_generic_init($params['handle'], $params['key'], $iv) < 0)
 		{
 			if ($params['handle'] !== $this->_handle)
 			{
@@ -380,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);
 		}
 
@@ -396,7 +417,7 @@
 		// but OpenSSL isn't that dumb and we need to make the process
 		// portable, so ...
 		$data = (mcrypt_enc_get_modes_name($params['handle']) !== 'ECB')
-			? $params['iv'].mcrypt_generic($params['handle'], $data)
+			? $iv.mcrypt_generic($params['handle'], $data)
 			: mcrypt_generic($params['handle'], $data);
 
 		mcrypt_generic_deinit($params['handle']);
@@ -423,19 +444,17 @@
 		{
 			return FALSE;
 		}
-		elseif ( ! isset($params['iv']))
-		{
-			$params['iv'] = ($iv_size = openssl_cipher_iv_length($params['handle']))
-				? openssl_random_pseudo_bytes($iv_size)
-				: NULL;
-		}
+
+		$iv = ($iv_size = openssl_cipher_iv_length($params['handle']))
+			? openssl_random_pseudo_bytes($iv_size)
+			: NULL;
 
 		$data = openssl_encrypt(
 			$data,
 			$params['handle'],
 			$params['key'],
 			1, // DO NOT TOUCH!
-			$params['iv']
+			$iv
 		);
 
 		if ($data === FALSE)
@@ -443,7 +462,7 @@
 			return FALSE;
 		}
 
-		return $params['iv'].$data;
+		return $iv.$data;
 	}
 
 	// --------------------------------------------------------------------
@@ -470,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']);
@@ -499,12 +518,7 @@
 			$data = base64_decode($data);
 		}
 
-		if (isset($params['iv']) && strncmp($params['iv'], $data, $iv_size = strlen($params['iv'])) === 0)
-		{
-			$data = substr($data, $iv_size);
-		}
-
-		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);
 	}
@@ -524,30 +538,28 @@
 		{
 			return FALSE;
 		}
-		elseif ( ! isset($params['iv']))
+
+		// The greater-than-1 comparison is mostly a work-around for a bug,
+		// where 1 is returned for ARCFour instead of 0.
+		if (($iv_size = mcrypt_enc_get_iv_size($params['handle'])) > 1)
 		{
-			// The greater-than-1 comparison is mostly a work-around for a bug,
-			// where 1 is returned for ARCFour instead of 0.
-			if (($iv_size = mcrypt_enc_get_iv_size($params['handle'])) > 1)
+			if (mcrypt_enc_get_modes_name($params['handle']) !== 'ECB')
 			{
-				if (mcrypt_enc_get_modes_name($params['handle']) !== 'ECB')
-				{
-					$params['iv'] = substr($data, 0, $iv_size);
-					$data = substr($data, $iv_size);
-				}
-				else
-				{
-					// MCrypt is dumb and this is ignored, only size matters
-					$params['iv'] = str_repeat("\x0", $iv_size);
-				}
+				$iv = self::substr($data, 0, $iv_size);
+				$data = self::substr($data, $iv_size);
 			}
 			else
 			{
-				$params['iv'] = NULL;
+				// MCrypt is dumb and this is ignored, only size matters
+				$iv = str_repeat("\x0", $iv_size);
 			}
 		}
+		else
+		{
+			$iv = NULL;
+		}
 
-		if (mcrypt_generic_init($params['handle'], $params['key'], $params['iv']) < 0)
+		if (mcrypt_generic_init($params['handle'], $params['key'], $iv) < 0)
 		{
 			if ($params['handle'] !== $this->_handle)
 			{
@@ -561,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']);
@@ -584,17 +596,14 @@
 	 */
 	protected function _openssl_decrypt($data, $params)
 	{
-		if ( ! isset($params['iv']))
+		if ($iv_size = openssl_cipher_iv_length($params['handle']))
 		{
-			if ($iv_size = openssl_cipher_iv_length($params['handle']))
-			{
-				$params['iv'] = substr($data, 0, $iv_size);
-				$data = substr($data, $iv_size);
-			}
-			else
-			{
-				$params['iv'] = NULL;
-			}
+			$iv = self::substr($data, 0, $iv_size);
+			$data = self::substr($data, $iv_size);
+		}
+		else
+		{
+			$iv = NULL;
 		}
 
 		return empty($params['handle'])
@@ -604,7 +613,7 @@
 				$params['handle'],
 				$params['key'],
 				1, // DO NOT TOUCH!
-				$params['iv']
+				$iv
 			);
 	}
 
@@ -627,7 +636,7 @@
 					'mode' => $this->_mode,
 					'key' => NULL,
 					'base64' => TRUE,
-					'hmac_digest' => ($this->_mode !== 'gcm' ? 'sha512' : NULL),
+					'hmac_digest' => 'sha512',
 					'hmac_key' => NULL
 				)
 				: FALSE;
@@ -650,7 +659,7 @@
 			}
 		}
 
-		if ($params['mode'] === 'gcm' OR (isset($params['hmac']) && $params['hmac'] === FALSE))
+		if (isset($params['hmac']) && $params['hmac'] === FALSE)
 		{
 			$params['hmac_digest'] = $params['hmac_key'] = NULL;
 		}
@@ -679,7 +688,6 @@
 			'cipher' => $params['cipher'],
 			'mode' => $params['mode'],
 			'key' => $params['key'],
-			'iv' => isset($params['iv']) ? $params['iv'] : NULL,
 			'base64' => isset($params['raw_data']) ? ! $params['raw_data'] : FALSE,
 			'hmac_digest' => $params['hmac_digest'],
 			'hmac_key' => $params['hmac_key']
@@ -828,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);
 	}
 
 	// --------------------------------------------------------------------
@@ -864,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/system/libraries/Form_validation.php b/system/libraries/Form_validation.php
index dc5d17f..b640f1e 100644
--- a/system/libraries/Form_validation.php
+++ b/system/libraries/Form_validation.php
@@ -701,6 +701,12 @@
 			{
 				$callable = TRUE;
 			}
+			elseif (is_array($rule) && isset($rule[0], $rule[1]) && is_callable($rule[1]))
+			{
+				// We have a "named" callable, so save the name
+				$callable = $rule[0];
+				$rule = $rule[1];
+			}
 
 			// Strip the parameter (if exists) from the rule
 			// Rules can contain a parameter: max_length[5]
@@ -712,7 +718,7 @@
 			}
 
 			// Call the function that corresponds to the rule
-			if ($callback OR $callable)
+			if ($callback OR $callable !== FALSE)
 			{
 				if ($callback)
 				{
@@ -730,8 +736,14 @@
 				else
 				{
 					$result = is_array($rule)
-						? $rule[0]->{$rule[1]}($postdata, $param)
-						: $rule($postdata, $param);
+						? $rule[0]->{$rule[1]}($postdata)
+						: $rule($postdata);
+
+					// Is $callable set to a rule name?
+					if ($callable !== FALSE)
+					{
+						$rule = $callable;
+					}
 				}
 
 				// Re-assign the result to the master data array
@@ -791,27 +803,29 @@
 			// Did the rule test negatively? If so, grab the error.
 			if ($result === FALSE)
 			{
-				// Callable rules don't have named error messages
-				if ( ! is_callable($rule))
+				// Callable rules might not have named error messages
+				if ( ! is_string($rule))
 				{
-					// Check if a custom message is defined
-					if (isset($this->_field_data[$row['field']]['errors'][$rule]))
+					return;
+				}
+
+				// Check if a custom message is defined
+				if (isset($this->_field_data[$row['field']]['errors'][$rule]))
+				{
+					$line = $this->_field_data[$row['field']]['errors'][$rule];
+				}
+				elseif ( ! isset($this->_error_messages[$rule]))
+				{
+					if (FALSE === ($line = $this->CI->lang->line('form_validation_'.$rule))
+						// DEPRECATED support for non-prefixed keys
+						&& FALSE === ($line = $this->CI->lang->line($rule, FALSE)))
 					{
-						$line = $this->_field_data[$row['field']]['errors'][$rule];
+						$line = 'Unable to access an error message corresponding to your field name.';
 					}
-					elseif ( ! isset($this->_error_messages[$rule]))
-					{
-						if (FALSE === ($line = $this->CI->lang->line('form_validation_'.$rule))
-							// DEPRECATED support for non-prefixed keys
-							&& FALSE === ($line = $this->CI->lang->line($rule, FALSE)))
-						{
-							$line = 'Unable to access an error message corresponding to your field name.';
-						}
-					}
-					else
-					{
-						$line = $this->_error_messages[$rule];
-					}
+				}
+				else
+				{
+					$line = $this->_error_messages[$rule];
 				}
 
 				// Is the parameter we are inserting into the error message the name
@@ -1225,6 +1239,11 @@
 	 */
 	public function valid_email($str)
 	{
+		if (function_exists('idn_to_ascii') && $atpos = strpos($str, '@'))
+		{
+			$str = substr($str, 0, ++$atpos).idn_to_ascii(substr($str, $atpos));
+		}
+
 		return (bool) filter_var($str, FILTER_VALIDATE_EMAIL);
 	}
 
diff --git a/system/libraries/Image_lib.php b/system/libraries/Image_lib.php
index f1339b5..3975370 100644
--- a/system/libraries/Image_lib.php
+++ b/system/libraries/Image_lib.php
@@ -327,6 +327,13 @@
 	public $full_dst_path		= '';
 
 	/**
+	 * File permissions
+	 *
+	 * @var	int
+	 */
+	public $file_permissions = 0644;
+
+	/**
 	 * Name of function to create image
 	 *
 	 * @var string
@@ -734,7 +741,7 @@
 		{
 			if ($this->source_image !== $this->new_image && @copy($this->full_src_path, $this->full_dst_path))
 			{
-				@chmod($this->full_dst_path, 0666);
+				chmod($this->full_dst_path, $this->file_permissions);
 			}
 
 			return TRUE;
@@ -810,8 +817,7 @@
 		imagedestroy($dst_img);
 		imagedestroy($src_img);
 
-		// Set the file to 666
-		@chmod($this->full_dst_path, 0666);
+		chmod($this->full_dst_path, $this->file_permissions);
 
 		return TRUE;
 	}
@@ -880,8 +886,7 @@
 			return FALSE;
 		}
 
-		// Set the file to 666
-		@chmod($this->full_dst_path, 0666);
+		chmod($this->full_dst_path, $this->file_permissions);
 
 		return TRUE;
 	}
@@ -969,7 +974,7 @@
 		// we have to rename the temp file.
 		copy($this->dest_folder.'netpbm.tmp', $this->full_dst_path);
 		unlink($this->dest_folder.'netpbm.tmp');
-		@chmod($this->full_dst_path, 0666);
+		chmod($this->full_dst_path, $this->file_permissions);
 
 		return TRUE;
 	}
@@ -1013,8 +1018,7 @@
 		imagedestroy($dst_img);
 		imagedestroy($src_img);
 
-		// Set the file to 666
-		@chmod($this->full_dst_path, 0666);
+		chmod($this->full_dst_path, $this->file_permissions);
 
 		return TRUE;
 	}
@@ -1086,8 +1090,7 @@
 		// Kill the file handles
 		imagedestroy($src_img);
 
-		// Set the file to 666
-		@chmod($this->full_dst_path, 0666);
+		chmod($this->full_dst_path, $this->file_permissions);
 
 		return TRUE;
 	}
diff --git a/system/libraries/Pagination.php b/system/libraries/Pagination.php
index 5b9bfcb..b7df062 100644
--- a/system/libraries/Pagination.php
+++ b/system/libraries/Pagination.php
@@ -51,28 +51,21 @@
 	 *
 	 * @var	string
 	 */
-	protected $prefix		= '';
+	protected $prefix = '';
 
 	/**
 	 * Suffix
 	 *
 	 * @var	string
 	 */
-	protected $suffix		= '';
+	protected $suffix = '';
 
 	/**
 	 * Total number of items
 	 *
 	 * @var	int
 	 */
-	protected $total_rows		= 0;
-
-	/**
-	 * Items per page
-	 *
-	 * @var	int
-	 */
-	protected $per_page		= 10;
+	protected $total_rows = 0;
 
 	/**
 	 * Number of links to show
@@ -82,14 +75,21 @@
 	 *
 	 * @var	int
 	 */
-	protected $num_links		= 2;
+	protected $num_links = 2;
+
+	/**
+	 * Items per page
+	 *
+	 * @var	int
+	 */
+	public $per_page = 10;
 
 	/**
 	 * Current page
 	 *
 	 * @var	int
 	 */
-	protected $cur_page		= 0;
+	public $cur_page = 0;
 
 	/**
 	 * Use page numbers flag
@@ -98,84 +98,84 @@
 	 *
 	 * @var	bool
 	 */
-	protected $use_page_numbers	= FALSE;
+	protected $use_page_numbers = FALSE;
 
 	/**
 	 * First link
 	 *
 	 * @var	string
 	 */
-	protected $first_link		= '&lsaquo; First';
+	protected $first_link = '&lsaquo; First';
 
 	/**
 	 * Next link
 	 *
 	 * @var	string
 	 */
-	protected $next_link		= '&gt;';
+	protected $next_link = '&gt;';
 
 	/**
 	 * Previous link
 	 *
 	 * @var	string
 	 */
-	protected $prev_link		= '&lt;';
+	protected $prev_link = '&lt;';
 
 	/**
 	 * Last link
 	 *
 	 * @var	string
 	 */
-	protected $last_link		= 'Last &rsaquo;';
+	protected $last_link = 'Last &rsaquo;';
 
 	/**
 	 * URI Segment
 	 *
 	 * @var	int
 	 */
-	protected $uri_segment		= 0;
+	protected $uri_segment = 0;
 
 	/**
 	 * Full tag open
 	 *
 	 * @var	string
 	 */
-	protected $full_tag_open	= '';
+	protected $full_tag_open = '';
 
 	/**
 	 * Full tag close
 	 *
 	 * @var	string
 	 */
-	protected $full_tag_close	= '';
+	protected $full_tag_close = '';
 
 	/**
 	 * First tag open
 	 *
 	 * @var	string
 	 */
-	protected $first_tag_open	= '';
+	protected $first_tag_open = '';
 
 	/**
 	 * First tag close
 	 *
 	 * @var	string
 	 */
-	protected $first_tag_close	= '';
+	protected $first_tag_close = '';
 
 	/**
 	 * Last tag open
 	 *
 	 * @var	string
 	 */
-	protected $last_tag_open	= '';
+	protected $last_tag_open = '';
 
 	/**
 	 * Last tag close
 	 *
 	 * @var	string
 	 */
-	protected $last_tag_close	= '';
+	protected $last_tag_close = '';
 
 	/**
 	 * First URL
@@ -184,70 +184,70 @@
 	 *
 	 * @var	string
 	 */
-	protected $first_url		= '';
+	protected $first_url = '';
 
 	/**
 	 * Current tag open
 	 *
 	 * @var	string
 	 */
-	protected $cur_tag_open		= '<strong>';
+	protected $cur_tag_open = '<strong>';
 
 	/**
 	 * Current tag close
 	 *
 	 * @var	string
 	 */
-	protected $cur_tag_close	= '</strong>';
+	protected $cur_tag_close = '</strong>';
 
 	/**
 	 * Next tag open
 	 *
 	 * @var	string
 	 */
-	protected $next_tag_open	= '';
+	protected $next_tag_open = '';
 
 	/**
 	 * Next tag close
 	 *
 	 * @var	string
 	 */
-	protected $next_tag_close	= '';
+	protected $next_tag_close = '';
 
 	/**
 	 * Previous tag open
 	 *
 	 * @var	string
 	 */
-	protected $prev_tag_open	= '';
+	protected $prev_tag_open = '';
 
 	/**
 	 * Previous tag close
 	 *
 	 * @var	string
 	 */
-	protected $prev_tag_close	= '';
+	protected $prev_tag_close = '';
 
 	/**
 	 * Number tag open
 	 *
 	 * @var	string
 	 */
-	protected $num_tag_open		= '';
+	protected $num_tag_open = '';
 
 	/**
 	 * Number tag close
 	 *
 	 * @var	string
 	 */
-	protected $num_tag_close	= '';
+	protected $num_tag_close = '';
 
 	/**
 	 * Page query string flag
 	 *
 	 * @var	bool
 	 */
-	protected $page_query_string	= FALSE;
+	protected $page_query_string = FALSE;
 
 	/**
 	 * Query string segment
@@ -261,14 +261,14 @@
 	 *
 	 * @var	bool
 	 */
-	protected $display_pages	= TRUE;
+	protected $display_pages = TRUE;
 
 	/**
 	 * Attributes
 	 *
 	 * @var	string
 	 */
-	protected $_attributes		= '';
+	protected $_attributes = '';
 
 	/**
 	 * Link types
@@ -278,21 +278,21 @@
 	 * @see	CI_Pagination::_attr_rel()
 	 * @var	array
 	 */
-	protected $_link_types		= array();
+	protected $_link_types = array();
 
 	/**
 	 * Reuse query string flag
 	 *
 	 * @var	bool
 	 */
-	protected $reuse_query_string   = FALSE;
+	protected $reuse_query_string = FALSE;
 
 	/**
 	 * Data page attribute
 	 *
 	 * @var	string
 	 */
-	protected $data_page_attr	= 'data-ci-pagination-page';
+	protected $data_page_attr = 'data-ci-pagination-page';
 
 	/**
 	 * CI Singleton
@@ -393,9 +393,9 @@
 		// Check the user defined number of links.
 		$this->num_links = (int) $this->num_links;
 
-		if ($this->num_links < 1)
+		if ($this->num_links < 0)
 		{
-			show_error('Your number of links must be a positive number.');
+			show_error('Your number of links must be a non-negative number.');
 		}
 
 		// Keep any existing query string items.
@@ -533,7 +533,7 @@
 		$output = '';
 
 		// Render the "First" link.
-		if ($this->first_link !== FALSE && $this->cur_page > ($this->num_links + 1))
+		if ($this->first_link !== FALSE && $this->cur_page > ($this->num_links + 1 + ! $this->num_links))
 		{
 			// Take the general parameters, and squeeze this pagination-page attr in for JS frameworks.
 			$attributes = sprintf('%s %s="%d"', $this->_attributes, $this->data_page_attr, 1);
@@ -609,7 +609,7 @@
 		}
 
 		// Render the "Last" link
-		if ($this->last_link !== FALSE && ($this->cur_page + $this->num_links) < $num_pages)
+		if ($this->last_link !== FALSE && ($this->cur_page + $this->num_links + ! $this->num_links) < $num_pages)
 		{
 			$i = ($this->use_page_numbers) ? $num_pages : ($num_pages * $this->per_page) - $this->per_page;
 
diff --git a/system/libraries/Parser.php b/system/libraries/Parser.php
index d23a534..2c2fc73 100644
--- a/system/libraries/Parser.php
+++ b/system/libraries/Parser.php
@@ -128,13 +128,20 @@
 			return FALSE;
 		}
 
+		$replace = array();
 		foreach ($data as $key => $val)
 		{
-			$template = is_array($val)
+			$replace = array_merge(
+				$replace,
+				is_array($val)
 					? $this->_parse_pair($key, $val, $template)
-					: $template = $this->_parse_single($key, (string) $val, $template);
+					: $this->_parse_single($key, (string) $val, $template)
+			);
 		}
 
+		unset($data);
+		$template = strtr($template, $replace);
+
 		if ($return === FALSE)
 		{
 			$this->CI->output->append_output($template);
@@ -170,7 +177,7 @@
 	 */
 	protected function _parse_single($key, $val, $string)
 	{
-		return str_replace($this->l_delim.$key.$this->r_delim, (string) $val, $string);
+		return array($this->l_delim.$key.$this->r_delim => (string) $val);
 	}
 
 	// --------------------------------------------------------------------
@@ -187,50 +194,43 @@
 	 */
 	protected function _parse_pair($variable, $data, $string)
 	{
-		if (FALSE === ($matches = $this->_match_pair($string, $variable)))
-		{
-			return $string;
-		}
+		$replace = array();
+		preg_match_all(
+			'#'.preg_quote($this->l_delim.$variable.$this->r_delim).'(.+?)'.preg_quote($this->l_delim.'/'.$variable.$this->r_delim).'#s',
+			$string,
+			$matches,
+			PREG_SET_ORDER
+		);
 
-		$str = '';
-		$search = $replace = array();
 		foreach ($matches as $match)
 		{
 			$str = '';
 			foreach ($data as $row)
 			{
-				$temp = $match[1];
+				$temp = array();
 				foreach ($row as $key => $val)
 				{
-					$temp = is_array($val)
-						? $this->_parse_pair($key, $val, $temp)
-						: $this->_parse_single($key, $val, $temp);
+					if (is_array($val))
+					{
+						$pair = $this->_parse_pair($key, $val, $match[1]);
+						if ( ! empty($pair))
+						{
+							$temp = array_merge($temp, $pair);
+						}
+
+						continue;
+					}
+
+					$temp[$this->l_delim.$key.$this->r_delim] = $val;
 				}
 
-				$str .= $temp;
+				$str .= strtr($match[1], $temp);
 			}
 
-			$search[] = $match[0];
-			$replace[] = $str;
+			$replace[$match[0]] = $str;
 		}
 
-		return str_replace($search, $replace, $string);
-	}
-
-	// --------------------------------------------------------------------
-
-	/**
-	 * Matches a variable pair
-	 *
-	 * @param	string	$string
-	 * @param	string	$variable
-	 * @return	mixed
-	 */
-	protected function _match_pair($string, $variable)
-	{
-		return preg_match_all('|'.preg_quote($this->l_delim).$variable.preg_quote($this->r_delim).'(.+?)'.preg_quote($this->l_delim).'/'.$variable.preg_quote($this->r_delim).'|s',
-					$string, $match, PREG_SET_ORDER)
-			? $match : FALSE;
+		return $replace;
 	}
 
 }
diff --git a/system/libraries/Upload.php b/system/libraries/Upload.php
index 75fc062..49c69a3 100644
--- a/system/libraries/Upload.php
+++ b/system/libraries/Upload.php
@@ -327,23 +327,26 @@
 					$this->$key = $defaults[$key];
 				}
 			}
-
-			return $this;
+		
 		}
-
-		foreach ($config as $key => &$value)
+		else
 		{
-			if ($key[0] !== '_' && $reflection->hasProperty($key))
+			
+			foreach ($config as $key => &$value)
 			{
-				if ($reflection->hasMethod('set_'.$key))
+				if ($key[0] !== '_' && $reflection->hasProperty($key))
 				{
-					$this->{'set_'.$key}($value);
-				}
-				else
-				{
-					$this->$key = $value;
+					if ($reflection->hasMethod('set_'.$key))
+					{
+						$this->{'set_'.$key}($value);
+					}
+					else
+					{
+						$this->$key = $value;
+					}
 				}
 			}
+			
 		}
 
 		// if a file_name was provided in the config, use it instead of the user input
@@ -1155,28 +1158,14 @@
 	 */
 	protected function _prep_filename($filename)
 	{
-		if ($this->mod_mime_fix === FALSE OR $this->allowed_types === '*' OR strpos($filename, '.') === FALSE)
+		if ($this->mod_mime_fix === FALSE OR $this->allowed_types === '*' OR ($ext_pos = strrpos($filename, '.')) === FALSE)
 		{
 			return $filename;
 		}
 
-		$parts		= explode('.', $filename);
-		$ext		= array_pop($parts);
-		$filename	= array_shift($parts);
-
-		foreach ($parts as $part)
-		{
-			if ( ! in_array(strtolower($part), $this->allowed_types) OR ! isset($this->_mimes[strtolower($part)]))
-			{
-				$filename .= '.'.$part.'_';
-			}
-			else
-			{
-				$filename .= '.'.$part;
-			}
-		}
-
-		return $filename.'.'.$ext;
+		$ext = substr($filename, $ext_pos);
+		$filename = substr($filename, 0, $ext_pos);
+		return str_replace('.', '_', $filename).$ext;
 	}
 
 	// --------------------------------------------------------------------
diff --git a/system/libraries/Zip.php b/system/libraries/Zip.php
index ab30a90..62a84ae 100644
--- a/system/libraries/Zip.php
+++ b/system/libraries/Zip.php
@@ -405,7 +405,7 @@
 
 		flock($fp, LOCK_EX);
 
-		for ($written = 0, $data = $this->get_zip(), $length = strlen($data); $written < $length; $written += $result)
+		for ($result = $written = 0, $data = $this->get_zip(), $length = strlen($data); $written < $length; $written += $result)
 		{
 			if (($result = fwrite($fp, substr($data, $written))) === FALSE)
 			{
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/tests/codeigniter/libraries/Encrypt_test.php b/tests/codeigniter/libraries/Encrypt_test.php
index a08db8e..ced7633 100644
--- a/tests/codeigniter/libraries/Encrypt_test.php
+++ b/tests/codeigniter/libraries/Encrypt_test.php
@@ -1,15 +1,21 @@
 <?php
-
+/**
+ * @requires extension mcrypt
+ */
 class Encrypt_test extends CI_TestCase {
 
 	public function set_up()
 	{
+		if ( ! extension_loaded('mcrypt'))
+		{
+			return;
+		}
+
 		$this->encrypt = new Mock_Libraries_Encrypt();
 		$this->ci_instance_var('encrypt', $this->encrypt);
 
 		$this->ci_set_config('encryption_key', "Encryptin'glike@boss!");
 		$this->msg = 'My secret message';
-		$this->mcrypt = extension_loaded('mcrypt');
 	}
 
 	// --------------------------------------------------------------------
@@ -40,12 +46,6 @@
 
 	public function test_default_cipher()
 	{
-		if ( ! $this->mcrypt)
-		{
-			$this->markTestSkipped('MCrypt not available');
-			return;
-		}
-
 		$this->assertEquals('rijndael-256', $this->encrypt->get_cipher());
 	}
 
@@ -53,12 +53,6 @@
 
 	public function test_set_cipher()
 	{
-		if ( ! $this->mcrypt)
-		{
-			$this->markTestSkipped('MCrypt not available');
-			return;
-		}
-
 		$this->encrypt->set_cipher(MCRYPT_BLOWFISH);
 		$this->assertEquals('blowfish', $this->encrypt->get_cipher());
 	}
@@ -67,12 +61,6 @@
 
 	public function test_default_mode()
 	{
-		if ( ! $this->mcrypt)
-		{
-			$this->markTestSkipped('MCrypt not available');
-			return;
-		}
-
 		$this->assertEquals('cbc', $this->encrypt->get_mode());
 	}
 
@@ -80,12 +68,6 @@
 
 	public function test_set_mode()
 	{
-		if ( ! $this->mcrypt)
-		{
-			$this->markTestSkipped('MCrypt not available');
-			return;
-		}
-
 		$this->encrypt->set_mode(MCRYPT_MODE_CFB);
 		$this->assertEquals('cfb', $this->encrypt->get_mode());
 	}
diff --git a/tests/codeigniter/libraries/Encryption_test.php b/tests/codeigniter/libraries/Encryption_test.php
index 759d7cd..f457fe3 100644
--- a/tests/codeigniter/libraries/Encryption_test.php
+++ b/tests/codeigniter/libraries/Encryption_test.php
@@ -141,7 +141,6 @@
 
 		$this->assertTrue(is_array($this->encryption->__get_params($params)));
 
-		$params['iv'] = NULL;
 		$params['base64'] = TRUE;
 		$params['hmac_digest'] = 'sha512';
 
@@ -150,7 +149,6 @@
 			'cipher' => 'aes-128',
 			'mode' => 'cbc',
 			'key' => str_repeat("\x0", 16),
-			'iv' => str_repeat("\x0", 16),
 			'raw_data' => TRUE,
 			'hmac_key' => str_repeat("\x0", 16),
 			'hmac_digest' => 'sha256'
@@ -216,22 +214,17 @@
 		$this->assertFalse($this->encryption->encrypt($message, array('foo')));
 		$this->assertFalse($this->encryption->decrypt($message, array('foo')));
 
-		// Custom IV (we'll check it), no HMAC, binary output
+		// No HMAC, binary output
 		$params = array(
 			'cipher' => 'tripledes',
 			'mode' => 'cfb',
 			'key' => str_repeat("\x1", 16),
-			'iv' => str_repeat("\x2", 8),
 			'base64' => FALSE,
 			'hmac' => FALSE
 		);
 
 		$ciphertext = $this->encryption->encrypt($message, $params);
-		$this->assertEquals(0, strncmp($params['iv'], $ciphertext, 8));
 
-		// IV should be found in the cipher-text, no matter if it was supplied or not
-		$this->assertEquals($message, $this->encryption->decrypt($ciphertext, $params));
-		unset($params['iv']);
 		$this->assertEquals($message, $this->encryption->decrypt($ciphertext, $params));
 	}
 
diff --git a/tests/mocks/core/common.php b/tests/mocks/core/common.php
index 5c32ca5..2e8265b 100644
--- a/tests/mocks/core/common.php
+++ b/tests/mocks/core/common.php
@@ -32,7 +32,7 @@
 
 		if ( ! isset($config[$item]))
 		{
-			return FALSE;
+			return NULL;
 		}
 
 		return $config[$item];
diff --git a/user_guide_src/source/changelog.rst b/user_guide_src/source/changelog.rst
index 8492be2..0e49302 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
 
@@ -79,6 +80,7 @@
       - :func:`url_title()` will now trim extra dashes from beginning and end.
       - :func:`anchor_popup()` will now fill the *href* attribute with the URL and its JS code will return FALSE instead.
       - Added JS window name support to the :func:`anchor_popup()` function.
+      - Added support for menubar attribute to the :func:`anchor_popup()`.
       - Added support (auto-detection) for HTTP/1.1 response codes 303, 307 in :func:`redirect()`.
       - Changed :func:`redirect()` to choose the **refresh** method only on IIS servers, instead of all servers on Windows (when **auto** is used).
       - Changed :func:`anchor()`, :func:`anchor_popup()`, and :func:`redirect()` to support protocol-relative URLs (e.g. *//ellislab.com/codeigniter*).
@@ -135,6 +137,7 @@
       - Added *word_length* and *pool* options to allow customization of the generated word.
       - Added *colors* configuration to allow customization for the *background*, *border*, *text* and *grid* colors.
       - Added *filename* to the returned array elements.
+      - Updated to use `imagepng()` in case that `imagejpeg()` isn't available.
 
    -  :doc:`Text Helper <helpers/text_helper>` changes include:
 
@@ -184,6 +187,7 @@
       - Changed ``limit()`` to ignore NULL values instead of always casting to integer.
       - Changed ``offset()`` to ignore empty values instead of always casting to integer.
       - Methods ``insert_batch()`` and ``update_batch()`` now return an integer representing the number of rows affected by them.
+      - Methods ``where()``, ``or_where()``, ``having()`` and ``or_having()`` now convert trailing  ``=`` and ``<>``,  ``!=`` SQL operators to ``IS NULL`` and ``IS NOT NULL`` respectively when the supplied comparison value is ``NULL``.
 
    -  :doc:`Database Results <database/results>` changes include:
 
@@ -302,6 +306,7 @@
       -  Added a ``$reset`` parameter to method ``initialize()``.
       -  Removed method ``clean_file_name()`` and its usage in favor of :doc:`Security Library <libraries/security>`'s ``sanitize_filename()``.
       -  Removed method ``mimes_types()``.
+      -  Changed ``CI_Upload::_prep_filename()`` to simply replace all (but the last) dots in the filename with underscores, instead of suffixing them.
 
    -  :doc:`Calendar Library <libraries/calendar>` changes include:
 
@@ -328,6 +333,7 @@
       -  If property *maintain_ratio* is set to TRUE, ``image_reproportion()`` now doesn't need both width and height to be specified.
       -  Property *maintain_ratio* is now taken into account when resizing images using ImageMagick library.
       -  Added support for maintaining transparency for PNG images in method ``text_watermark()``.
+      -  Added a **file_permissions** setting.
 
    -  :doc:`Form Validation Library <libraries/form_validation>` changes include:
 
@@ -347,6 +353,7 @@
       -  Added rule **alpha_numeric_spaces**.
       -  Added support for custom error messages per field rule.
       -  Added support for callable rules when they are passed as an array.
+      -  Added support for non-ASCII domains in **valid_email** rule, depending on the Intl extension.
 
    -  :doc:`Caching Library <libraries/caching>` changes include:
 
@@ -375,6 +382,7 @@
       -  Added an optional parameter to ``print_debugger()`` to allow specifying which parts of the message should be printed ('headers', 'subject', 'body').
       -  Added SMTP keepalive option to avoid opening the connection for each ``send()`` call. Accessible as ``$smtp_keepalive``.
       -  Public method ``set_header()`` now filters the input by removing all "\\r" and "\\n" characters.
+      -  Added support for non-ASCII domains in ``valid_email()``, depending on the Intl extension.
 
    -  :doc:`Pagination Library <libraries/pagination>` changes include:
 
@@ -385,6 +393,7 @@
       -  Added support for language translations of the *first_link*, *next_link*, *prev_link* and *last_link* values.
       -  Added ``$config['reuse_query_string']`` to allow automatic repopulation of query string arguments, combined with normal URI segments.
       -  Removed the default ``&nbsp;`` from a number of the configuration variables.
+      -  Added support for ``$config['num_links'] = 0`` configuration.
 
    -  :doc:`Profiler Library <general/profiling>` changes include:
 
@@ -482,6 +491,8 @@
       -  Removed the third (`$php_error`) argument from function :func:`log_message()`.
       -  Changed internal function ``load_class()`` to accept a constructor parameter instead of (previously unused) class name prefix.
       -  Removed default parameter value of :func:`is_php()`.
+      -  Added a second argument ``$double_encode`` to :func:`html_escape()`.
+      -  Changed function ``config_item()`` to return NULL instead of FALSE when no value is found.
 
    -  :doc:`Output Library <libraries/output>` changes include:
 
@@ -499,10 +510,12 @@
 
    -  :doc:`Security Library <libraries/security>` changes include:
 
+      -  Added ``$config['csrf_regeneration']``, which makes CSRF token regeneration optional.
+      -  Added ``$config['csrf_exclude_uris']``, allowing for exclusion of URIs from the CSRF protection (regular expressions are supported).
       -  Added method ``strip_image_tags()``.
-      -  Added ``$config['csrf_regeneration']``, which makes token regeneration optional.
-      -  Added ``$config['csrf_exclude_uris']``, which allows you list URIs which will not have the CSRF validation methods run.
+      -  Added method ``get_random_bytes()`` and switched CSRF & XSS token generation to use it.
       -  Modified method ``sanitize_filename()`` to read a public ``$filename_bad_chars`` property for getting the invalid characters list.
+      -  Return status code of 403 instead of a 500 if CSRF protection is enabled but a token is missing from a request.
 
    -  :doc:`Language Library <libraries/language>` changes include:
 
@@ -522,15 +535,19 @@
       -  Changed method ``clean_string()`` to utilize ``mb_convert_encoding()`` if it is available.
       -  Renamed method ``_is_ascii()`` to ``is_ascii()`` and made it public.
 
+   -  Log Library changes include:
+
+      -  Added a ``$config['log_file_permissions']`` setting.
+      -  Changed the library constructor to try to create the **log_path** directory if it doesn't exist.
+
    -  Added `compatibility layers <general/compatibility_functions>` for:
 
       - `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.
    -  Added support for HTTP-Only cookies with new config option *cookie_httponly* (default FALSE).
    -  ``$config['time_reference']`` now supports all timezone strings supported by PHP.
    -  Fatal PHP errors are now also passed to ``_exception_handler()``, so they can be logged.
@@ -733,6 +750,8 @@
 -  Partially fixed a bug (#261) - UTF-8 class method ``clean_string()`` generating log messages and/or not producing the desired result due to an upstream bug in iconv.
 -  Fixed a bug where ``CI_Xmlrpcs::parseRequest()`` could fail if ``$HTTP_RAW_POST_DATA`` is not populated.
 -  Fixed a bug in :doc:`Zip Library <libraries/zip>` internal method ``_get_mod_time()`` where it was not parsing result returned by ``filemtime()``.
+-  Fixed a bug (#3161) - :doc:`Cache Library <libraries/cache>` methods `increment()`, `decrement()` didn't auto-create non-existent items when using redis and/or file storage.
+-  Fixed a bug (#3189) - :doc:`Parser Library <libraries/parser>` used double replacement on ``key->value`` pairs, exposing a potential template injection vulnerability.
 
 Version 2.2.0
 =============
@@ -789,7 +808,7 @@
 -  Fixed a bug (#227) - :doc:`Input Library <libraries/input>` allowed unconditional spoofing of HTTP clients' IP addresses through the *HTTP_CLIENT_IP* header.
 -  Fixed a bug (#907) - :doc:`Input Library <libraries/input>` ignored *HTTP_X_CLUSTER_CLIENT_IP* and *HTTP_X_CLIENT_IP* headers when checking for proxies.
 -  Fixed a bug (#940) - ``csrf_verify()`` used to set the CSRF cookie while processing a POST request with no actual POST data, which resulted in validating a request that should be considered invalid.
--  Fixed a bug (#499) - :doc:`Security Library <libraries/security>` where a CSRF cookie was created even if ``$config['csrf_protection']`` is set tot FALSE.
+-  Fixed a bug (#499) - :doc:`Security Library <libraries/security>` where a CSRF cookie was created even if ``$config['csrf_protection']`` is set to FALSE.
 -  Fixed a bug (#1715) - :doc:`Input Library <libraries/input>` triggered ``csrf_verify()`` on CLI requests.
 -  Fixed a bug (#751) - :doc:`Query Builder <database/query_builder>` didn't properly handle cached field escaping overrides.
 -  Fixed a bug (#2004) - :doc:`Query Builder <database/query_builder>` didn't properly merge cached calls with non-cache ones.
diff --git a/user_guide_src/source/general/ancillary_classes.rst b/user_guide_src/source/general/ancillary_classes.rst
index edb3a14..f9b6ba2 100644
--- a/user_guide_src/source/general/ancillary_classes.rst
+++ b/user_guide_src/source/general/ancillary_classes.rst
@@ -78,7 +78,7 @@
 
 		public function bar()
 		{
-			$this->CI->config_item('base_url');
+			$this->CI->config->item('base_url');
 		}
 
 	}
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/common_functions.rst b/user_guide_src/source/general/common_functions.rst
index 9c0a7cb..399a323 100644
--- a/user_guide_src/source/general/common_functions.rst
+++ b/user_guide_src/source/general/common_functions.rst
@@ -63,7 +63,7 @@
 .. function:: config_item($key)
 
 	:param	string	$key: Config item key
-	:returns:	Configuration key value or FALSE if not found
+	:returns:	Configuration key value or NULL if not found
 	:rtype:	mixed
 
 	The :doc:`Config Library <../libraries/config>` is the preferred way of
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/general/creating_libraries.rst b/user_guide_src/source/general/creating_libraries.rst
index a1e1b3e..0e3ae4c 100644
--- a/user_guide_src/source/general/creating_libraries.rst
+++ b/user_guide_src/source/general/creating_libraries.rst
@@ -170,7 +170,7 @@
 
 		public function bar()
 		{
-			echo $this->CI->config_item('base_url');
+			echo $this->CI->config->item('base_url');
 		}
 
 	}
diff --git a/user_guide_src/source/general/routing.rst b/user_guide_src/source/general/routing.rst
index 0b91d3f..766e0b2 100644
--- a/user_guide_src/source/general/routing.rst
+++ b/user_guide_src/source/general/routing.rst
@@ -116,15 +116,13 @@
 With regular expressions, you can also catch a segment containing a
 forward slash ('/'), which would usually represent the delimiter between
 multiple segments.
+
 For example, if a user accesses a password protected area of your web
 application and you wish to be able to redirect them back to the same
 page after they log in, you may find this example useful::
 
 	$route['login/(.+)'] = 'auth/login/$1';
 
-That will call the "auth" controller class and its ``login()`` method,
-passing everything contained in the URI after *login/* as a parameter.
-
 For those of you who don't know regular expressions and want to learn
 more about them, `regular-expressions.info <http://www.regular-expressions.info/>`
 might be a good starting point.
diff --git a/user_guide_src/source/helpers/captcha_helper.rst b/user_guide_src/source/helpers/captcha_helper.rst
index d83490b..1b74d08 100644
--- a/user_guide_src/source/helpers/captcha_helper.rst
+++ b/user_guide_src/source/helpers/captcha_helper.rst
@@ -54,7 +54,7 @@
    can draw randomly from.
 -  If you do not specify a path to a TRUE TYPE font, the native ugly GD
    font will be used.
--  The "captcha" folder must be writable (666, or 777)
+-  The "captcha" directory must be writable
 -  The **expiration** (in seconds) signifies how long an image will remain
    in the captcha folder before it will be deleted. The default is two
    hours.
diff --git a/user_guide_src/source/helpers/file_helper.rst b/user_guide_src/source/helpers/file_helper.rst
index 59cabcc..013b583 100644
--- a/user_guide_src/source/helpers/file_helper.rst
+++ b/user_guide_src/source/helpers/file_helper.rst
@@ -80,8 +80,8 @@
 	for mode options.
 
 	.. note: In order for this function to write data to a file, its permissions must
-		be set such that it is writable (666, 777, etc.). If the file does not
-		already exist, the directory containing it must be writable.
+		be set such that it is writable. If the file does not already exist,
+		then the directory containing it must be writable.
 
 	.. note:: The path is relative to your main site index.php file, NOT your
 		controller or view files. CodeIgniter uses a front controller so paths
diff --git a/user_guide_src/source/installation/upgrade_300.rst b/user_guide_src/source/installation/upgrade_300.rst
index 6915faf..81340e6 100644
--- a/user_guide_src/source/installation/upgrade_300.rst
+++ b/user_guide_src/source/installation/upgrade_300.rst
@@ -158,6 +158,10 @@
 
 Many methods and functions now return NULL instead of FALSE when the required items don't exist:
 
+ - :doc:`Common functions <../general/common_functions>`
+
+   - config_item()
+
  - :doc:`Config Class <../libraries/config>`
 
    - config->item()
diff --git a/user_guide_src/source/libraries/encryption.rst b/user_guide_src/source/libraries/encryption.rst
index a4415f5..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.
 
@@ -84,14 +84,19 @@
 key security so you may want to think carefully before using it for
 anything that requires high security, like storing credit card numbers.
 
-Your encryption key should be as long as the encyption algorithm in use
-allows. For AES-128, that's 128 bits or 16 bytes (charcters) long. The
-key should be as random as possible and it should **not** be a simple
-text string.
-
+Your encryption key **must** be as long as the encyption algorithm in use
+allows. For AES-128, that's 128 bits or 16 bytes (charcters) long.
 You will find a table below that shows the supported key lengths of
 different ciphers.
 
+The key should be as random as possible and it **must not** be a regular
+text string, nor the output of a hashing function, etc. In order to create
+a proper key, you must use the Encryption library's ``create_key()`` method
+::
+
+	// $key will be assigned a 16-byte (128-bit) random key
+	$key = $this->encryption->create_key(16);
+
 The key can be either stored in your *application/config/config.php*, or
 you can design your own storage mechanism and pass the key dynamically
 when encrypting/decrypting.
@@ -168,9 +173,9 @@
 ============== ========= ============================== =========================================
 Cipher name    Driver    Key lengths (bits / bytes)     Supported modes
 ============== ========= ============================== =========================================
-AES-128        OpenSSL   128 / 16                       CBC, CTR, CFB, CFB8, OFB, ECB, GCM, XTS
-AES-192        OpenSSL   192 / 24                       CBC, CTR, CFB, CFB8, OFB, ECB, GCM, XTS
-AES-256        OpenSSL   256 / 32                       CBC, CTR, CFB, CFB8, OFB, ECB, GCM, XTS
+AES-128        OpenSSL   128 / 16                       CBC, CTR, CFB, CFB8, OFB, ECB, XTS
+AES-192        OpenSSL   192 / 24                       CBC, CTR, CFB, CFB8, OFB, ECB, XTS
+AES-256        OpenSSL   256 / 32                       CBC, CTR, CFB, CFB8, OFB, ECB, XTS
 Rijndael-128   MCrypt    128 / 16, 192 / 24, 256 / 32   CBC, CTR, CFB, CFB8, OFB, OFB8, ECB
 Rijndael-192   MCrypt    128 / 16, 192 / 24, 256 / 32   CBC, CTR, CFB, CFB8, OFB, OFB8, ECB
 Rijndael-256   MCrypt    128 / 16, 192 / 24, 256 / 32   CBC, CTR, CFB, CFB8, OFB, OFB8, ECB
@@ -234,7 +239,6 @@
 OFB         ofb                MCrypt, OpenSSL   N/A
 OFB8        ofb8               MCrypt            Same as OFB, but operates in 8-bit mode (not recommended).
 ECB         ecb                MCrypt, OpenSSL   Ignores IV (not recommended).
-GCM         gcm                OpenSSL           Provides authentication and therefore doesn't need a HMAC.
 XTS         xts                OpenSSL           Usually used for encrypting random access data such as RAM or hard-disk storage.
 Stream      stream             MCrypt, OpenSSL   This is not actually a mode, it just says that a stream cipher is being used. Required because of the general cipher+mode initialization process.
 =========== ================== ================= ===================================================================================================================================================
@@ -246,10 +250,9 @@
 longer than the original, plain-text string (depending on the cipher).
 
 This is influenced by the cipher algorithm itself, the IV prepended to the
-cipher-text and (unless you are using GCM mode) the HMAC authentication
-message that is also prepended. Furthermore, the encrypted message is also
-Base64-encoded so that it is safe for storage and transmission, regardless
-of a possible character set in use.
+cipher-text and the HMAC authentication message that is also prepended.
+Furthermore, the encrypted message is also Base64-encoded so that it is safe
+for storage and transmission, regardless of a possible character set in use.
 
 Keep this information in mind when selecting your data storage mechanism.
 Cookies, for example, can only hold 4K of information.
@@ -425,9 +428,6 @@
 cipher        N/A             Yes                           Encryption algorithm (see :ref:`ciphers-and-modes`).
 mode          N/A             Yes                           Encryption mode (see :ref:`encryption-modes`).
 key           N/A             Yes                           Encryption key.
-iv            N/A             No                            Initialization vector (IV).
-                                                            If not provided it will be automatically generated
-                                                            during encryption and looked for during decryption.
 hmac          TRUE            No                            Whether to use a HMAC.
                                                             Boolean. If set to FALSE, then *hmac_digest* and
                                                             *hmac_key* will be ignored.
@@ -444,9 +444,6 @@
 	value is incorrect. This includes *hmac_key*, unless *hmac*
 	is set to FALSE.
 
-.. note:: If GCM mode is used, *hmac* will always be FALSE. This is
-	because GCM mode itself provides authentication.
-
 .. _digests:
 
 Supported HMAC authentication algorithms
diff --git a/user_guide_src/source/libraries/form_validation.rst b/user_guide_src/source/libraries/form_validation.rst
index 2ae56d2..aae9e3b 100644
--- a/user_guide_src/source/libraries/form_validation.rst
+++ b/user_guide_src/source/libraries/form_validation.rst
@@ -505,11 +505,40 @@
 			'required',
 			function($value)
 			{
-				// Check $value and return TRUE/FALSE
+				// Check $value
 			}
 		)
 	);
 
+Of course, since a Callable rule by itself is not a string, it isn't
+a rule name either. That is a problem when you want to set error messages
+for them. In order to get around that problem, you can put such rules as
+the second element of an array, with the first one being the rule name::
+
+	$this->form_validation->set_rules(
+		'username', 'Username',
+		array(
+			'required',
+			array('username_callable', array($this->users_model, 'valid_username'))
+		)
+	);
+
+Anonymous function (PHP 5.3+) version::
+
+	$this->form_validation->set_rules(
+		'username', 'Username',
+		array(
+			'required',
+			array(
+				'username_callable',
+				function($str)
+				{
+					// Check validity of $str and return TRUE or FALSE
+				}
+			)
+		)
+	);
+
 .. _setting-error-messages:
 
 Setting Error Messages
diff --git a/user_guide_src/source/libraries/ftp.rst b/user_guide_src/source/libraries/ftp.rst
index dd94404..4be1a6e 100644
--- a/user_guide_src/source/libraries/ftp.rst
+++ b/user_guide_src/source/libraries/ftp.rst
@@ -270,7 +270,7 @@
 		::
 
 			// Creates a folder named "bar"
-			$this->ftp->mkdir('/public_html/foo/bar/', DIR_WRITE_MODE);
+			$this->ftp->mkdir('/public_html/foo/bar/', 0755);
 
 	.. method:: chmod($path, $perm)
 
@@ -282,8 +282,8 @@
 		Permits you to set file permissions. Supply the path to the file or
 		directory you wish to alter permissions on::
 
-			// Chmod "bar" to 777
-			$this->ftp->chmod('/public_html/foo/bar/', DIR_WRITE_MODE);
+			// Chmod "bar" to 755
+			$this->ftp->chmod('/public_html/foo/bar/', 0755);
 
 	.. method:: changedir($path[, $suppress_debug = FALSE])
 
diff --git a/user_guide_src/source/libraries/image_lib.rst b/user_guide_src/source/libraries/image_lib.rst
index 16acf09..a52cf3e 100644
--- a/user_guide_src/source/libraries/image_lib.rst
+++ b/user_guide_src/source/libraries/image_lib.rst
@@ -137,6 +137,8 @@
                                                                                 image can be shown at a time, and it can't be positioned on the page. It
                                                                                 simply outputs the raw image dynamically to your browser, along with
                                                                                 image headers.
+**file_permissions**    0644                    (integer)                       File system permissions to apply on the resulting image file,               R, C, X, W
+                                                                                writing it to the disk. WARNING: Use octal integer notation!
 **quality**             90%                     1 - 100%                        Sets the quality of the image. The higher the quality the larger the        R, C, X, W
                                                                                 file size.
 **new_image**           None                    None                            Sets the destination image name/path. You'll use this preference when       R, C, X, W
diff --git a/user_guide_src/source/libraries/output.rst b/user_guide_src/source/libraries/output.rst
index e49ea53..218ec58 100644
--- a/user_guide_src/source/libraries/output.rst
+++ b/user_guide_src/source/libraries/output.rst
@@ -205,4 +205,28 @@
 
 		Caches the current page for the specified amount of seconds.
 
-		For more information, please see the :doc:`caching documentation <../general/caching>`.
\ No newline at end of file
+		For more information, please see the :doc:`caching documentation <../general/caching>`.
+
+	.. method:: _display([$output = ''])
+
+		:param	string	$output: Output data override
+		:returns:	void
+		:rtype:	void
+
+		Sends finalized output data to the browser along with any server headers. It also stops benchmark
+		timers.
+
+		.. note:: This method is called automatically at the end of script execution, you won't need to 
+			call it manually unless you are aborting script execution using ``exit()`` or ``die()`` in your code.
+		
+		Example::
+			$response = array('status' => 'OK');
+
+			$this->output
+				->set_status_header(200)
+				->set_content_type('application/json', 'utf-8')
+				->set_output(json_encode($response, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES))
+				->_display();
+			exit;
+
+		.. note:: Calling this method manually without aborting script execution will result in duplicated output.
\ No newline at end of file
diff --git a/user_guide_src/source/libraries/security.rst b/user_guide_src/source/libraries/security.rst
index fb875a0..0c51e34 100644
--- a/user_guide_src/source/libraries/security.rst
+++ b/user_guide_src/source/libraries/security.rst
@@ -97,6 +97,13 @@
 
 	$config['csrf_exclude_uris'] = array('api/person/add');
 
+Regular expressions are also supported (case-insensitive)::
+
+	$config['csrf_exclude_uris'] = array(
+		'api/record/[0-9]+',
+		'api/title/[a-z]+'
+	);
+
 ***************
 Class Reference
 ***************
@@ -156,4 +163,19 @@
 		This method acts a lot like PHP's own native ``html_entity_decode()`` function in ENT_COMPAT mode, only
 		it tries to detect HTML entities that don't end in a semicolon because some browsers allow that.
 
-		If the ``$charset`` parameter is left empty, then your configured ``$config['charset']`` value will be used.
\ No newline at end of file
+		If the ``$charset`` parameter is left empty, then your configured ``$config['charset']`` value will be used.
+
+	.. method:: get_random_bytes($length)
+
+		:param	int	$length: Output length
+		:returns:	A binary stream of random bytes or FALSE on failure
+		:rtype:	string
+
+		A convenience method for getting proper random bytes via ``mcrypt_create_iv()``,
+		``/dev/urandom`` or ``openssl_random_pseudo_bytes()`` (in that order), if one
+		of them is available.
+
+		Used for generating CSRF and XSS tokens.
+
+		.. note:: The output is NOT guaranteed to be cryptographically secure,
+			just the best attempt at that.
\ No newline at end of file
diff --git a/user_guide_src/source/libraries/table.rst b/user_guide_src/source/libraries/table.rst
index 9d95edd..bb001e8 100644
--- a/user_guide_src/source/libraries/table.rst
+++ b/user_guide_src/source/libraries/table.rst
@@ -95,24 +95,30 @@
 specify the design of your layout. Here is the template prototype::
 
 	$template = array(
-		'table_open'          => '<table border="0" cellpadding="4" cellspacing="0">',
+		'table_open'		=> '<table border="0" cellpadding="4" cellspacing="0">',
 
-		'heading_row_start'   => '<tr>',
-		'heading_row_end'     => '</tr>',
-		'heading_cell_start'  => '<th>',
-		'heading_cell_end'    => '</th>',
+		'thead_open'		=> '<thead>',
+		'thead_close'		=> '</thead>',
 
-		'row_start'           => '<tr>',
-		'row_end'             => '</tr>',
-		'cell_start'          => '<td>',
-		'cell_end'            => '</td>',
+		'heading_row_start'	=> '<tr>',
+		'heading_row_end'	=> '</tr>',
+		'heading_cell_start'	=> '<th>',
+		'heading_cell_end'	=> '</th>',
 
-		'row_alt_start'       => '<tr>',
-		'row_alt_end'         => '</tr>',
-		'cell_alt_start'      => '<td>',
-		'cell_alt_end'        => '</td>',
+		'tbody_open'		=> '<tbody>',
+		'tbody_close'		=> '</tbody>',
 
-		'table_close'         => '</table>'
+		'row_start'		=> '<tr>',
+		'row_end'		=> '</tr>',
+		'cell_start'		=> '<td>',
+		'cell_end'		=> '</td>',
+
+		'row_alt_start'		=> '<tr>',
+		'row_alt_end'		=> '</tr>',
+		'cell_alt_start'	=> '<td>',
+		'cell_alt_end'		=> '</td>',
+
+		'table_close'		=> '</table>'
 	);
 
 	$this->table->set_template($template);
@@ -288,4 +294,4 @@
 			$this->table->add_row('Mary', 'Monday', 'Air');
 			$this->table->add_row('John', 'Saturday', 'Overnight');
 
-			echo $this->table->generate();
\ No newline at end of file
+			echo $this->table->generate();
diff --git a/user_guide_src/source/libraries/zip.rst b/user_guide_src/source/libraries/zip.rst
index 5ff7d07..4ca1408 100644
--- a/user_guide_src/source/libraries/zip.rst
+++ b/user_guide_src/source/libraries/zip.rst
@@ -173,7 +173,7 @@
 		:rtype:	bool
 
 		Writes the Zip-encoded file to a directory on your server. Submit a valid server path ending in the file name.
-		Make sure the directory is writable (660 or 666 is usually OK). Example::
+		Make sure the directory is writable (755 is usually OK). Example::
 
 			$this->zip->archive('/path/to/folder/myarchive.zip'); // Creates a file named myarchive.zip