Finish cleanup/fixes of RFC 2231 related stuff.
authorMichael M Slusarz <slusarz@curecanti.org>
Tue, 2 Dec 2008 20:20:26 +0000 (13:20 -0700)
committerMichael M Slusarz <slusarz@curecanti.org>
Tue, 2 Dec 2008 20:20:26 +0000 (13:20 -0700)
framework/Mime/lib/Horde/Mime.php
framework/Mime/lib/Horde/Mime/Headers.php

index 55fd4f9..3d54935 100644 (file)
@@ -371,59 +371,81 @@ class Horde_Mime
             $output[$name . (($wrap) ? ('*' . $i++) : '') . (($encode) ? '*' : '')] = $line;
         }
 
-        if (self::$brokenRFC2231 && !isset($output[$name])) {
-            $output[$name] = self::encode($val, $charset);
-        }
-
-        return $output;
+        return (self::$brokenRFC2231 && !isset($output[$name]))
+            ? array_merge(array($name => self::encode($val, $charset)), $output)
+            : $output;
     }
 
     /**
-     * Decodes a header string encoded pursuant to RFC 2231.
+     * Decodes a MIME parameter string pursuant to RFC 2183 & 2231
+     * (Content-Type and Content-Disposition headers).
      *
-     * @param string $string      The string to decode.
-     * @param string $to_charset  The charset the text should be decoded to.
+     * @param string $string   The full header to decode (including the header
+     *                         name).
+     * @param string $charset  The charset the text should be decoded to.
+     *                         Defaults to system charset.
      *
      * @return array  An array with the following entries:
      * <pre>
+     * 'params' - (array) The header's parameter values.
+     * 'val' - (string) The header's "base" value.
      * </pre>
      */
-    static public function decodeParamString($string, $to_charset = null)
+    static public function decodeParam($string, $charset = null)
     {
-        if (($pos = strpos($string, '*')) === false) {
-            return false;
+        $convert = array();
+        $ret = array('params' => array(), 'val' => '');
+
+        /* Give $string a bogus body part or else decode() will complain. */
+        require 'Mail/mimeDecode.php';
+        $mime_decode = new Mail_mimeDecode($string . "\n\nA");
+        $res = $mime_decode->decode();
+
+        /* Are we dealing with content-type or content-disposition? */
+        if ($res->ctype_primary) {
+            $ret['val'] = $res->ctype_primary . '/' . $res->ctype_secondary;
+            $params = $res->ctype_parameters;
+        } elseif ($res->disposition) {
+            $ret['val'] = $res->disposition;
+            $params = $res->d_parameters;
+        } else {
+            return $ret;
         }
 
-        $attribute = substr($string, 0, $pos);
-        $charset = $lang = null;
-        $output = '';
+        /* Sort the params list. Prevents us from having to manually keep
+         * track of continuation values below. */
+        uksort($params, 'strnatcasecmp');
 
-        /* Get the character set and language used in the encoding, if
-         * any. */
-        if (preg_match("/^[^=]+\*\=([^']*)'([^']*)'/", $string, $matches)) {
-            $charset = $matches[1];
-            $lang = $matches[2];
-            $string = str_replace($charset . "'" . $lang . "'", '', $string);
-        }
+        foreach ($params as $name => $val) {
+            /* Asterisk at end indicates encoded value. */
+            if (($encode = substr($name, -1)) == '*') {
+                $name = substr($name, 0, -1);
+                $val = urldecode($val);
+            }
 
-        $lines = preg_split('/\s*' . preg_quote($attribute) . '(?:\*\d)*/', $string);
-        foreach ($lines as $line) {
-            if (strpos($line, '*=') === 0) {
-                $output .= urldecode(str_replace(array('_', '='), array('%20', '%'), substr($line, 2)));
+            /* This asterisk indicates continuation parameter. */
+            if (($pos = strrpos($name, '*')) === false) {
+                $ret['params'][$name] = $val;
             } else {
-                $output .= substr($line, 1);
+                $first_part = ($encode && (substr($name, -2) == '*0'));
+                $name = substr($name, 0, $pos);
+                if ($first_part) {
+                    $quote = strpos($val, "'");
+                    $convert[$name] = substr($val, 0, $quote);
+                    /* Ignore language. */
+                    $quote = strpos($val, "'", $quote + 1);
+                    $ret['params'][$name] = substr($val, $quote + 1);
+                } else {
+                    $ret['params'][$name] .= $val;
+                }
             }
         }
 
-        /* RFC 2231 uses quoted printable encoding. */
-        if (!is_null($charset)) {
-            $output = String::convertCharset($output, $charset, $to_charset);
+        foreach ($convert as $key => $val) {
+            $ret['params'][$key] = String::convertCharset($ret['params'][$key], $val, $charset);
         }
 
-        return array(
-            'attribute' => $attribute,
-            'value' => $output
-        );
+        return $ret;
     }
 
     /**
index ceb521a..6f9c411 100644 (file)
@@ -57,6 +57,7 @@ class Horde_Mime_Headers
     {
         $charset = empty($options['charset']) ? null : $options['charset'];
         $address_keys = $charset ? array() : $this->addressFields();
+        $mime = $this->mimeParamFields();
         $ret = array();
 
         foreach ($this->_headers as $header => $ob) {
@@ -69,23 +70,21 @@ class Horde_Mime_Headers
                     if (is_a($text, 'PEAR_Error')) {
                         $text = $val[$key];
                     }
+                } elseif (in_array($header, $mime) && !empty($ob['params'])) {
+                    /* MIME encoded headers (RFC 2231). */
+                    $text = $val[$key];
+                    foreach ($ob['params'] as $name => $param) {
+                        foreach (Horde_Mime::encodeParam($name, $param, $charset) as $name2 => $param2) {
+                            /* MIME parameter quoting is identical to RFC 822
+                             * quoted-string encoding. See RFC 2045 [Appendix
+                             * A]. */
+                            $text .= '; ' . $name2 . '=' . Horde_Mime_Address::encode($param2, null);
+                        }
+                    }
                 } else {
                     $text = $charset
                         ? Horde_Mime::encode($val[$key], $charset)
                         : $val[$key];
-
-                    /* MIME encoded headers (RFC 2231). */
-                    if (in_array($header, array('content-type', 'content-disposition')) &&
-                        !empty($ob['params'])) {
-                        foreach ($ob['params'] as $name => $param) {
-                            foreach (Horde_Mime::encodeParam($name, $param, $charset) as $name2 => $param2) {
-                                /* MIME parameter quoting is identical to RFC
-                                 * 822 quoted-string encoding. See RFC 2045
-                                 * [Appendix A]. */
-                                $text .= '; ' . $name2 . '=' . Horde_Mime_Address::encode($param2, null);
-                            }
-                        }
-                    }
                 }
 
                 if (empty($options['nowrap'])) {
@@ -363,6 +362,7 @@ class Horde_Mime_Headers
      *   * To, From, Cc, Bcc, Date, Sender, Reply-to, Message-ID, In-Reply-To,
      *     References, Subject (RFC 2822 [3.6])
      *   * All List Headers (RFC 2369 [3])
+     * The values are not MIME encoded.
      *
      * @param string $header  The header to search for.
      *
@@ -373,15 +373,22 @@ class Horde_Mime_Headers
     {
         require_once 'Horde/String.php';
 
+        $entry = null;
         $header = String::lower($header);
 
         if (isset($this->_headers[$header])) {
-            return (is_array($this->_headers[$header]['value']) && in_array($header, $this->singleFields(true)))
-                ? $this->_headers[$header]['value'][0]
-                : $this->_headers[$header]['value'];
+            $ptr = &$this->_headers[$header];
+            $entry = (is_array($ptr['value']) && in_array($header, $this->singleFields(true)))
+                ? $ptr['value'][0]
+                : $ptr['value'];
+            if (isset($ptr['params'])) {
+                foreach ($ptr['params'] as $key => $val) {
+                    $entry .= '; ' . $key . '=' . $val;
+                }
+            }
         }
 
-        return null;
+        return $entry;
     }
 
     /**
@@ -421,6 +428,17 @@ class Horde_Mime_Headers
     }
 
     /**
+     * Returns the list of RFC defined MIME header fields that may contain
+     * parameter info.
+     *
+     * @return array  The list of headers, in lowercase.
+     */
+    public function mimeParamFields()
+    {
+        return array('content-type', 'content-disposition');
+    }
+
+    /**
      * Returns the list of valid mailing list headers.
      *
      * @return array  The list of valid mailing list headers.
@@ -498,6 +516,9 @@ class Horde_Mime_Headers
     {
         $headers = new Horde_Mime_Headers();
         $currheader = $currtext = null;
+        $mime = $this->mimeParamFields();
+
+        require_once 'Horde/String.php';
 
         foreach (explode("\n", $text) as $val) {
             $val = rtrim($val);
@@ -509,8 +530,12 @@ class Horde_Mime_Headers
                 $currtext .= ' ' . ltrim($val);
             } else {
                 if (!is_null($currheader)) {
-                    // TODO: RFC 2231
-                    $headers->addHeader($currheader, $currtext, array('decode' => true));
+                    if (in_array(String::lower($currheader), $mime)) {
+                        $res = Horde_Mime::decodeParam($currtext);
+                        $headers->addHeader($currheader, $res['val'], array('decode' => true, 'params' => $res['params']));
+                    } else {
+                        $headers->addHeader($currheader, $currtext, array('decode' => true));
+                    }
                 }
                 $pos = strpos($val, ':');
                 $currheader = substr($val, 0, $pos);