Merge branch 'develop' of github.com:EllisLab/CodeIgniter into feature/db_qb_aliasing
diff --git a/composer.json b/composer.json
index dc098ac..7d60020 100644
--- a/composer.json
+++ b/composer.json
@@ -3,6 +3,6 @@
         "mikey179/vfsStream": "*"
     },
     "require-dev": {
-		"EHER/PHPUnit": "*"
+		"phpunit/phpunit": "*"
 	}
 }
\ No newline at end of file
diff --git a/system/core/Common.php b/system/core/Common.php
index 341402c..2dd31d3 100644
--- a/system/core/Common.php
+++ b/system/core/Common.php
@@ -330,6 +330,24 @@
 
 // ------------------------------------------------------------------------
 
+if ( ! function_exists('is_https'))
+{
+	/**
+	 * Is HTTPS?
+	 *
+	 * Determines if the application is accessed via an encrypted
+	 * (HTTPS) connection.
+	 *
+	 * @return	bool
+	 */
+	function is_https()
+	{
+		return ( ! empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) !== 'off');
+	}
+}
+
+// ------------------------------------------------------------------------
+
 if ( ! function_exists('show_error'))
 {
 	/**
diff --git a/system/core/Config.php b/system/core/Config.php
index 8e4f998..e78128c 100644
--- a/system/core/Config.php
+++ b/system/core/Config.php
@@ -75,7 +75,7 @@
 		{
 			if (isset($_SERVER['HTTP_HOST']))
 			{
-				$base_url = ( ! empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) !== 'off') ? 'https' : 'http';
+				$base_url = is_https() ? 'https' : 'http';
 				$base_url .= '://'.$_SERVER['HTTP_HOST']
 					.str_replace(basename($_SERVER['SCRIPT_NAME']), '', $_SERVER['SCRIPT_NAME']);
 			}
diff --git a/system/core/Input.php b/system/core/Input.php
index 82482f2..ec935d5 100644
--- a/system/core/Input.php
+++ b/system/core/Input.php
@@ -390,31 +390,32 @@
 					}
 
 					// Convert the REMOTE_ADDR IP address to binary, if needed
-					if ( ! isset($ip, $convert_func))
+					if ( ! isset($ip, $sprintf))
 					{
 						if ($separator === ':')
 						{
 							// Make sure we're have the "full" IPv6 format
-							$ip = str_replace('::', str_repeat(':', 9 - substr_count($this->ip_address, ':')), $this->ip_address);
-							$convert_func = is_php('5.3')
-								? function ($value)
-									{
-										return str_pad(base_convert($value, 16, 2), 16, '0', STR_PAD_LEFT);
-									}
-								: create_function('$value', 'return str_pad(base_convert($value, 16, 2), 16, "0", STR_PAD_LEFT);');
+							$ip = explode(':',
+								str_replace('::',
+									str_repeat(':', 9 - substr_count($this->ip_address, ':')),
+									$this->ip_address
+								)
+							);
+
+							for ($i = 0; $i < 8; $i++)
+							{
+								$ip[$i] = intval($ip[$i], 16);
+							}
+
+							$sprintf = '%016b%016b%016b%016b%016b%016b%016b%016b';
 						}
 						else
 						{
-							$ip = $this->ip_address;
-							$convert_func = is_php('5.3')
-								? function ($value)
-									{
-										return str_pad(decbin($value), 8, '0', STR_PAD_LEFT);
-									}
-								: create_function('$value', 'return str_pad(decbin($value), 8, "0", STR_PAD_LEFT);');
+							$ip = explode('.', $this->ip_address);
+							$sprintf = '%08b%08b%08b%08b';
 						}
 
-						$ip = implode(array_map($convert_func, explode($separator, $ip)));
+						$ip = vsprintf($sprintf, $ip);
 					}
 
 					// Split the netmask length off the network address
@@ -423,12 +424,19 @@
 					// Again, an IPv6 address is most likely in a compressed form
 					if ($separator === ':')
 					{
-						$netaddr = str_replace('::', str_repeat(':', 9 - substr_count($netaddr, ':')), $netaddr);
+						$netaddr = explode(':', str_replace('::', str_repeat(':', 9 - substr_count($netaddr, ':')), $netaddr));
+						for ($i = 0; $i < 8; $i++)
+						{
+							$netaddr[$i] = intval($netaddr[$i], 16);
+						}
+					}
+					else
+					{
+						$netaddr = explode('.', $netaddr);
 					}
 
-					// Convert to a binary form and finally compare
-					$netaddr = implode(array_map($convert_func, explode($separator, $netaddr)));
-					if (strncmp($ip, $netaddr, $masklen) === 0)
+					// Convert to binary and finally compare
+					if (strncmp($ip, vsprintf($sprintf, $netaddr), $masklen) === 0)
 					{
 						$this->ip_address = $spoof;
 						break;
diff --git a/system/core/Security.php b/system/core/Security.php
index b22d2cf..2fbc5b3 100644
--- a/system/core/Security.php
+++ b/system/core/Security.php
@@ -198,7 +198,7 @@
 		$expire = time() + $this->_csrf_expire;
 		$secure_cookie = (bool) config_item('cookie_secure');
 
-		if ($secure_cookie && (empty($_SERVER['HTTPS']) OR strtolower($_SERVER['HTTPS']) === 'off'))
+		if ($secure_cookie && ! is_https())
 		{
 			return FALSE;
 		}
diff --git a/system/database/drivers/mysqli/mysqli_driver.php b/system/database/drivers/mysqli/mysqli_driver.php
index 0a454da..91ab13a 100644
--- a/system/database/drivers/mysqli/mysqli_driver.php
+++ b/system/database/drivers/mysqli/mysqli_driver.php
@@ -69,7 +69,7 @@
 			? 'p:'.$this->hostname : $this->hostname;
 		$port = empty($this->port) ? NULL : $this->port;
 		$client_flags = ($this->compress === TRUE) ? MYSQLI_CLIENT_COMPRESS : 0;
-		$mysqli = new mysqli();
+		$mysqli = mysqli_init();
 
 		return @$mysqli->real_connect($hostname, $this->username, $this->password, $this->database, $port, NULL, $client_flags)
 			? $mysqli : FALSE;
diff --git a/system/helpers/url_helper.php b/system/helpers/url_helper.php
index b1f5ecc..de5bdec 100644
--- a/system/helpers/url_helper.php
+++ b/system/helpers/url_helper.php
@@ -388,40 +388,43 @@
 
 			for ($i = 0, $c = count($matches[0]); $i < $c; $i++)
 			{
-				if (preg_match('|\.$|', $matches[6][$i]))
+				if (preg_match('/(\.|\,)$/i', $matches[6][$i], $m))
 				{
-					$period = '.';
+					$punct = $m[1];
 					$matches[6][$i] = substr($matches[6][$i], 0, -1);
 				}
 				else
 				{
-					$period = '';
+					$punct = '';
 				}
 
 				$str = str_replace($matches[0][$i],
 							$matches[1][$i].'<a href="http'.$matches[4][$i].'://'
 								.$matches[5][$i].$matches[6][$i].'"'.$pop.'>http'
 								.$matches[4][$i].'://'.$matches[5][$i]
-								.$matches[6][$i].'</a>'.$period,
+								.$matches[6][$i].'</a>'.$punct,
 							$str);
 			}
 		}
 
-		if ($type !== 'url' && preg_match_all('/([a-zA-Z0-9_\.\-\+]+)@([a-zA-Z0-9\-]+)\.([a-zA-Z0-9\-\.]*)/i', $str, $matches))
+		if ($type !== 'url' && preg_match_all('/([a-zA-Z0-9_\.\-\+]+)@([a-zA-Z0-9\-]+)\.([a-zA-Z0-9\-\.]+)/i', $str, $matches))
 		{
 			for ($i = 0, $c = count($matches); $i < $c; $i++)
 			{
-				if (preg_match('|\.$|', $matches[3][$i]))
+				if (preg_match('/(\.|\,)$/i', $matches[3][$i], $m))
 				{
-					$period = '.';
+					$punct = $m[1];
 					$matches[3][$i] = substr($matches[3][$i], 0, -1);
 				}
 				else
 				{
-					$period = '';
+					$punct = '';
 				}
 
-				$str = str_replace($matches[0][$i], safe_mailto($matches[1][$i].'@'.$matches[2][$i].'.'.$matches[3][$i]).$period, $str);
+				if (filter_var(($m = $matches[1][$i].'@'.$matches[2][$i].'.'.$matches[3][$i]), FILTER_VALIDATE_EMAIL) !== FALSE)
+				{
+					$str = str_replace($matches[0][$i], safe_mailto($m).$punct, $str);
+				}
 			}
 		}
 
diff --git a/system/libraries/Email.php b/system/libraries/Email.php
index fa1d5e9..c1130e9 100644
--- a/system/libraries/Email.php
+++ b/system/libraries/Email.php
@@ -98,7 +98,7 @@
 	 */
 	public function __construct($config = array())
 	{
-		$this->charset = strtoupper(config_item('charset'));
+		$this->charset = config_item('charset');
 
 		if (count($config) > 0)
 		{
@@ -110,6 +110,8 @@
 			$this->_safe_mode = (bool) @ini_get('safe_mode');
 		}
 
+		$this->charset = strtoupper($this->charset);
+
 		log_message('debug', 'Email Class Initialized');
 	}
 
@@ -186,11 +188,11 @@
 	/**
 	 * Set FROM
 	 *
-	 * @param	string
-	 * @param	string
+	 * @param	string	From
+	 * @param	string	Return-Path
 	 * @return	object
 	 */
-	public function from($from, $name = '', $return_path = '')
+	public function from($from, $name = '', $return_path = NULL)
 	{
 		if (preg_match('/\<(.*)\>/', $from, $match))
 		{
@@ -217,16 +219,13 @@
 			}
 			else
 			{
-				$name = $this->_prep_q_encoding($name, TRUE);
+				$name = $this->_prep_q_encoding($name);
 			}
 		}
 
 		$this->set_header('From', $name.' <'.$from.'>');
 
-		if( ! $return_path)
-		{
-			$return_path = $from;
-		}
+		isset($return_path) OR $return_path = $from;
 		$this->set_header('Return-Path', '<'.$return_path.'>');
 
 		return $this;
@@ -292,16 +291,7 @@
 			$this->set_header('To', implode(', ', $to));
 		}
 
-		switch ($this->_get_protocol())
-		{
-			case 'smtp':
-				$this->_recipients = $to;
-			break;
-			case 'sendmail':
-			case 'mail':
-				$this->_recipients = implode(', ', $to);
-			break;
-		}
+		$this->_recipients = $to;
 
 		return $this;
 	}
@@ -752,8 +742,8 @@
 	/**
 	 * Build alternative plain text message
 	 *
-	 * This public function provides the raw message for use
-	 * in plain-text headers of HTML-formatted emails.
+	 * Provides the raw message for use in plain-text headers of
+	 * HTML-formatted emails.
 	 * If the user hasn't specified his own alternative message
 	 * it creates one by stripping the HTML
 	 *
@@ -761,9 +751,11 @@
 	 */
 	protected function _get_alt_message()
 	{
-		if ($this->alt_message !== '')
+		if ( ! empty($this->alt_message))
 		{
-			return $this->word_wrap($this->alt_message, '76');
+			return ($this->wordwrap)
+				? $this->word_wrap($this->alt_message, 76)
+				: $this->alt_message;
 		}
 
 		$body = preg_match('/\<body.*?\>(.*)\<\/body\>/si', $this->_body, $match) ? $match[1] : $this->_body;
@@ -774,7 +766,9 @@
 			$body = str_replace(str_repeat("\n", $i), "\n\n", $body);
 		}
 
-		return $this->word_wrap($body, 76);
+		return ($this->wordwrap)
+			? $this->word_wrap($body, 76)
+			: $body;
 	}
 
 	// --------------------------------------------------------------------
@@ -783,15 +777,15 @@
 	 * Word Wrap
 	 *
 	 * @param	string
-	 * @param	int
+	 * @param	int	line-length limit
 	 * @return	string
 	 */
-	public function word_wrap($str, $charlim = '')
+	public function word_wrap($str, $charlim = NULL)
 	{
-		// Se the character limit
-		if ($charlim === '')
+		// Set the character limit, if not already present
+		if (empty($charlim))
 		{
-			$charlim = ($this->wrapchars === '') ? 76 : $this->wrapchars;
+			$charlim = empty($this->wrapchars) ? 76 : $this->wrapchars;
 		}
 
 		// Reduce multiple spaces
@@ -1105,6 +1099,10 @@
 	 */
 	protected function _prep_quoted_printable($str)
 	{
+		// We are intentionally wrapping so mail servers will encode characters
+		// properly and MUAs will behave, so {unwrap} must go!
+		$str = str_replace(array('{unwrap}', '{/unwrap}'), '', $str);
+
 		// RFC 2045 specifies CRLF as "\r\n".
 		// However, many developers choose to override that and violate
 		// the RFC rules due to (apparently) a bug in MS Exchange,
@@ -1130,10 +1128,6 @@
 			$str = str_replace(array("\r\n", "\r"), "\n", $str);
 		}
 
-		// We are intentionally wrapping so mail servers will encode characters
-		// properly and MUAs will behave, so {unwrap} must go!
-		$str = str_replace(array('{unwrap}', '{/unwrap}'), '', $str);
-
 		$escape = '=';
 		$output = '';
 
@@ -1186,66 +1180,75 @@
 	/**
 	 * Prep Q Encoding
 	 *
-	 * Performs "Q Encoding" on a string for use in email headers.  It's related
-	 * but not identical to quoted-printable, so it has its own method
+	 * Performs "Q Encoding" on a string for use in email headers.
+	 * It's related but not identical to quoted-printable, so it has its
+	 * own method.
 	 *
 	 * @param	string
-	 * @param	bool	set to TRUE for processing From: headers
 	 * @return	string
 	 */
-	protected function _prep_q_encoding($str, $from = FALSE)
+	protected function _prep_q_encoding($str)
 	{
-		$str = str_replace(array("\r", "\n"), array('', ''), $str);
+		$str = str_replace(array("\r", "\n"), '', $str);
 
-		// Line length must not exceed 76 characters, so we adjust for
-		// a space, 7 extra characters =??Q??=, and the charset that we will add to each line
-		$limit = 75 - 7 - strlen($this->charset);
-
-		// these special characters must be converted too
-		$convert = array('_', '=', '?');
-
-		if ($from === TRUE)
+		if ($this->charset === 'UTF-8')
 		{
-			$convert[] = ',';
-			$convert[] = ';';
+			if (MB_ENABLED === TRUE)
+			{
+				return mb_encode_mimeheader($str, $this->charset, 'Q', $this->crlf);
+			}
+			elseif (extension_loaded('iconv'))
+			{
+				$output = @iconv_mime_encode('', $str,
+					array(
+						'scheme' => 'Q',
+						'line-length' => 76,
+						'input-charset' => $this->charset,
+						'output-charset' => $this->charset,
+						'line-break-chars' => $this->crlf
+					)
+				);
+
+				// There are reports that iconv_mime_encode() might fail and return FALSE
+				if ($output !== FALSE)
+				{
+					// iconv_mime_encode() will always put a header field name.
+					// We've passed it an empty one, but it still prepends our
+					// encoded string with ': ', so we need to strip it.
+					return substr($output, 2);
+				}
+
+				$chars = iconv_strlen($str, 'UTF-8');
+			}
 		}
 
-		$output = '';
-		$temp = '';
+		// We might already have this set for UTF-8
+		isset($chars) OR $chars = strlen($str);
 
-		for ($i = 0, $length = strlen($str); $i < $length; $i++)
+		$output = '=?'.$this->charset.'?Q?';
+		for ($i = 0, $length = strlen($output), $iconv = extension_loaded('iconv'); $i < $chars; $i++)
 		{
-			// Grab the next character
-			$char = $str[$i];
-			$ascii = ord($char);
+			$chr = ($this->charset === 'UTF-8' && $iconv === TRUE)
+				? '='.implode('=', str_split(strtoupper(bin2hex(iconv_substr($str, $i, 1, $this->charset))), 2))
+				: '='.strtoupper(bin2hex($str[$i]));
 
-			// convert ALL non-printable ASCII characters and our specials
-			if ($ascii < 32 OR $ascii > 126 OR in_array($char, $convert))
+			// RFC 2045 sets a limit of 76 characters per line.
+			// We'll append ?= to the end of each line though.
+			if ($length + ($l = strlen($chr)) > 74)
 			{
-				$char = '='.dechex($ascii);
+				$output .= '?='.$this->crlf // EOL
+					.' =?'.$this->charset.'?Q?'.$chr; // New line
+				$length = 6 + strlen($this->charset) + $l; // Reset the length for the new line
 			}
-
-			// handle regular spaces a bit more compactly than =20
-			if ($ascii === 32)
+			else
 			{
-				$char = '_';
+				$output .= $chr;
+				$length += $l;
 			}
-
-			// If we're at the character limit, add the line to the output,
-			// reset our temp variable, and keep on chuggin'
-			if ((strlen($temp) + strlen($char)) >= $limit)
-			{
-				$output .= $temp.$this->crlf;
-				$temp = '';
-			}
-
-			// Add the character to our temporary line
-			$temp .= $char;
 		}
 
-		// wrap each line with the shebang, charset, and transfer encoding
-		// the preceding space on successive lines is required for header "folding"
-		return trim(preg_replace('/^(.*?)(\r*)$/m', ' =?'.$this->charset.'?Q?$1?=$2', $output.$temp));
+		// End the header
+		return $output.'?=';
 	}
 
 	// --------------------------------------------------------------------
@@ -1408,6 +1411,11 @@
 	 */
 	protected function _send_with_mail()
 	{
+		if (is_array($this->_recipients))
+		{
+			$this->_recipients = implode(', ', $this->_recipients);
+		}
+
 		if ($this->_safe_mode === TRUE)
 		{
 			return mail($this->_recipients, $this->_subject, $this->_finalbody, $this->_header_str);
@@ -1754,47 +1762,6 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Get IP
-	 *
-	 * @return	string
-	 */
-	protected function _get_ip()
-	{
-		if ($this->_IP !== FALSE)
-		{
-			return $this->_IP;
-		}
-
-		$cip = ( ! empty($_SERVER['HTTP_CLIENT_IP'])) ? $_SERVER['HTTP_CLIENT_IP'] : FALSE;
-		$rip = ( ! empty($_SERVER['REMOTE_ADDR'])) ? $_SERVER['REMOTE_ADDR'] : FALSE;
-		if ($cip) $this->_IP = $cip;
-		elseif ($rip) $this->_IP = $rip;
-		else
-		{
-			$fip = ( ! empty($_SERVER['HTTP_X_FORWARDED_FOR'])) ? $_SERVER['HTTP_X_FORWARDED_FOR'] : FALSE;
-			if ($fip)
-			{
-				$this->_IP = $fip;
-			}
-		}
-
-		if (strpos($this->_IP, ',') !== FALSE)
-		{
-			$x = explode(',', $this->_IP);
-			$this->_IP = end($x);
-		}
-
-		if ( ! preg_match('/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/', $this->_IP))
-		{
-			$this->_IP = '0.0.0.0';
-		}
-
-		return $this->_IP;
-	}
-
-	// --------------------------------------------------------------------
-
-	/**
 	 * Get Debug Message
 	 *
 	 * @return	string
diff --git a/system/libraries/Pagination.php b/system/libraries/Pagination.php
index e1e729b..36b57b3 100644
--- a/system/libraries/Pagination.php
+++ b/system/libraries/Pagination.php
@@ -157,7 +157,7 @@
 		// See if we are using a prefix or suffix on links
 		if ($this->prefix !== '' OR $this->suffix !== '')
 		{
-			$this->cur_page = (int) str_replace(array($this->prefix, $this->suffix), '', $CI->uri->segment($this->uri_segment));
+			$this->cur_page = (int) str_replace(array($this->prefix, $this->suffix), '', $CI->uri->rsegment($this->uri_segment));
 		}
 
 		if ($CI->config->item('enable_query_strings') === TRUE OR $this->page_query_string === TRUE)
@@ -169,7 +169,7 @@
 		}
 		elseif ( ! $this->cur_page && $CI->uri->segment($this->uri_segment) !== $base_page)
 		{
-			$this->cur_page = (int) $CI->uri->segment($this->uri_segment);
+			$this->cur_page = (int) $CI->uri->rsegment($this->uri_segment);
 		}
 
 		// Set current page to 1 if it's not valid or if using page numbers instead of offset
diff --git a/system/libraries/Session/drivers/Session_cookie.php b/system/libraries/Session/drivers/Session_cookie.php
index fb62c7e..8617aec 100755
--- a/system/libraries/Session/drivers/Session_cookie.php
+++ b/system/libraries/Session/drivers/Session_cookie.php
@@ -308,7 +308,7 @@
 		}
 
 		// Kill the cookie
-		$this->_setcookie($this->sess_cookie_name, addslashes(serialize(array())), ($this->now - 31500000),
+		$this->_setcookie($this->sess_cookie_name, '', ($this->now - 31500000),
 			$this->cookie_path, $this->cookie_domain, 0);
 
 		// Kill session data
@@ -372,27 +372,31 @@
 			return FALSE;
 		}
 
+		$len = strlen($session) - 40;
+
+		if ($len < 0)
+		{
+			log_message('debug', 'The session cookie was not signed.');
+			return FALSE;
+		}
+
+		// Check cookie authentication
+		$hmac	 = substr($session, $len);
+		$session = substr($session, 0, $len);
+
+		if ($hmac !== hash_hmac('sha1', $session, $this->encryption_key))
+		{
+			log_message('error', 'The session cookie data did not match what was expected.');
+			$this->sess_destroy();
+			return FALSE;
+		}
+
 		// Check for encryption
 		if ($this->sess_encrypt_cookie === TRUE)
 		{
 			// Decrypt the cookie data
 			$session = $this->CI->encrypt->decode($session);
 		}
-		else
-		{
-			// Encryption was not used, so we need to check the md5 hash in the last 32 chars
-			$len	 = strlen($session)-32;
-			$hash	 = substr($session, $len);
-			$session = substr($session, 0, $len);
-
-			// Does the md5 hash match? This is to prevent manipulation of session data in userspace
-			if ($hash !== md5($session.$this->encryption_key))
-			{
-				log_message('error', 'The session cookie data did not match what was expected. This could be a possible hacking attempt.');
-				$this->sess_destroy();
-				return FALSE;
-			}
-		}
 
 		// Unserialize the session array
 		$session = $this->_unserialize($session);
@@ -405,7 +409,7 @@
 		}
 
 		// Is the session current?
-		if (($session['last_activity'] + $this->sess_expiration) < $this->now)
+		if (($session['last_activity'] + $this->sess_expiration) < $this->now OR $session['last_activity'] > $this->now)
 		{
 			$this->sess_destroy();
 			return FALSE;
@@ -658,10 +662,13 @@
 		// Serialize the userdata for the cookie
 		$cookie_data = $this->_serialize($cookie_data);
 
-		$cookie_data = ($this->sess_encrypt_cookie === TRUE)
-			? $this->CI->encrypt->encode($cookie_data)
-			// if encryption is not used, we provide an md5 hash to prevent userside tampering
-			: $cookie_data.md5($cookie_data.$this->encryption_key);
+		if ($this->sess_encrypt_cookie === TRUE)
+		{
+			$cookie_data = $this->CI->encrypt->encode($cookie_data);
+		}
+
+		// Require message authentication
+		$cookie_data .= hash_hmac('sha1', $cookie_data, $this->encryption_key);
 
 		$expire = ($this->sess_expire_on_close === TRUE) ? 0 : $this->sess_expiration + time();
 
diff --git a/system/libraries/Session/drivers/Session_native.php b/system/libraries/Session/drivers/Session_native.php
index 8d5e515..da744f3 100755
--- a/system/libraries/Session/drivers/Session_native.php
+++ b/system/libraries/Session/drivers/Session_native.php
@@ -55,7 +55,9 @@
 			'sess_time_to_update',
 			'cookie_prefix',
 			'cookie_path',
-			'cookie_domain'
+			'cookie_domain',
+			'cookie_secure',
+			'cookie_httponly'
 		);
 
 		foreach ($prefs as $key)
@@ -82,6 +84,9 @@
 		$expire = 7200;
 		$path = '/';
 		$domain = '';
+		$secure = (bool) $config['cookie_secure'];
+		$http_only = (bool) $config['cookie_httponly'];
+
 		if ($config['sess_expiration'] !== FALSE)
 		{
 			// Default to 2 years if expiration is "0"
@@ -99,7 +104,8 @@
 			// Use specified domain
 			$domain = $config['cookie_domain'];
 		}
-		session_set_cookie_params($config['sess_expire_on_close'] ? 0 : $expire, $path, $domain);
+
+		session_set_cookie_params($config['sess_expire_on_close'] ? 0 : $expire, $path, $domain, $secure, $http_only);
 
 		// Start session
 		session_start();
@@ -107,7 +113,7 @@
 		// Check session expiration, ip, and agent
 		$now = time();
 		$destroy = FALSE;
-		if (isset($_SESSION['last_activity']) && ($_SESSION['last_activity'] + $expire) < $now)
+		if (isset($_SESSION['last_activity']) && (($_SESSION['last_activity'] + $expire) < $now OR $_SESSION['last_activity'] > $now))
 		{
 			// Expired - destroy
 			$destroy = TRUE;
@@ -137,8 +143,12 @@
 		if ($config['sess_time_to_update'] && isset($_SESSION['last_activity'])
 			&& ($_SESSION['last_activity'] + $config['sess_time_to_update']) < $now)
 		{
-			// Regenerate ID, but don't destroy session
-			$this->sess_regenerate(FALSE);
+			// Changing the session ID amidst a series of AJAX calls causes problems
+			if( ! $this->CI->input->is_ajax_request())
+			{
+				// Regenerate ID, but don't destroy session
+				$this->sess_regenerate(FALSE);
+			}
 		}
 
 		// Set activity time
@@ -189,7 +199,7 @@
 		{
 			// Clear session cookie
 			$params = session_get_cookie_params();
-			setcookie($name, '', time() - 42000, $params['path'], $params['domain']);
+			setcookie($name, '', time() - 42000, $params['path'], $params['domain'], $params['secure'], $params['httponly']);
 			unset($_COOKIE[$name]);
 		}
 		session_destroy();
diff --git a/tests/codeigniter/helpers/captcha_helper_test.php b/tests/codeigniter/helpers/captcha_helper_test.php
new file mode 100644
index 0000000..fc86305
--- /dev/null
+++ b/tests/codeigniter/helpers/captcha_helper_test.php
@@ -0,0 +1,10 @@
+<?php
+
+class Captcha_helper_test extends CI_TestCase {
+
+	public function test_create_captcha()
+	{
+		$this->markTestSkipped('Cant easily test');
+	}
+
+}
\ No newline at end of file
diff --git a/tests/codeigniter/helpers/cookie_helper_test.php b/tests/codeigniter/helpers/cookie_helper_test.php
new file mode 100644
index 0000000..fba68f2
--- /dev/null
+++ b/tests/codeigniter/helpers/cookie_helper_test.php
@@ -0,0 +1,59 @@
+<?php
+
+class Cookie_helper_test extends CI_TestCase {
+
+	public function set_up()
+	{
+		$this->helper('cookie');
+	}
+
+	// ------------------------------------------------------------------------
+
+	function test_set_cookie()
+	{
+		/*$input_cls = $this->ci_core_class('input');
+		$this->ci_instance_var('input', new $input_cls);
+
+		$this->assertTrue(set_cookie(
+			'my_cookie',
+			'foobar'
+		));*/
+
+		$this->markTestSkipped('Need to find a way to overcome a headers already set exception');
+	}
+
+	// ------------------------------------------------------------------------
+
+	function test_get_cookie()
+	{
+		$_COOKIE['foo'] = 'bar';
+
+		$security = new Mock_Core_Security();
+		$utf8 = new Mock_Core_Utf8();
+		$input_cls = $this->ci_core_class('input');
+		$this->ci_instance_var('input', new Mock_Core_Input($security, $utf8));
+
+		$this->assertEquals('bar', get_cookie('foo', FALSE));
+		$this->assertEquals('bar', get_cookie('foo', TRUE));
+
+		$_COOKIE['bar'] = "Hello, i try to <script>alert('Hack');</script> your site";
+
+		$this->assertEquals("Hello, i try to [removed]alert&#40;'Hack'&#41;;[removed] your site", get_cookie('bar', TRUE));
+		$this->assertEquals("Hello, i try to <script>alert('Hack');</script> your site", get_cookie('bar', FALSE));
+	}
+
+	// ------------------------------------------------------------------------
+
+	function test_delete_cookie()
+	{
+		/*$input_cls = $this->ci_core_class('input');
+		$this->ci_instance_var('input', new $input_cls);
+
+		$this->assertTrue(delete_cookie(
+			'my_cookie'
+		));*/
+
+		$this->markTestSkipped('Need to find a way to overcome a headers already set exception');
+	}
+
+}
\ No newline at end of file
diff --git a/tests/codeigniter/helpers/download_helper_test.php b/tests/codeigniter/helpers/download_helper_test.php
new file mode 100644
index 0000000..d2b42e4
--- /dev/null
+++ b/tests/codeigniter/helpers/download_helper_test.php
@@ -0,0 +1,10 @@
+<?php
+
+class Download_helper_test extends CI_TestCase {
+
+	public function test_force_download()
+	{
+		$this->markTestSkipped('Cant easily test');
+	}
+
+}
\ No newline at end of file
diff --git a/tests/codeigniter/helpers/language_helper_test.php b/tests/codeigniter/helpers/language_helper_test.php
new file mode 100644
index 0000000..06932b9
--- /dev/null
+++ b/tests/codeigniter/helpers/language_helper_test.php
@@ -0,0 +1,14 @@
+<?php
+
+class Language_helper_test extends CI_TestCase {
+
+	public function test_lang()
+	{
+		$this->helper('language');
+		$this->ci_instance_var('lang', new Mock_Core_Lang());
+
+		$this->assertFalse(lang(1));
+		$this->assertEquals('<label for="foo"></label>', lang(1, 'foo'));
+	}
+
+}
\ No newline at end of file
diff --git a/tests/codeigniter/helpers/security_helper_test.php b/tests/codeigniter/helpers/security_helper_test.php
new file mode 100644
index 0000000..effd3ec
--- /dev/null
+++ b/tests/codeigniter/helpers/security_helper_test.php
@@ -0,0 +1,64 @@
+<?php
+
+class Security_helper_tests extends CI_TestCase {
+
+	function setUp()
+	{
+		$this->helper('security');
+		$obj = new stdClass;
+		$obj->security = new Mock_Core_Security();
+		$this->ci_instance($obj);
+	}
+
+	function test_xss_clean()
+	{
+		$this->assertEquals('foo', xss_clean('foo'));
+
+		$this->assertEquals("Hello, i try to [removed]alert&#40;'Hack'&#41;;[removed] your site", xss_clean("Hello, i try to <script>alert('Hack');</script> your site"));
+	}
+
+	function test_sanitize_filename()
+	{
+		$this->assertEquals('hello.doc', sanitize_filename('hello.doc'));
+
+		$filename = './<!--foo-->';
+		$this->assertEquals('foo', sanitize_filename($filename));
+	}
+
+	function test_do_hash()
+	{
+		$md5 = md5('foo');
+		$sha1 = sha1('foo');
+
+		$algos = hash_algos();
+		$algo_results = array();
+		foreach ($algos as $k => $v)
+		{
+			$algo_results[$v] = hash($v, 'foo');
+		}
+
+		$this->assertEquals($sha1, do_hash('foo'));
+		$this->assertEquals($sha1, do_hash('foo', 'sha1'));
+		$this->assertEquals($md5, do_hash('foo', 'md5'));
+		$this->assertEquals($md5, do_hash('foo', 'foobar'));
+
+		// Test each algorithm available to PHP
+		foreach ($algo_results as $algo => $result)
+		{
+			$this->assertEquals($result, do_hash('foo', $algo));
+		}
+	}
+
+	function test_strip_image_tags()
+	{
+		$this->assertEquals('http://example.com/spacer.gif', strip_image_tags('http://example.com/spacer.gif'));
+
+		$this->assertEquals('http://example.com/spacer.gif', strip_image_tags('<img src="http://example.com/spacer.gif" alt="Who needs CSS when you have a spacer.gif?" />'));
+	}
+
+	function test_encode_php_tags()
+	{
+		$this->assertEquals('&lt;? echo $foo; ?&gt;', encode_php_tags('<? echo $foo; ?>'));
+	}
+
+}
\ No newline at end of file
diff --git a/tests/codeigniter/helpers/url_helper_test.php b/tests/codeigniter/helpers/url_helper_test.php
index c81c5f1..5fc3642 100644
--- a/tests/codeigniter/helpers/url_helper_test.php
+++ b/tests/codeigniter/helpers/url_helper_test.php
@@ -51,6 +51,8 @@
 			'www.codeigniter.com test' => '<a href="http://www.codeigniter.com">http://www.codeigniter.com</a> test',
 			'This is my noreply@codeigniter.com test' => 'This is my noreply@codeigniter.com test',
 			'<br />www.google.com' => '<br /><a href="http://www.google.com">http://www.google.com</a>',
+			'Download CodeIgniter at www.codeigniter.com. Period test.' => 'Download CodeIgniter at <a href="http://www.codeigniter.com">http://www.codeigniter.com</a>. Period test.',
+			'Download CodeIgniter at www.codeigniter.com, comma test' => 'Download CodeIgniter at <a href="http://www.codeigniter.com">http://www.codeigniter.com</a>, comma test'
 		);
 
 		foreach ($strings as $in => $out)
diff --git a/tests/codeigniter/libraries/Upload_test.php b/tests/codeigniter/libraries/Upload_test.php
index d79a3ff..b4ef7bb 100644
--- a/tests/codeigniter/libraries/Upload_test.php
+++ b/tests/codeigniter/libraries/Upload_test.php
@@ -18,9 +18,9 @@
 		$this->_test_dir = vfsStreamWrapper::getRoot();
 	}
 
-	function test_do_upload() 
+	function test_do_upload()
 	{
-		$this->markTestIncomplete('We can\'t really test this at the moment because of the call to `is_uploaded_file` in do_upload which isn\'t supported by vfsStream');
+		$this->markTestSkipped('We can\'t really test this at the moment because of the call to `is_uploaded_file` in do_upload which isn\'t supported by vfsStream');
 	}
 
 	function test_data()
@@ -75,7 +75,7 @@
 	{
 		$this->upload->set_max_filesize(100);
 		$this->assertEquals(100, $this->upload->max_size);
-	}	
+	}
 
 	function test_set_max_filename()
 	{
@@ -87,7 +87,7 @@
 	{
 		$this->upload->set_max_width(100);
 		$this->assertEquals(100, $this->upload->max_width);
-	}	
+	}
 
 	function test_set_max_height()
 	{
@@ -181,7 +181,7 @@
 		$this->upload->file_type = 'image/gif';
 		$this->upload->file_temp = 'tests/mocks/uploads/ci_logo.gif';
 
-		$this->upload->max_width = 10;		
+		$this->upload->max_width = 10;
 		$this->assertFalse($this->upload->is_allowed_dimensions());
 
 		$this->upload->max_width = 170;
diff --git a/tests/phpunit.xml b/tests/phpunit.xml
index 56cb884..96c3af9 100644
--- a/tests/phpunit.xml
+++ b/tests/phpunit.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 
-<phpunit 
+<phpunit
 	bootstrap="./Bootstrap.php"
 	colors="true"
 	convertNoticesToExceptions="true"
@@ -16,10 +16,11 @@
 			<directory suffix="test.php">./codeigniter/libraries</directory>
 		</testsuite>
 	</testsuites>
-	<filters>
+	<filter>
 		<blacklist>
 			<directory suffix=".php">PEAR_INSTALL_DIR</directory>
 			<directory suffix=".php">PHP_LIBDIR</directory>
+			<directory suffix=".php">../vendor</directory>
 		</blacklist>
-	</filters>
+	</filter>
 </phpunit>
\ No newline at end of file
diff --git a/user_guide_src/source/changelog.rst b/user_guide_src/source/changelog.rst
index 145853a..d3f91de 100644
--- a/user_guide_src/source/changelog.rst
+++ b/user_guide_src/source/changelog.rst
@@ -65,7 +65,7 @@
    -  ``create_captcha()`` accepts additional colors parameter, allowing for color customization.
    -  :doc:`URL Helper <helpers/url_helper>` changes include:
 	 - ``url_title()`` will now trim extra dashes from beginning and end.
-	 - ``anchor_popup()`` will now fill the "href" attribute with the URL and its JS code will return false instead.
+	 - ``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 ``anchor_popup()`` function.
 	 - Added support (auto-detection) for HTTP/1.1 response code 303 in ``redirect()``.
 	 - "auto" method in ``redirect()`` now chooses the "refresh" method only on IIS servers, instead of all servers on Windows.
@@ -153,12 +153,14 @@
 
    -  :doc:`Session Library <libraries/sessions>` changes include:
 	 -  Library changed to :doc:`Driver <general/drivers>` with classic Cookie driver as default.
-	 -  Added Native PHP Session driver to work with $_SESSION.
-	 -  Custom session drivers can be added anywhere in package paths and loaded with Session library.
-	 -  Session drivers interchangeable on the fly.
-	 -  New tempdata feature allows setting user data items with an expiration time.
-	 -  Added default $config['sess_driver'] and $config['sess_valid_drivers'] items to config.php file.
-	 -  Cookie driver now respects php.ini's session.gc_probability and session.gc_divisor
+	 -  Added Native PHP Session driver to work with ``$_SESSION``.
+	 -  Custom drivers can be added anywhere in package paths and be loaded with the library.
+	 -  Drivers interchangeable on the fly.
+	 -  New **tempdata** feature allows setting user data items with an expiration time.
+	 -  Added default ``$config['sess_driver']`` and ``$config['sess_valid_drivers']`` items to *config.php* file.
+	 -  Cookie driver now respects php.ini's *session.gc_probability* and *session.gc_divisor* settings.
+	 -  Cookie driver now uses HMAC authentication instead of the simple md5 checksum.
+	 -  The Cookie driver now also checks authentication on encrypted session data.
 	 -  Changed the Cookie driver to select only one row when using database sessions.
 	 -  Cookie driver now only writes to database at end of request when using database.
 	 -  Cookie driver now uses PHP functions for faster array manipulation when using database.
@@ -203,6 +205,8 @@
 	 -  Removed the second parameter (character limit) from internal method ``_prep_quoted_printable()`` as it is never used.
 	 -  Internal method ``_prep_quoted_printable()`` will now utilize the native ``quoted_printable_encode()``, ``imap_8bit()`` functions (if available) when CRLF is set to "\r\n".
 	 -  Default charset now relies on the global ``$config['charset']`` setting.
+	 -  Removed unused protected method ``_get_ip()`` (:doc:`Input Library <libraries/input>`'s ``ip_address()`` should be used anyway).
+	 -  Internal method ``_prep_q_encoding()`` now utilizes PHP's *mbstring* and *iconv* extensions (when available) and no longer has a second (``$from``) argument.
    -  :doc:`Pagination Library <libraries/pagination>` changes include:
 	 -  Added support for the anchor "rel" attribute.
 	 -  Added support for setting custom attributes.
@@ -229,10 +233,11 @@
 	 -  Modified ``valid_ip()`` to use PHP's ``filter_var()``.
 	 -  Added support for arrays and network addresses (e.g. 192.168.1.1/24) for use with the *proxy_ips* setting.
    -  :doc:`Common functions <general/common_functions>` changes include:
-	 -  Added ``get_mimes()`` function to return the *config/mimes.php* array.
+	 -  Added function ``get_mimes()`` to return the *config/mimes.php* array.
 	 -  Added support for HTTP code 303 ("See Other") in ``set_status_header()``.
 	 -  Removed redundant conditional to determine HTTP server protocol in ``set_status_header()``.
 	 -  Changed ``_exception_handler()`` to respect php.ini *display_errors* setting.
+	 -  Added function ``is_https()`` to check if a secure connection is used.
    -  Added support for HTTP-Only cookies with new config option *cookie_httponly* (default FALSE).
    -  Renamed method ``_call_hook()`` to ``call_hook()`` in the :doc:`Hooks Library <general/hooks>`.
    -  :doc:`Output Library <libraries/output>` changes include:
@@ -340,7 +345,7 @@
 -  Fixed a bug (#318) - :doc:`Profiling <general/profiling>` setting *query_toggle_count* was not settable as described in the manual.
 -  Fixed a bug (#938) - :doc:`Config Library <libraries/config>` method ``site_url()`` added a question mark to the URL string when query strings are enabled even if it already existed.
 -  Fixed a bug (#999) - :doc:`Config Library <libraries/config>` method ``site_url()`` always appended ``$config['url_suffix']`` to the end of the URL string, regardless of whether a query string exists in it.
--  Fixed a bug where :doc:`URL Helper <helpers/url_helper>` function anchor_popup() ignored the attributes argument if it is not an array.
+-  Fixed a bug where :doc:`URL Helper <helpers/url_helper>` function ``anchor_popup()`` ignored the attributes argument if it is not an array.
 -  Fixed a bug (#1328) - :doc:`Form Validation Library <libraries/form_validation>` didn't properly check the type of the form fields before processing them.
 -  Fixed a bug (#79) - :doc:`Form Validation Library <libraries/form_validation>` didn't properly validate array fields that use associative keys or have custom indexes.
 -  Fixed a bug (#427) - :doc:`Form Validation Library <libraries/form_validation>` method ``strip_image_tags()`` was an alias to a non-existent method.
@@ -360,7 +365,12 @@
 -  Fixed a bug (#1765) - :doc:`Database Library <database/index>` didn't properly detect connection errors for MySQLi.
 -  Fixed a bug (#1257) - :doc:`Query Builder <database/query_builder>` used to (unnecessarily) group FROM clause contents, which breaks certain queries and is invalid for some databases.
 -  Fixed a bug (#1709) - :doc:`Email <libraries/email>` headers were broken when using long email subjects and \r\n as CRLF.
--  Fixed a bug where MB_ENABLED was only declared if UTF8_ENABLED was set to TRUE.
+-  Fixed a bug where ``MB_ENABLED`` was only declared if ``UTF8_ENABLED`` was set to TRUE.
+-  Fixed a bug where the :doc:`Session Library <libraries/sessions>` accepted cookies with *last_activity* values being in the future.
+-  Fixed a bug (#1897) - :doc:`Email Library <libraries/email>` triggered PHP E_WARNING errors when *mail* protocol used and ``to()`` is never called.
+-  Fixed a bug (#1409) - :doc:`Email Library <libraries/email>` didn't properly handle multibyte characters when applying Q-encoding to headers.
+-  Fixed a bug where :doc:`Email Library <libraries/email>` didn't honor it's *wordwrap* setting while handling alternative messages.
+-  Fixed a bug (#1476, #1909) - :doc:`Pagination Library <libraries/pagination>` didn't take into account actual routing when determining the current page.
 
 Version 2.1.3
 =============
@@ -374,7 +384,7 @@
 -------------------
 
 -  Fixed a bug (#1543) - File-based :doc:`Caching <libraries/caching>` method ``get_metadata()`` used a non-existent array key to look for the TTL value.
--  Fixed a bug (#1314) - :doc:`Session Library <libraries/session>` method ``sess_destroy()`` didn't destroy the userdata array.
+-  Fixed a bug (#1314) - :doc:`Session Library <libraries/sessions>` method ``sess_destroy()`` didn't destroy the userdata array.
 -  Fixed a bug (#804) - Profiler library was trying to handle objects as strings in some cases, resulting in *E_WARNING* messages being issued by ``htmlspecialchars()``.
 -  Fixed a bug (#1699) - :doc:`Migration Library <libraries/migration>` ignored the ``$config['migration_path']`` setting.
 -  Fixed a bug (#227) - :doc:`Input Library <libraries/input>` allowed unconditional spoofing of HTTP clients' IP addresses through the *HTTP_CLIENT_IP* header.
diff --git a/user_guide_src/source/general/common_functions.rst b/user_guide_src/source/general/common_functions.rst
index 99126f9..f3d48ac 100644
--- a/user_guide_src/source/general/common_functions.rst
+++ b/user_guide_src/source/general/common_functions.rst
@@ -7,7 +7,7 @@
 loading any libraries or helpers.
 
 is_php('version_number')
-==========================
+========================
 
 is_php() determines of the PHP version being used is greater than the
 supplied version_number.
@@ -24,7 +24,7 @@
 version of PHP is lower than the supplied version number.
 
 is_really_writable('path/to/file')
-====================================
+==================================
 
 is_writable() returns TRUE on Windows servers when you really can't
 write to the file as the OS reports to PHP as FALSE only if the
@@ -44,7 +44,7 @@
 	}
 
 config_item('item_key')
-=========================
+=======================
 
 The :doc:`Config library <../libraries/config>` is the preferred way of
 accessing configuration information, however config_item() can be used
@@ -56,8 +56,8 @@
 
 These are each outlined on the :doc:`Error Handling <errors>` page.
 
-set_status_header(code, 'text');
-================================
+set_status_header(code, 'text')
+===============================
 
 Permits you to manually set a server status header. Example::
 
@@ -68,19 +68,25 @@
 a full list of headers.
 
 remove_invisible_characters($str)
-===================================
+=================================
 
 This function prevents inserting null characters between ascii
 characters, like Java\\0script.
 
 html_escape($mixed)
-====================
+===================
 
-This function provides short cut for htmlspecialchars() function. It
+This function provides short cut for ``htmlspecialchars()`` function. It
 accepts string and array. To prevent Cross Site Scripting (XSS), it is
 very useful.
 
 get_mimes()
-=============
+===========
 
-This function returns the MIMEs array from config/mimes.php.
\ No newline at end of file
+This function returns the MIMEs array *from config/mimes.php*.
+
+is_https()
+==========
+
+Returns TRUE if a secure (HTTPS) connection is used and FALSE
+in any other case (including non-HTTP requests).
\ No newline at end of file
diff --git a/user_guide_src/source/libraries/javascript.rst b/user_guide_src/source/libraries/javascript.rst
index d5e09c3..393d4e3 100644
--- a/user_guide_src/source/libraries/javascript.rst
+++ b/user_guide_src/source/libraries/javascript.rst
@@ -192,7 +192,7 @@
 	'width' => '50%',
 	'marginLeft' => 125
 	);
-	$this->jquery->click('#trigger', $this->jquery->animate('#note', $params, normal));
+	$this->jquery->click('#trigger', $this->jquery->animate('#note', $params, 'normal'));
 
 fadeIn() / fadeOut()
 --------------------
diff --git a/user_guide_src/source/libraries/output.rst b/user_guide_src/source/libraries/output.rst
index 3289a24..2b72ba7 100644
--- a/user_guide_src/source/libraries/output.rst
+++ b/user_guide_src/source/libraries/output.rst
@@ -105,7 +105,7 @@
 `See here <http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html>`_ for
 a full list of headers.
 
-.. note:: This method is an alias for :doc:`Common function <../general/common_funtions.rst>`
+.. note:: This method is an alias for :doc:`Common function <../general/common_functions.rst>`
 	``set_status_header()``.
 
 $this->output->enable_profiler();