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/mimePart.php |  189 +++++++++++++++++++++++++++-------------------
 1 files changed, 111 insertions(+), 78 deletions(-)

diff --git a/program/lib/Mail/mimePart.php b/program/lib/Mail/mimePart.php
index 55ea7a6..f3e75dd 100644
--- a/program/lib/Mail/mimePart.php
+++ b/program/lib/Mail/mimePart.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
  */
 
@@ -70,7 +70,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   Release: @package_version@
+ * @version   Release: 1.8.7
  * @link      http://pear.php.net/package/Mail_mime
  */
 class Mail_mimePart
@@ -131,6 +131,7 @@
     */
     var $_eol = "\r\n";
 
+
     /**
     * Constructor.
     *
@@ -141,18 +142,22 @@
     *     content_type      - The content type for this part eg multipart/mixed
     *     encoding          - The encoding to use, 7bit, 8bit,
     *                         base64, or quoted-printable
+    *     charset           - Content character set
     *     cid               - Content ID to apply
     *     disposition       - Content disposition, inline or attachment
-    *     dfilename         - Filename parameter for content disposition
+    *     filename          - Filename parameter for content disposition
     *     description       - Content description
-    *     charset           - Character set to use
-    *     name_encoding     - Encoding for attachment name (Content-Type)
+    *     name_encoding     - Encoding of the attachment name (Content-Type)
     *                         By default filenames are encoded using RFC2231
     *                         Here you can set RFC2047 encoding (quoted-printable
     *                         or base64) instead
-    *     filename_encoding - Encoding for attachment filename (Content-Disposition)
+    *     filename_encoding - Encoding of the attachment filename (Content-Disposition)
     *                         See 'name_encoding'
+    *     headers_charset   - Charset of the headers e.g. filename, description.
+    *                         If not set, 'charset' will be used
     *     eol               - End of line sequence. Default: "\r\n"
+    *     headers           - Hash array with additional part headers. Array keys can be
+    *                         in form of <header_name>:<parameter_name>
     *     body_file         - Location of file with part's body (instead of $body)
     *
     * @access public
@@ -165,14 +170,13 @@
             $this->_eol = MAIL_MIMEPART_CRLF;
         }
 
-        $c_type = array();
-        $c_disp = array();
+        // Additional part headers
+        if (!empty($params['headers']) && is_array($params['headers'])) {
+            $headers = $params['headers'];
+        }
+
         foreach ($params as $key => $value) {
             switch ($key) {
-            case 'content_type':
-                $c_type['type'] = $value;
-                break;
-
             case 'encoding':
                 $this->_encoding = $value;
                 $headers['Content-Transfer-Encoding'] = $value;
@@ -182,29 +186,6 @@
                 $headers['Content-ID'] = '<' . $value . '>';
                 break;
 
-            case 'disposition':
-                $c_disp['disp'] = $value;
-                break;
-
-            case 'dfilename':
-                $c_disp['filename'] = $value;
-                $c_type['name'] = $value;
-                break;
-
-            case 'description':
-                $headers['Content-Description'] = $value;
-                break;
-
-            case 'charset':
-                $c_type['charset'] = $value;
-                $c_disp['charset'] = $value;
-                break;
-
-            case 'language':
-                $c_type['language'] = $value;
-                $c_disp['language'] = $value;
-                break;
-
             case 'location':
                 $headers['Content-Location'] = $value;
                 break;
@@ -212,59 +193,89 @@
             case 'body_file':
                 $this->_body_file = $value;
                 break;
+
+            // for backward compatibility
+            case 'dfilename':
+                $params['filename'] = $value;
+                break;
             }
         }
 
         // Default content-type
-        if (empty($c_type['type'])) {
-            $c_type['type'] = 'text/plain';
+        if (empty($params['content_type'])) {
+            $params['content_type'] = 'text/plain';
         }
 
         // Content-Type
-        if (!empty($c_type['type'])) {
-            $headers['Content-Type'] = $c_type['type'];
-            if (!empty($c_type['charset'])) {
-                $charset = "charset={$c_type['charset']}";
-                // place charset parameter in the same line, if possible
-                if ((strlen($headers['Content-Type']) + strlen($charset) + 16) <= 76) {
-                    $headers['Content-Type'] .= '; ';
-                } else {
-                    $headers['Content-Type'] .= ';' . $this->_eol . ' ';
-                }
-                $headers['Content-Type'] .= $charset;
+        $headers['Content-Type'] = $params['content_type'];
+        if (!empty($params['charset'])) {
+            $charset = "charset={$params['charset']}";
+            // place charset parameter in the same line, if possible
+            if ((strlen($headers['Content-Type']) + strlen($charset) + 16) <= 76) {
+                $headers['Content-Type'] .= '; ';
+            } else {
+                $headers['Content-Type'] .= ';' . $this->_eol . ' ';
             }
-            if (!empty($c_type['name'])) {
-                $headers['Content-Type'] .= ';' . $this->_eol;
-                $headers['Content-Type'] .= $this->_buildHeaderParam(
-                    'name', $c_type['name'],
-                    isset($c_type['charset']) ? $c_type['charset'] : 'US-ASCII',
-                    isset($c_type['language']) ? $c_type['language'] : null,
-                    isset($params['name_encoding']) ?  $params['name_encoding'] : null
-                );
+            $headers['Content-Type'] .= $charset;
+
+            // Default headers charset
+            if (!isset($params['headers_charset'])) {
+                $params['headers_charset'] = $params['charset'];
             }
+        }
+
+        // header values encoding parameters
+        $h_charset  = !empty($params['headers_charset']) ? $params['headers_charset'] : 'US-ASCII';
+        $h_language = !empty($params['language']) ? $params['language'] : null;
+        $h_encoding = !empty($params['name_encoding']) ? $params['name_encoding'] : null;
+
+
+        if (!empty($params['filename'])) {
+            $headers['Content-Type'] .= ';' . $this->_eol;
+            $headers['Content-Type'] .= $this->_buildHeaderParam(
+                'name', $params['filename'], $h_charset, $h_language, $h_encoding
+            );
         }
 
         // Content-Disposition
-        if (!empty($c_disp['disp'])) {
-            $headers['Content-Disposition'] = $c_disp['disp'];
-            if (!empty($c_disp['filename'])) {
+        if (!empty($params['disposition'])) {
+            $headers['Content-Disposition'] = $params['disposition'];
+            if (!empty($params['filename'])) {
                 $headers['Content-Disposition'] .= ';' . $this->_eol;
                 $headers['Content-Disposition'] .= $this->_buildHeaderParam(
-                    'filename', $c_disp['filename'],
-                    isset($c_disp['charset']) ? $c_disp['charset'] : 'US-ASCII',
-                    isset($c_disp['language']) ? $c_disp['language'] : null,
-                    isset($params['filename_encoding']) ?  $params['filename_encoding'] : null
+                    'filename', $params['filename'], $h_charset, $h_language,
+                    !empty($params['filename_encoding']) ? $params['filename_encoding'] : null
                 );
+            }
+
+            // add attachment size
+            $size = $this->_body_file ? filesize($this->_body_file) : strlen($body);
+            if ($size) {
+                $headers['Content-Disposition'] .= ';' . $this->_eol . ' size=' . $size;
             }
         }
 
-        if (!empty($headers['Content-Description'])) {
+        if (!empty($params['description'])) {
             $headers['Content-Description'] = $this->encodeHeader(
-                'Content-Description', $headers['Content-Description'],
-                isset($c_type['charset']) ? $c_type['charset'] : 'US-ASCII',
-                isset($params['name_encoding']) ?  $params['name_encoding'] : 'quoted-printable',
+                'Content-Description', $params['description'], $h_charset, $h_encoding,
                 $this->_eol
             );
+        }
+
+        // Search and add existing headers' parameters
+        foreach ($headers as $key => $value) {
+            $items = explode(':', $key);
+            if (count($items) == 2) {
+                $header = $items[0];
+                $param  = $items[1];
+                if (isset($headers[$header])) {
+                    $headers[$header] .= ';' . $this->_eol;
+                }
+                $headers[$header] .= $this->_buildHeaderParam(
+                    $param, $value, $h_charset, $h_language, $h_encoding
+                );
+                unset($headers[$key]);
+            }
         }
 
         // Default encoding
@@ -304,7 +315,7 @@
             for ($i = 0; $i < count($this->_subparts); $i++) {
                 $encoded['body'] .= '--' . $boundary . $eol;
                 $tmp = $this->_subparts[$i]->encode();
-                if (PEAR::isError($tmp)) {
+                if ($this->_isError($tmp)) {
                     return $tmp;
                 }
                 foreach ($tmp['headers'] as $key => $value) {
@@ -327,7 +338,7 @@
                 @ini_set('magic_quotes_runtime', $magic_quote_setting);
             }
 
-            if (PEAR::isError($body)) {
+            if ($this->_isError($body)) {
                 return $body;
             }
             $encoded['body'] = $body;
@@ -379,7 +390,7 @@
             @ini_set('magic_quotes_runtime', $magic_quote_setting);
         }
 
-        return PEAR::isError($res) ? $res : $this->_headers;
+        return $this->_isError($res) ? $res : $this->_headers;
     }
 
     /**
@@ -414,7 +425,7 @@
             for ($i = 0; $i < count($this->_subparts); $i++) {
                 fwrite($fh, $f_eol . '--' . $boundary . $eol);
                 $res = $this->_subparts[$i]->_encodePartToFile($fh);
-                if (PEAR::isError($res)) {
+                if ($this->_isError($res)) {
                     return $res;
                 }
                 $f_eol = $eol;
@@ -429,7 +440,7 @@
             $res = $this->_getEncodedDataFromFile(
                 $this->_body_file, $this->_encoding, $fh
             );
-            if (PEAR::isError($res)) {
+            if ($this->_isError($res)) {
                 return $res;
             }
         }
@@ -657,8 +668,8 @@
         // RFC 2045:
         // value needs encoding if contains non-ASCII chars or is longer than 78 chars
         if (!preg_match('#[^\x20-\x7E]#', $value)) {
-            $token_regexp = '#([^\x21,\x23-\x27,\x2A,\x2B,\x2D'
-                . ',\x2E,\x30-\x39,\x41-\x5A,\x5E-\x7E])#';
+            $token_regexp = '#([^\x21\x23-\x27\x2A\x2B\x2D'
+                . '\x2E\x30-\x39\x41-\x5A\x5E-\x7E])#';
             if (!preg_match($token_regexp, $value)) {
                 // token
                 if (strlen($name) + strlen($value) + 3 <= $maxLength) {
@@ -680,7 +691,7 @@
 
         // RFC2231:
         $encValue = preg_replace_callback(
-            '/([^\x21,\x23,\x24,\x26,\x2B,\x2D,\x2E,\x30-\x39,\x41-\x5A,\x5E-\x7E])/',
+            '/([^\x21\x23\x24\x26\x2B\x2D\x2E\x30-\x39\x41-\x5A\x5E-\x7E])/',
             array($this, '_encodeReplaceCallback'), $value
         );
         $value = "$charset'$language'$encValue";
@@ -804,6 +815,7 @@
             'from', 'to', 'cc', 'bcc', 'sender', 'reply-to',
             'resent-from', 'resent-to', 'resent-cc', 'resent-bcc',
             'resent-sender', 'resent-reply-to',
+            'mail-reply-to', 'mail-followup-to',
             'return-receipt-to', 'disposition-notification-to',
         );
         $other_headers = array(
@@ -824,6 +836,9 @@
 
         // Structured header (make sure addr-spec inside is not encoded)
         if (!empty($separator)) {
+            // Simple e-mail address regexp
+            $email_regexp = '([^\s<]+|("[^\r\n"]+"))@\S+';
+
             $parts = Mail_mimePart::_explodeQuotedString($separator, $value);
             $value = '';
 
@@ -841,12 +856,12 @@
                 }
 
                 // let's find phrase (name) and/or addr-spec
-                if (preg_match('/^<\S+@\S+>$/', $part)) {
+                if (preg_match('/^<' . $email_regexp . '>$/', $part)) {
                     $value .= $part;
-                } else if (preg_match('/^\S+@\S+$/', $part)) {
+                } else if (preg_match('/^' . $email_regexp . '$/', $part)) {
                     // address without brackets and without name
                     $value .= $part;
-                } else if (preg_match('/<*\S+@\S+>*$/', $part, $matches)) {
+                } else if (preg_match('/<*' . $email_regexp . '>*$/', $part, $matches)) {
                     // address with name (handle name)
                     $address = $matches[0];
                     $word = str_replace($address, '', $part);
@@ -1211,4 +1226,22 @@
         return sprintf('%%%02X', ord($matches[1]));
     }
 
+    /**
+     * 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