Fix issue #1409
diff --git a/system/libraries/Email.php b/system/libraries/Email.php
index bc9d62e..795db20 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;
@@ -1177,66 +1176,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.'?=';
 	}
 
 	// --------------------------------------------------------------------