From 037af6890fe6fdb84a08d3c86083e847c90ec0ad Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Tue, 22 Oct 2013 08:17:26 -0400
Subject: [PATCH] Fix vulnerability in handling _session argument of utils/save-prefs (#1489382)

---
 program/lib/Mail/mime.php |  598 +++++++++++++++++++++++++++--------------------------------
 1 files changed, 271 insertions(+), 327 deletions(-)

diff --git a/program/lib/Mail/mime.php b/program/lib/Mail/mime.php
index a4f4901..69a032c 100644
--- a/program/lib/Mail/mime.php
+++ b/program/lib/Mail/mime.php
@@ -48,7 +48,7 @@
  * @author    Aleksander Machniak <alec@php.net>
  * @copyright 2003-2006 PEAR <pear-group@php.net>
  * @license   http://www.opensource.org/licenses/bsd-license.php BSD License
- * @version   CVS: $Id$
+ * @version   Release: 1.8.7
  * @link      http://pear.php.net/package/Mail_mime
  *
  *            This class is based on HTML Mime Mail class from
@@ -89,7 +89,7 @@
  * @author    Sean Coates <sean@php.net>
  * @copyright 2003-2006 PEAR <pear-group@php.net>
  * @license   http://www.opensource.org/licenses/bsd-license.php BSD License
- * @version   Release: @package_version@
+ * @version   Release: 1.8.7
  * @link      http://pear.php.net/package/Mail_mime
  */
 class Mail_mime
@@ -109,22 +109,6 @@
      * @access private
      */
     var $_htmlbody;
-
-    /**
-     * contains the mime encoded text
-     *
-     * @var string
-     * @access private
-     */
-    var $_mime;
-
-    /**
-     * contains the multipart content
-     *
-     * @var string
-     * @access private
-     */
-    var $_multipart;
 
     /**
      * list of the attached images
@@ -261,7 +245,7 @@
             }
         } else {
             $cont = $this->_file2str($data);
-            if (PEAR::isError($cont)) {
+            if ($this->_isError($cont)) {
                 return $cont;
             }
             if (!$append) {
@@ -302,7 +286,7 @@
             $this->_htmlbody = $data;
         } else {
             $cont = $this->_file2str($data);
-            if (PEAR::isError($cont)) {
+            if ($this->_isError($cont)) {
                 return $cont;
             }
             $this->_htmlbody = $cont;
@@ -352,7 +336,7 @@
                 $filedata = null;
                 $bodyfile = $file;
             } else {
-                if (PEAR::isError($filedata = $this->_file2str($file))) {
+                if ($this->_isError($filedata = $this->_file2str($file))) {
                     return $filedata;
                 }
             }
@@ -381,29 +365,30 @@
      * Adds a file to the list of attachments.
      *
      * @param string $file        The file name of the file to attach
-     *                            OR the file contents itself
+     *                            or the file contents itself
      * @param string $c_type      The content type
      * @param string $name        The filename of the attachment
      *                            Only use if $file is the contents
-     * @param bool   $isfile      Whether $file is a filename or not
-     *                            Defaults to true
-     * @param string $encoding    The type of encoding to use.
-     *                            Defaults to base64.
-     *                            Possible values: 7bit, 8bit, base64, 
-     *                            or quoted-printable.
+     * @param bool   $isfile      Whether $file is a filename or not. Defaults to true
+     * @param string $encoding    The type of encoding to use. Defaults to base64.
+     *                            Possible values: 7bit, 8bit, base64 or quoted-printable.
      * @param string $disposition The content-disposition of this file
      *                            Defaults to attachment.
      *                            Possible values: attachment, inline.
-     * @param string $charset     The character set used in the filename
-     *                            of this attachment.
+     * @param string $charset     The character set of attachment's content.
      * @param string $language    The language of the attachment
      * @param string $location    The RFC 2557.4 location of the attachment
-     * @param string $n_encoding  Encoding for attachment name (Content-Type)
+     * @param string $n_encoding  Encoding of the attachment's name in Content-Type
      *                            By default filenames are encoded using RFC2231 method
      *                            Here you can set RFC2047 encoding (quoted-printable
      *                            or base64) instead
-     * @param string $f_encoding  Encoding for attachment filename (Content-Disposition)
-     *                            See $n_encoding description
+     * @param string $f_encoding  Encoding of the attachment's filename
+     *                            in Content-Disposition header.
+     * @param string $description Content-Description header
+     * @param string $h_charset   The character set of the headers e.g. filename
+     *                            If not specified, $charset will be used
+     * @param array  $add_headers Additional part headers. Array keys can be in form
+     *                            of <header_name>:<parameter_name>
      *
      * @return mixed              True on success or PEAR_Error object
      * @access public
@@ -418,7 +403,10 @@
         $language    = '',
         $location    = '',
         $n_encoding  = null,
-        $f_encoding  = null
+        $f_encoding  = null,
+        $description = '',
+        $h_charset   = null,
+        $add_headers = array()
     ) {
         $bodyfile = null;
 
@@ -428,12 +416,12 @@
                 $filedata = null;
                 $bodyfile = $file;
             } else {
-                if (PEAR::isError($filedata = $this->_file2str($file))) {
+                if ($this->_isError($filedata = $this->_file2str($file))) {
                     return $filedata;
                 }
             }
             // Force the name the user supplied, otherwise use $file
-            $filename = ($name ? $name : $file);
+            $filename = ($name ? $name : $this->_basename($file));
         } else {
             $filedata = $file;
             $filename = $name;
@@ -444,20 +432,22 @@
             $err = PEAR::raiseError($msg);
             return $err;
         }
-        $filename = $this->_basename($filename);
 
         $this->_parts[] = array(
             'body'        => $filedata,
             'body_file'   => $bodyfile,
             'name'        => $filename,
             'c_type'      => $c_type,
-            'encoding'    => $encoding,
             'charset'     => $charset,
+            'encoding'    => $encoding,
             'language'    => $language,
             'location'    => $location,
             'disposition' => $disposition,
+            'description' => $description,
+            'add_headers' => $add_headers,
             'name_encoding'     => $n_encoding,
-            'filename_encoding' => $f_encoding
+            'filename_encoding' => $f_encoding,
+            'headers_charset'   => $h_charset,
         );
 
         return true;
@@ -471,7 +461,7 @@
      * @return string           Contents of $file_name
      * @access private
      */
-    function &_file2str($file_name)
+    function _file2str($file_name)
     {
         // Check state of file and raise an error properly
         if (!file_exists($file_name)) {
@@ -510,7 +500,7 @@
      * @return object      The text mimePart object
      * @access private
      */
-    function &_addTextPart(&$obj, $text)
+    function _addTextPart(&$obj, $text)
     {
         $params['content_type'] = 'text/plain';
         $params['encoding']     = $this->_build_params['text_encoding'];
@@ -536,7 +526,7 @@
      * @return object     The html mimePart object
      * @access private
      */
-    function &_addHtmlPart(&$obj)
+    function _addHtmlPart(&$obj)
     {
         $params['content_type'] = 'text/html';
         $params['encoding']     = $this->_build_params['html_encoding'];
@@ -560,7 +550,7 @@
      * @return object The multipart/mixed mimePart object
      * @access private
      */
-    function &_addMixedPart()
+    function _addMixedPart()
     {
         $params                 = array();
         $params['content_type'] = 'multipart/mixed';
@@ -582,7 +572,7 @@
      * @return object     The multipart/mixed mimePart object
      * @access private
      */
-    function &_addAlternativePart(&$obj)
+    function _addAlternativePart(&$obj)
     {
         $params['content_type'] = 'multipart/alternative';
         $params['eol']          = $this->_build_params['eol'];
@@ -606,7 +596,7 @@
      * @return object     The multipart/mixed mimePart object
      * @access private
      */
-    function &_addRelatedPart(&$obj)
+    function _addRelatedPart(&$obj)
     {
         $params['content_type'] = 'multipart/related';
         $params['eol']          = $this->_build_params['eol'];
@@ -629,12 +619,12 @@
      * @return object       The image mimePart object
      * @access private
      */
-    function &_addHtmlImagePart(&$obj, $value)
+    function _addHtmlImagePart(&$obj, $value)
     {
         $params['content_type'] = $value['c_type'];
         $params['encoding']     = 'base64';
         $params['disposition']  = 'inline';
-        $params['dfilename']    = $value['name'];
+        $params['filename']     = $value['name'];
         $params['cid']          = $value['cid'];
         $params['body_file']    = $value['body_file'];
         $params['eol']          = $this->_build_params['eol'];
@@ -660,22 +650,28 @@
      * @return object       The image mimePart object
      * @access private
      */
-    function &_addAttachmentPart(&$obj, $value)
+    function _addAttachmentPart(&$obj, $value)
     {
         $params['eol']          = $this->_build_params['eol'];
-        $params['dfilename']    = $value['name'];
+        $params['filename']     = $value['name'];
         $params['encoding']     = $value['encoding'];
         $params['content_type'] = $value['c_type'];
         $params['body_file']    = $value['body_file'];
         $params['disposition']  = isset($value['disposition']) ? 
                                   $value['disposition'] : 'attachment';
-        if ($value['charset']) {
+
+        // content charset
+        if (!empty($value['charset'])) {
             $params['charset'] = $value['charset'];
         }
-        if ($value['language']) {
+        // headers charset (filename, description)
+        if (!empty($value['headers_charset'])) {
+            $params['headers_charset'] = $value['headers_charset'];
+        }
+        if (!empty($value['language'])) {
             $params['language'] = $value['language'];
         }
-        if ($value['location']) {
+        if (!empty($value['location'])) {
             $params['location'] = $value['location'];
         }
         if (!empty($value['name_encoding'])) {
@@ -683,6 +679,12 @@
         }
         if (!empty($value['filename_encoding'])) {
             $params['filename_encoding'] = $value['filename_encoding'];
+        }
+        if (!empty($value['description'])) {
+            $params['description'] = $value['description'];
+        }
+        if (is_array($value['add_headers'])) {
+            $params['headers'] = $value['add_headers'];
         }
 
         $ret = $obj->addSubpart($value['body'], $params);
@@ -716,13 +718,29 @@
 
         $body = $this->get($params);
 
-        if (PEAR::isError($body)) {
+        if ($this->_isError($body)) {
             return $body;
         }
 
         $head = $this->txtHeaders($headers, $overwrite);
         $mail = $head . $separation . $body;
         return $mail;
+    }
+
+    /**
+     * Returns the complete e-mail body, ready to send using an alternative
+     * mail delivery method.
+     * 
+     * @param array $params The Build parameters passed to the
+     *                      &get() function. See &get for more info.
+     *
+     * @return mixed The e-mail body or PEAR error object
+     * @access public
+     * @since 1.6.0
+     */
+    function getMessageBody($params = null)
+    {
+        return $this->get($params, null, true);
     }
 
     /**
@@ -738,6 +756,7 @@
      *
      * @return mixed True or PEAR error object
      * @access public
+     * @since 1.6.0
      */
     function saveMessage($filename, $params = null, $headers = null, $overwrite = false)
     {
@@ -777,18 +796,55 @@
     }
 
     /**
+     * Writes (appends) the complete e-mail body into file.
+     * 
+     * @param string $filename Output file location
+     * @param array  $params   The Build parameters passed to the
+     *                         &get() function. See &get for more info.
+     *
+     * @return mixed True or PEAR error object
+     * @access public
+     * @since 1.6.0
+     */
+    function saveMessageBody($filename, $params = null)
+    {
+        // Check state of file and raise an error properly
+        if (file_exists($filename) && !is_writable($filename)) {
+            $err = PEAR::raiseError('File is not writable: ' . $filename);
+            return $err;
+        }
+
+        // Temporarily reset magic_quotes_runtime and read file contents
+        if ($magic_quote_setting = get_magic_quotes_runtime()) {
+            @ini_set('magic_quotes_runtime', 0);
+        }
+
+        if (!($fh = fopen($filename, 'ab'))) {
+            $err = PEAR::raiseError('Unable to open file: ' . $filename);
+            return $err;
+        }
+
+        // Write the rest of the message into file
+        $res = $this->get($params, $filename, true);
+
+        return $res ? $res : true;
+    }
+
+    /**
      * Builds the multipart message from the list ($this->_parts) and
      * returns the mime content.
      *
-     * @param array    $params   Build parameters that change the way the email
-     *                           is built. Should be associative. See $_build_params.
-     * @param resource $filename Output file where to save the message instead of
-     *                           returning it
+     * @param array    $params    Build parameters that change the way the email
+     *                            is built. Should be associative. See $_build_params.
+     * @param resource $filename  Output file where to save the message instead of
+     *                            returning it
+     * @param boolean  $skip_head True if you want to return/save only the message
+     *                            without headers
      *
      * @return mixed The MIME message content string, null or PEAR error object
      * @access public
      */
-    function &get($params = null, $filename = null)
+    function &get($params = null, $filename = null, $skip_head = false)
     {
         if (isset($params)) {
             while (list($key, $value) = each($params)) {
@@ -798,14 +854,16 @@
 
         if (isset($this->_headers['From'])) {
             // Bug #11381: Illegal characters in domain ID
-            if (preg_match("|(@[0-9a-zA-Z\-\.]+)|", $this->_headers['From'], $matches)) {
+            if (preg_match('#(@[0-9a-zA-Z\-\.]+)#', $this->_headers['From'], $matches)) {
                 $domainID = $matches[1];
             } else {
-                $domainID = "@localhost";
+                $domainID = '@localhost';
             }
             foreach ($this->_html_images as $i => $img) {
-                $this->_html_images[$i]['cid']
-                    = $this->_html_images[$i]['cid'] . $domainID;
+                $cid = $this->_html_images[$i]['cid']; 
+                if (!preg_match('#'.preg_quote($domainID).'$#', $cid)) {
+                    $this->_html_images[$i]['cid'] = $cid . $domainID;
+                }
             }
         }
 
@@ -827,11 +885,13 @@
             }
         }
 
+        $this->_checkParams();
+
         $null        = null;
         $attachments = count($this->_parts)                 ? true : false;
         $html_images = count($this->_html_images)           ? true : false;
         $html        = strlen($this->_htmlbody)             ? true : false;
-        $text        = (!$html && strlen($this->_txtbody)) ? true : false;
+        $text        = (!$html && strlen($this->_txtbody))  ? true : false;
 
         switch (true) {
         case $text && !$attachments:
@@ -947,7 +1007,7 @@
             $ret = null;
             return $ret;
         }
-        
+
         // Use saved boundary
         if (!empty($this->_build_params['boundary'])) {
             $boundary = $this->_build_params['boundary'];
@@ -958,14 +1018,16 @@
         // Write output to file
         if ($filename) {
             // Append mimePart message headers and body into file
-            if (PEAR::isError($headers = $message->encodeToFile($filename, $boundary))) {
+            $headers = $message->encodeToFile($filename, $boundary, $skip_head);
+            if ($this->_isError($headers)) {
                 return $headers;
             }
             $this->_headers = array_merge($this->_headers, $headers);
             $ret = null;
             return $ret;
         } else {
-            if (PEAR::isError($output = $message->encode($boundary))) {
+            $output = $message->encode($boundary, $skip_head);
+            if ($this->_isError($output)) {
                 return $output;
             }
             $this->_headers = array_merge($this->_headers, $output['headers']);
@@ -980,8 +1042,9 @@
      * $array['header-name'] = 'header-value';
      *
      * @param array $xtra_headers Assoc array with any extra headers (optional)
+     *                            (Don't set Content-Type for multipart messages here!)
      * @param bool  $overwrite    Overwrite already existing headers.
-     * @param bool  $skip_content Don't return content headers: Content-Type
+     * @param bool  $skip_content Don't return content headers: Content-Type,
      *                            Content-Disposition and Content-Transfer-Encoding
      * 
      * @return array              Assoc array with the mime headers
@@ -1016,6 +1079,8 @@
             unset($headers['Content-Type']);
             unset($headers['Content-Transfer-Encoding']);
             unset($headers['Content-Disposition']);
+        } else if (!empty($this->_build_params['ctype'])) {
+            $headers['Content-Type'] = $this->_build_params['ctype'];
         }
 
         $encodedHeaders = $this->_encodeHeaders($headers);
@@ -1027,9 +1092,10 @@
      * (usefull if you want to use the PHP mail() function)
      *
      * @param array $xtra_headers Assoc array with any extra headers (optional)
-     * @param bool  $overwrite    Overwrite the existing heaers with new.
-     * @param bool  $skip_content Don't return content headers: Content-Type
-     *                            and Content-Transfer-Encoding
+     *                            (Don't set Content-Type for multipart messages here!)
+     * @param bool  $overwrite    Overwrite the existing headers with new.
+     * @param bool  $skip_content Don't return content headers: Content-Type,
+     *                            Content-Disposition and Content-Transfer-Encoding
      *
      * @return string             Plain text headers
      * @access public
@@ -1063,6 +1129,54 @@
     }
 
     /**
+     * Sets message Content-Type header.
+     * Use it to build messages with various content-types e.g. miltipart/raport
+     * not supported by _contentHeaders() function.
+     *
+     * @param string $type   Type name
+     * @param array  $params Hash array of header parameters
+     *
+     * @return void
+     * @access public
+     * @since 1.7.0
+     */
+    function setContentType($type, $params = array())
+    {
+        $header = $type;
+
+        $eol = !empty($this->_build_params['eol'])
+            ? $this->_build_params['eol'] : "\r\n";
+
+        // add parameters
+        $token_regexp = '#([^\x21\x23-\x27\x2A\x2B\x2D'
+            . '\x2E\x30-\x39\x41-\x5A\x5E-\x7E])#';
+        if (is_array($params)) {
+            foreach ($params as $name => $value) {
+                if ($name == 'boundary') {
+                    $this->_build_params['boundary'] = $value;
+                }
+                if (!preg_match($token_regexp, $value)) {
+                    $header .= ";$eol $name=$value";
+                } else {
+                    $value = addcslashes($value, '\\"');
+                    $header .= ";$eol $name=\"$value\"";
+                }
+            }
+        }
+
+        // add required boundary parameter if not defined
+        if (preg_match('/^multipart\//i', $type)) {
+            if (empty($this->_build_params['boundary'])) {
+                $this->_build_params['boundary'] = '=_' . md5(rand() . microtime());
+            }
+
+            $header .= ";$eol boundary=\"".$this->_build_params['boundary']."\"";
+        }
+
+        $this->_build_params['ctype'] = $header;
+    }
+
+    /**
      * Sets the Subject header
      *
      * @param string $subject String to set the subject to.
@@ -1086,6 +1200,24 @@
     function setFrom($email)
     {
         $this->_headers['From'] = $email;
+    }
+
+    /**
+     * Add an email to the To header
+     * (multiple calls to this method are allowed)
+     *
+     * @param string $email The email direction to add
+     *
+     * @return void
+     * @access public
+     */
+    function addTo($email)
+    {
+        if (isset($this->_headers['To'])) {
+            $this->_headers['To'] .= ", $email";
+        } else {
+            $this->_headers['To'] = $email;
+        }
     }
 
     /**
@@ -1193,261 +1325,10 @@
      */
     function encodeHeader($name, $value, $charset, $encoding)
     {
-        // Structured headers
-        $comma_headers = array(
-            'from', 'to', 'cc', 'bcc', 'sender', 'reply-to',
-            'resent-from', 'resent-to', 'resent-cc', 'resent-bcc',
-            'resent-sender', 'resent-reply-to',
+        $mime_part = new Mail_mimePart;
+        return $mime_part->encodeHeader(
+            $name, $value, $charset, $encoding, $this->_build_params['eol']
         );
-        $other_headers = array(
-            'references', 'in-reply-to', 'message-id', 'resent-message-id',
-        );
-
-        $name = strtolower($name);
-        $eol = $this->_build_params['eol'];
-
-        if (in_array($name, $comma_headers)) {
-            $separator = ',';
-        } else if (in_array($name, $other_headers)) {
-            $separator = ' ';
-        }
-
-        if (!$charset) {
-            $charset = 'ISO-8859-1';
-        }
-
-        // Structured header (make sure addr-spec inside is not encoded)
-        if (!empty($separator)) {
-            $parts = $this->_explodeQuotedString($separator, $value);
-            $value = '';
-
-            foreach ($parts as $part) {
-                $part = preg_replace('/\r?\n[\s\t]*/', $eol . ' ', $part);
-                $part = trim($part);
-
-                if (!$part) {
-                    continue;
-                }
-                if ($value) {
-                    $value .= $separator==',' ? $separator.' ' : ' ';
-                } else {
-                    $value = $name . ': ';
-                }
-
-                // let's find phrase (name) and/or addr-spec
-                if (preg_match('/^<\S+@\S+>$/', $part)) {
-                    $value .= $part;
-                } else if (preg_match('/^\S+@\S+$/', $part)) {
-                    // address without brackets and without name
-                    $value .= $part;
-                } else if (preg_match('/<*\S+@\S+>*$/', $part, $matches)) {
-                    // address with name (handle name)
-                    $address = $matches[0];
-                    $word = str_replace($address, '', $part);
-                    $word = trim($word);
-                    // check if phrase requires quoting
-                    if ($word) {
-                        // non-ASCII: require encoding
-                        if (preg_match('#([\x80-\xFF]){1}#', $word)) {
-                            if ($word[0] == '"' && $word[strlen($word)-1] == '"') {
-                                // de-quote quoted-string, encoding changes
-                                // string to atom
-                                $search = array("\\\"", "\\\\");
-                                $replace = array("\"", "\\");
-                                $word = str_replace($search, $replace, $word);
-                                $word = substr($word, 1, -1);
-                            }
-                            // find length of last line
-                            if (($pos = strrpos($value, $eol)) !== false) {
-                                $last_len = strlen($value) - $pos;
-                            } else {
-                                $last_len = strlen($value);
-                            }
-                            $word = $this->_encodeString(
-                                $word, $charset, $encoding, $last_len
-                            );
-                        } else if (($word[0] != '"' || $word[strlen($word)-1] != '"')
-                            && preg_match('/[\(\)\<\>\\\.\[\]@,;:"]/', $word)
-                        ) {
-                            // ASCII: quote string if needed
-                            $word = '"'.addcslashes($word, '\\"').'"';
-                        }
-                    }
-                    $value .= $word.' '.$address;
-                } else {
-                    // addr-spec not found, don't encode (?)
-                    $value .= $part;
-                }
-
-                // RFC2822 recommends 78 characters limit, use 76 from RFC2047
-                $value = wordwrap($value, 76, $eol . ' ');
-            }
-
-            $value = preg_replace('/^'.$name.': /', '', $value);
-
-        } else {
-            // Unstructured header
-            // non-ASCII: require encoding
-            if (preg_match('#([\x80-\xFF]){1}#', $value)) {
-                if ($value[0] == '"' && $value[strlen($value)-1] == '"') {
-                    // de-quote quoted-string, encoding changes
-                    // string to atom
-                    $search = array("\\\"", "\\\\");
-                    $replace = array("\"", "\\");
-                    $value = str_replace($search, $replace, $value);
-                    $value = substr($value, 1, -1);
-                }
-                $value = $this->_encodeString(
-                    $value, $charset, $encoding, strlen($name) + 2
-                );
-            } else if (strlen($name.': '.$value) > 78) {
-                // ASCII: check if header line isn't too long and use folding
-                $value = preg_replace('/\r?\n[\s\t]*/', $eol . ' ', $value);
-                $tmp = wordwrap($name.': '.$value, 78, $eol . ' ');
-                $value = preg_replace('/^'.$name.':\s*/', '', $tmp);
-                // hard limit 998 (RFC2822)
-                $value = wordwrap($value, 998, $eol . ' ', true);
-            }
-        }
-
-        return $value;
-    }
-
-    /**
-     * Encodes a header value as per RFC2047
-     *
-     * @param string $value      The header data to encode
-     * @param string $charset    Character set name
-     * @param string $encoding   Encoding name (base64 or quoted-printable)
-     * @param int    $prefix_len Prefix length
-     *
-     * @return string            Encoded header data
-     * @access private
-     */
-    function _encodeString($value, $charset, $encoding, $prefix_len=0)
-    {
-        if ($encoding == 'base64') {
-            // Base64 encode the entire string
-            $value = base64_encode($value);
-
-            // Generate the header using the specified params and dynamicly 
-            // determine the maximum length of such strings.
-            // 75 is the value specified in the RFC.
-            $prefix = '=?' . $charset . '?B?';
-            $suffix = '?=';
-            $maxLength = 75 - strlen($prefix . $suffix) - 2;
-            $maxLength1stLine = $maxLength - $prefix_len;
-
-            // We can cut base4 every 4 characters, so the real max
-            // we can get must be rounded down.
-            $maxLength = $maxLength - ($maxLength % 4);
-            $maxLength1stLine = $maxLength1stLine - ($maxLength1stLine % 4);
-
-            $cutpoint = $maxLength1stLine;
-            $value_out = $value;
-            $output = '';
-            while ($value_out) {
-                // Split translated string at every $maxLength
-                $part = substr($value_out, 0, $cutpoint);
-                $value_out = substr($value_out, $cutpoint);
-                $cutpoint = $maxLength;
-                // RFC 2047 specifies that any split header should
-                // be seperated by a CRLF SPACE. 
-                if ($output) {
-                    $output .= $this->_build_params['eol'] . ' ';
-                }
-                $output .= $prefix . $part . $suffix;
-            }
-            $value = $output;
-        } else {
-            // quoted-printable encoding has been selected
-            $value = Mail_mimePart::encodeQP($value);
-
-            // Generate the header using the specified params and dynamicly 
-            // determine the maximum length of such strings.
-            // 75 is the value specified in the RFC.
-            $prefix = '=?' . $charset . '?Q?';
-            $suffix = '?=';
-            $maxLength = 75 - strlen($prefix . $suffix) - 3;
-            $maxLength1stLine = $maxLength - $prefix_len;
-            $maxLength = $maxLength - 1;
-
-            // This regexp will break QP-encoded text at every $maxLength
-            // but will not break any encoded letters.
-            $reg1st = "|(.{0,$maxLength1stLine}[^\=][^\=])|";
-            $reg2nd = "|(.{0,$maxLength}[^\=][^\=])|";
-
-            $value_out = $value;
-            $realMax = $maxLength1stLine + strlen($prefix . $suffix);
-            if (strlen($value_out) >= $realMax) {
-                // Begin with the regexp for the first line.
-                $reg = $reg1st;
-                $output = '';
-                while ($value_out) {
-                    // Split translated string at every $maxLength
-                    // But make sure not to break any translated chars.
-                    $found = preg_match($reg, $value_out, $matches);
-
-                    // After this first line, we need to use a different
-                    // regexp for the first line.
-                    $reg = $reg2nd;
-
-                    // Save the found part and encapsulate it in the
-                    // prefix & suffix. Then remove the part from the
-                    // $value_out variable.
-                    if ($found) {
-                        $part = $matches[0];
-                        $len = strlen($matches[0]);
-                        $value_out = substr($value_out, $len);
-                    } else {
-                        $part = $value_out;
-                        $value_out = "";
-                    }
-
-                    // RFC 2047 specifies that any split header should 
-                    // be seperated by a CRLF SPACE
-                    if ($output) {
-                        $output .= $this->_build_params['eol'] . ' ';
-                    }
-                    $output .= $prefix . $part . $suffix;
-                }
-                $value_out = $output;
-            } else {
-                $value_out = $prefix . $value_out . $suffix;
-            }
-            $value = $value_out;
-        }
-
-        return $value;
-    }
-
-    /**
-     * Explode quoted string
-     *
-     * @param string $delimiter Delimiter expression string for preg_match()
-     * @param string $string    Input string
-     *
-     * @return array            String tokens array
-     * @access private
-     */
-    function _explodeQuotedString($delimiter, $string)
-    {
-        $result = array();
-        $strlen = strlen($string);
-
-        for ($q=$p=$i=0; $i < $strlen; $i++) {
-            if ($string[$i] == "\""
-                && (empty($string[$i-1]) || $string[$i-1] != "\\")
-            ) {
-                $q = $q ? false : true;
-            } else if (!$q && preg_match("/$delimiter/", $string[$i])) {
-                $result[] = substr($string, $p, $i - $p);
-                $p = $i + 1;
-            }
-        }
-
-        $result[] = substr($string, $p);
-        return $result;
     }
 
     /**
@@ -1512,23 +1393,30 @@
             return $headers;
         }
 
+        $this->_checkParams();
+
         $eol = !empty($this->_build_params['eol'])
             ? $this->_build_params['eol'] : "\r\n";
 
         if ($headers['Content-Type'] == 'text/plain') {
             // single-part message: add charset and encoding
+            $charset = 'charset=' . $this->_build_params['text_charset'];
+            // place charset parameter in the same line, if possible
+            // 26 = strlen("Content-Type: text/plain; ")
             $headers['Content-Type']
-                .= ";$eol charset=" . $this->_build_params['text_charset'];
+                .= (strlen($charset) + 26 <= 76) ? "; $charset" : ";$eol $charset";
             $headers['Content-Transfer-Encoding']
                 = $this->_build_params['text_encoding'];
         } else if ($headers['Content-Type'] == 'text/html') {
             // single-part message: add charset and encoding
+            $charset = 'charset=' . $this->_build_params['html_charset'];
+            // place charset parameter in the same line, if possible
             $headers['Content-Type']
-                .= ";$eol charset=" . $this->_build_params['html_charset'];
+                .= (strlen($charset) + 25 <= 76) ? "; $charset" : ";$eol $charset";
             $headers['Content-Transfer-Encoding']
                 = $this->_build_params['html_encoding'];
         } else {
-            // multipart message: add charset and boundary
+            // multipart message: and boundary
             if (!empty($this->_build_params['boundary'])) {
                 $boundary = $this->_build_params['boundary'];
             } else if (!empty($this->_headers['Content-Type'])
@@ -1546,4 +1434,60 @@
         return $headers;
     }
 
+    /**
+     * Validate and set build parameters
+     *
+     * @return void
+     * @access private
+     */
+    function _checkParams()
+    {
+        $encodings = array('7bit', '8bit', 'base64', 'quoted-printable');
+
+        $this->_build_params['text_encoding']
+            = strtolower($this->_build_params['text_encoding']);
+        $this->_build_params['html_encoding']
+            = strtolower($this->_build_params['html_encoding']);
+
+        if (!in_array($this->_build_params['text_encoding'], $encodings)) {
+            $this->_build_params['text_encoding'] = '7bit';
+        }
+        if (!in_array($this->_build_params['html_encoding'], $encodings)) {
+            $this->_build_params['html_encoding'] = '7bit';
+        }
+
+        // text body
+        if ($this->_build_params['text_encoding'] == '7bit'
+            && !preg_match('/ascii/i', $this->_build_params['text_charset'])
+            && preg_match('/[^\x00-\x7F]/', $this->_txtbody)
+        ) {
+            $this->_build_params['text_encoding'] = 'quoted-printable';
+        }
+        // html body
+        if ($this->_build_params['html_encoding'] == '7bit'
+            && !preg_match('/ascii/i', $this->_build_params['html_charset'])
+            && preg_match('/[^\x00-\x7F]/', $this->_htmlbody)
+        ) {
+            $this->_build_params['html_encoding'] = 'quoted-printable';
+        }
+    }
+
+    /**
+     * PEAR::isError wrapper
+     *
+     * @param mixed $data Object
+     *
+     * @return bool True if object is an instance of PEAR_Error
+     * @access private
+     */
+    function _isError($data)
+    {
+        // PEAR::isError() is not PHP 5.4 compatible (see Bug #19473)
+        if (is_object($data) && is_a($data, 'PEAR_Error')) {
+            return true;
+        }
+
+        return false;
+    }
+
 } // End of class

--
Gitblit v1.9.1