From: Michael M Slusarz Date: Fri, 20 Feb 2009 20:39:15 +0000 (-0700) Subject: Fix parsing of MIME parameter values. X-Git-Url: https://git.internetallee.de/?a=commitdiff_plain;h=c9651070b8f2626358d6943cbeaff7cfc4a1c57e;p=horde.git Fix parsing of MIME parameter values. Correctly parse messages containing both RFC 2045 and RFC 2231 param values. --- diff --git a/framework/Mime/lib/Horde/Mime.php b/framework/Mime/lib/Horde/Mime.php index fd78348a0..f9cebc4bf 100644 --- a/framework/Mime/lib/Horde/Mime.php +++ b/framework/Mime/lib/Horde/Mime.php @@ -336,11 +336,18 @@ class Horde_Mime * @param string $name The parameter name. * @param string $val The parameter value. * @param string $charset The charset the text should be encoded with. - * @param string $lang The language to use when encoding. + * @param array $opts Additional options: + *
+     * 'escape' - (boolean) If true, escape param values as described in
+     *            RFC 2045 [Appendix A].
+     *            DEFAULT: false
+     * 'lang' - (string) The language to use when encoding.
+     *          DEFAULT: None specified
+     * 
* * @return array The encoded parameter string. */ - static public function encodeParam($name, $val, $charset, $lang = null) + static public function encodeParam($name, $val, $charset, $opts = array()) { $encode = $wrap = false; $output = array(); @@ -350,7 +357,7 @@ class Horde_Mime $pre_len = strlen($name) + 2; if (self::is8bit($val, $charset)) { - $string = String::lower($charset) . '\'' . (is_null($lang) ? '' : String::lower($lang)) . '\'' . rawurlencode($val); + $string = String::lower($charset) . '\'' . (empty($opts['lang']) ? '' : String::lower($opts['lang'])) . '\'' . rawurlencode($val); $encode = true; /* Account for trailing '*'. */ ++$pre_len; @@ -389,17 +396,30 @@ class Horde_Mime $output[$name . (($wrap) ? ('*' . $i) : '') . (($encode) ? '*' : '')] = $line; } - return (self::$brokenRFC2231 && !isset($output[$name])) - ? array_merge(array($name => self::encode($val, $charset)), $output) - : $output; + if (self::$brokenRFC2231 && !isset($output[$name])) { + $output = array_merge(array($name => self::encode($val, $charset)), $output); + } + + /* Escape certain characters in params (See RFC 2045 [Appendix A]. */ + if (!empty($opts['escape'])) { + foreach (array_keys($output) as $key) { + if (strcspn($output[$key], "\11\40\"(),/:;<=>?@[\\]") != strlen($output[$key])) { + $output[$key] = '"' . addcslashes($output[$key], '\\"') . '"'; + } + } + } + + return $output; } /** * Decodes a MIME parameter string pursuant to RFC 2183 & 2231 * (Content-Type and Content-Disposition headers). * - * @param string $string The full header to decode (including the header - * name). + * @param string $type Either 'Content-Type' or 'Content-Disposition' + * (case-insensitive). + * @param mixed $data The text of the header or an array of + * param name => param values. * @param string $charset The charset the text should be decoded to. * Defaults to system charset. * @@ -409,25 +429,36 @@ class Horde_Mime * 'val' - (string) The header's "base" value. * */ - static public function decodeParam($string, $charset = null) + static public function decodeParam($type, $data, $charset = null) { $convert = array(); $ret = array('params' => array(), 'val' => ''); - /* Give $string a bogus body part or else decode() will complain. */ - require_once '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 (isset($res->disposition)) { - $ret['val'] = $res->disposition; - $params = isset($res->d_parameters) ? $res->d_parameters : array(); - } elseif (isset($res->ctype_primary)) { - $ret['val'] = $res->ctype_primary . '/' . $res->ctype_secondary; - $params = isset($res->ctype_parameters) ? $res->ctype_parameters : array(); + /* Kind of annoying: we have to re-encode in string form in order for + * Mail_mimeDecode to parse. */ + if (is_array($data)) { + // Use dummy base values + $ret['val'] = (String::lower($type) == 'content-type') + ? 'text/plain' + : 'attachment'; + $params = $data; } else { - return $ret; + /* Give $string a bogus body part or else decode() will + * complain. */ + require_once 'Mail/mimeDecode.php'; + $mime_decode = new Mail_mimeDecode($type . ': ' . $data . "\n\nA"); + $res = $mime_decode->decode(); + + /* Are we dealing with content-type or content-disposition? */ + if (isset($res->disposition)) { + $ret['val'] = $res->disposition; + $params = isset($res->d_parameters) ? $res->d_parameters : array(); + } elseif (isset($res->ctype_primary)) { + $ret['val'] = $res->ctype_primary . '/' . $res->ctype_secondary; + $params = isset($res->ctype_parameters) ? $res->ctype_parameters : array(); + } else { + return $ret; + } } /* Sort the params list. Prevents us from having to manually keep @@ -436,18 +467,23 @@ class Horde_Mime foreach ($params as $name => $val) { /* Asterisk at end indicates encoded value. */ - if (($encode = substr($name, -1)) == '*') { + if (substr($name, -1) == '*') { $name = substr($name, 0, -1); + $encode = true; + } else { + $encode = false; } /* This asterisk indicates continuation parameter. */ - if (($pos = strrpos($name, '*')) === false) { + if (($pos = strrpos($name, '*')) !== false) { $name = substr($name, 0, $pos); } - if (!isset($ret['params'][$name])) { + if (!isset($ret['params'][$name]) || + ($encode && !isset($convert[$name]))) { $ret['params'][$name] = ''; } + $ret['params'][$name] .= $val; if ($encode) { @@ -465,6 +501,16 @@ class Horde_Mime $ret['params'][$name] = String::convertCharset(urldecode(substr($val, $quote + 1)), $orig_charset, $charset); } + /* MIME parameters are supposed to be encoded via RFC 2231, but many + * mailers do RFC 2045 encoding instead. However, if we see at least + * one RFC 2231 encoding, then assume the sending mailer knew what + * it was doing. */ + if (empty($convert)) { + foreach (array_diff(array_keys($ret['params']), array_keys($convert)) as $name) { + $ret['params'][$name] = self::decode($ret['params'][$name]); + } + } + return $ret; } diff --git a/framework/Mime/lib/Horde/Mime/Headers.php b/framework/Mime/lib/Horde/Mime/Headers.php index 8c3906994..112652419 100644 --- a/framework/Mime/lib/Horde/Mime/Headers.php +++ b/framework/Mime/lib/Horde/Mime/Headers.php @@ -82,12 +82,7 @@ class Horde_Mime_Headers /* MIME encoded headers (RFC 2231). */ $text = $val[$key]; foreach ($ob['params'] as $name => $param) { - foreach (Horde_Mime::encodeParam($name, $param, $charset) as $name2 => $param2) { - /* Escape certain characters in params (See RFC - * 2045 [Appendix A]. */ - if (strcspn($param2, "\11\40\"(),/:;<=>?@[\\]") != strlen($param2)) { - $param2 = '"' . addcslashes($param2, '\\"') . '"'; - } + foreach (Horde_Mime::encodeParam($name, $param, $charset, array('escape' => true)) as $name2 => $param2) { $text .= '; ' . $name2 . '=' . $param2; } } @@ -541,7 +536,7 @@ class Horde_Mime_Headers } else { if (!is_null($currheader)) { if (in_array(String::lower($currheader), $mime)) { - $res = Horde_Mime::decodeParam($currheader . ': ' . $currtext); + $res = Horde_Mime::decodeParam($currheader, $currtext); $to_process[] = array($currheader, $res['val'], array('decode' => true, 'params' => $res['params'])); } else { $to_process[] = array($currheader, $currtext, array('decode' => true)); diff --git a/framework/Mime/lib/Horde/Mime/Part.php b/framework/Mime/lib/Horde/Mime/Part.php index c042ac8d9..e6b241a76 100644 --- a/framework/Mime/lib/Horde/Mime/Part.php +++ b/framework/Mime/lib/Horde/Mime/Part.php @@ -1475,12 +1475,9 @@ class Horde_Mime_Part if (isset($data['disposition'])) { $ob->setDisposition($data['disposition']); if (!empty($data['dparameters'])) { - foreach ($data['dparameters'] as $key => $val) { - /* Disposition parameters are supposed to be encoded via - * RFC 2231, but many mailers do RFC 2045 encoding - * instead. */ - // @todo: RFC 2231 decoding - $ob->setDispositionParameter($key, Horde_Mime::decode($val)); + $params = Horde_Mime::decodeParam('content-disposition', $data['dparameters']); + foreach ($params['params'] as $key => $val) { + $ob->setDispositionParameter($key, $val); } } } @@ -1494,11 +1491,9 @@ class Horde_Mime_Part } if (!empty($data['parameters'])) { - foreach ($data['parameters'] as $key => $val) { - /* Content-type parameters are supposed to be encoded via RFC - * 2231, but many mailers do RFC 2045 encoding instead. */ - // @todo: RFC 2231 decoding - $ob->setContentTypeParameter($key, Horde_Mime::decode($val)); + $params = Horde_Mime::decodeParam('content-type', $data['parameters']); + foreach ($params['params'] as $key => $val) { + $ob->setContentTypeParameter($key, $val); } }