diff --git a/framework/Mime/doc/Horde/Mime/UPGRADING b/framework/Mime/doc/Horde/Mime/UPGRADING index 560ace27148..34f4664133e 100644 --- a/framework/Mime/doc/Horde/Mime/UPGRADING +++ b/framework/Mime/doc/Horde/Mime/UPGRADING @@ -16,6 +16,19 @@ Upgrading to 2.5 - Horde_Mime + - $brokenRFC2231 + + This static property is deprecated. Use the 'broken_rfc2231' parameter + to Horde_Mime_ContentParam#encode() instead. + + - decodeParam() + + This method is deprecated. Use Horde_Mime_ContentParam#decode() instead. + + - encodeParam() + + This method is deprecated. Use Horde_Mime_ContentParam#encode() instead. + - generateMessageId() This method is deprecated. Use Horde_Mime_Headers#generateMessageId() @@ -31,9 +44,11 @@ Upgrading to 2.5 This method was added. + - Horde_Mime_ContentParam + - Horde_Mime_ContentParam_Decode - Horde_Mime_Uudecode - This class was added. + These classes were added. Upgrading to 2.4 diff --git a/framework/Mime/lib/Horde/Mime.php b/framework/Mime/lib/Horde/Mime.php index 0e36f6b2fcc..562ce7971ea 100644 --- a/framework/Mime/lib/Horde/Mime.php +++ b/framework/Mime/lib/Horde/Mime.php @@ -1,43 +1,5 @@ - * Copyright (c) 2003-2006, PEAR - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or - * without modification, are permitted provided that the following - * conditions are met: - * - * - Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - Neither the name of the authors, nor the names of its contributors - * may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF - * THE POSSIBILITY OF SUCH DAMAGE. - * * Copyright 1999-2014 Horde LLC (http://www.horde.org/) * * See the enclosed file COPYING for license information (LGPL). If you @@ -69,26 +31,6 @@ class Horde_Mime */ const EOL = "\r\n"; - /** - * The list of characters required to be quoted in MIME parameters - * (regular expression). - * - * @since 2.1.0 - * - * @var string - */ - const MIME_PARAM_QUOTED = '/[\x01-\x20\x22\x28\x29\x2c\x2f\x3a-\x40\x5b-\x5d]/'; - - /** - * Attempt to work around non RFC 2231-compliant MUAs by generating both - * a RFC 2047-like parameter name and also the correct RFC 2231 - * parameter. See: - * http://lists.horde.org/archives/dev/Week-of-Mon-20040426/014240.html - * - * @var boolean - */ - static public $brokenRFC2231 = false; - /** * Use windows-1252 charset when decoding ISO-8859-1 data? * @@ -319,222 +261,6 @@ function($ord) { return $out . substr($string, $old_pos); } - /** - * Encodes a MIME parameter string pursuant to RFC 2183 & 2231 - * (Content-Type and Content-Disposition headers). - * - * @param string $name The parameter name. - * @param string $val The parameter value (UTF-8). - * @param array $opts Additional options: - * - charset: (string) The charset to encode to. - * DEFAULT: UTF-8 - * - lang: (string) The language to use when encoding. - * DEFAULT: None specified - * - * @return array The encoded parameter string (US-ASCII). - */ - static public function encodeParam($name, $val, array $opts = array()) - { - $curr = 0; - $encode = $wrap = false; - $output = array(); - - $charset = isset($opts['charset']) - ? $opts['charset'] - : 'UTF-8'; - - // 2 = '=', ';' - $pre_len = strlen($name) + 2; - - /* Several possibilities: - * - String is ASCII. Output as ASCII (duh). - * - Language information has been provided. We MUST encode output - * to include this information. - * - String is non-ASCII, but can losslessly translate to ASCII. - * Output as ASCII (most efficient). - * - String is in non-ASCII, but doesn't losslessly translate to - * ASCII. MUST encode output (duh). */ - if (empty($opts['lang']) && !self::is8bit($val, 'UTF-8')) { - $string = $val; - } else { - $cval = Horde_String::convertCharset($val, 'UTF-8', $charset); - $string = Horde_String::lower($charset) . '\'' . (empty($opts['lang']) ? '' : Horde_String::lower($opts['lang'])) . '\'' . rawurlencode($cval); - $encode = true; - /* Account for trailing '*'. */ - ++$pre_len; - } - - if (($pre_len + strlen($string)) > 75) { - /* Account for continuation '*'. */ - ++$pre_len; - $wrap = true; - - while ($string) { - $chunk = 75 - $pre_len - strlen($curr); - $pos = min($chunk, strlen($string) - 1); - - /* Don't split in the middle of an encoded char. */ - if (($chunk == $pos) && ($pos > 2)) { - for ($i = 0; $i <= 2; ++$i) { - if ($string[$pos - $i] == '%') { - $pos -= $i + 1; - break; - } - } - } - - $lines[] = substr($string, 0, $pos + 1); - $string = substr($string, $pos + 1); - ++$curr; - } - } else { - $lines = array($string); - } - - foreach ($lines as $i => $line) { - $output[$name . (($wrap) ? ('*' . $i) : '') . (($encode) ? '*' : '')] = $line; - } - - 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]). - * Must be quoted-string if one of these exists. - * Forbidden: SPACE, CTLs, ()<>@,;:\"/[]?= */ - foreach ($output as $k => $v) { - if (preg_match(self::MIME_PARAM_QUOTED, $v)) { - $output[$k] = '"' . addcslashes($v, '\\"') . '"'; - } - } - - return $output; - } - - /** - * Decodes a MIME parameter string pursuant to RFC 2183 & 2231 - * (Content-Type and Content-Disposition headers). - * - * @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. - * - * @return array An array with the following entries (all strings in - * UTF-8): - * - params: (array) The header's parameter values. - * - val: (string) The header's "base" value. - */ - static public function decodeParam($type, $data) - { - $convert = array(); - $ret = array('params' => array(), 'val' => ''); - $splitRegex = '/([^;\'"]*[\'"]([^\'"]*([^\'"]*)*)[\'"][^;\'"]*|([^;]+))(;|$)/'; - $type = Horde_String::lower($type); - - if (is_array($data)) { - // Use dummy base values - $ret['val'] = ($type == 'content-type') - ? 'text/plain' - : 'attachment'; - $params = $data; - } else { - /* This code was adapted from PEAR's Mail_mimeDecode::. */ - if (($pos = strpos($data, ';')) === false) { - $ret['val'] = trim($data); - return $ret; - } - - $ret['val'] = trim(substr($data, 0, $pos)); - $data = trim(substr($data, ++$pos)); - $params = $tmp = array(); - - if (strlen($data) > 0) { - /* This splits on a semi-colon, if there's no preceeding - * backslash. */ - preg_match_all($splitRegex, $data, $matches); - - for ($i = 0, $cnt = count($matches[0]); $i < $cnt; ++$i) { - $param = $matches[0][$i]; - while (substr($param, -2) == '\;') { - $param .= $matches[0][++$i]; - } - $tmp[] = $param; - } - - for ($i = 0, $cnt = count($tmp); $i < $cnt; ++$i) { - $pos = strpos($tmp[$i], '='); - $p_name = trim(substr($tmp[$i], 0, $pos), "'\";\t\\ "); - $p_val = trim(str_replace('\;', ';', substr($tmp[$i], $pos + 1)), "'\";\t\\ "); - if (strlen($p_val) && ($p_val[0] == '"')) { - $p_val = substr($p_val, 1, -1); - } - - $params[$p_name] = $p_val; - } - } - /* End of code adapted from PEAR's Mail_mimeDecode::. */ - } - - /* Sort the params list. Prevents us from having to manually keep - * track of continuation values below. */ - uksort($params, 'strnatcasecmp'); - - foreach ($params as $name => $val) { - /* Asterisk at end indicates encoded value. */ - if (substr($name, -1) == '*') { - $name = substr($name, 0, -1); - $encode = true; - } else { - $encode = false; - } - - /* This asterisk indicates continuation parameter. */ - if (($pos = strrpos($name, '*')) !== false) { - $name = substr($name, 0, $pos); - } - - if (!isset($ret['params'][$name]) || - ($encode && !isset($convert[$name]))) { - $ret['params'][$name] = ''; - } - - $ret['params'][$name] .= $val; - - if ($encode) { - $convert[$name] = true; - } - } - - foreach (array_keys($convert) as $name) { - $val = $ret['params'][$name]; - $quote = strpos($val, "'"); - $orig_charset = substr($val, 0, $quote); - if (self::$decodeWindows1252 && - (Horde_String::lower($orig_charset) == 'iso-8859-1')) { - $orig_charset = 'windows-1252'; - } - /* Ignore language. */ - $quote = strpos($val, "'", $quote + 1); - substr($val, $quote + 1); - $ret['params'][$name] = Horde_String::convertCharset(urldecode(substr($val, $quote + 1)), $orig_charset, 'UTF-8'); - } - - /* 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; - } - /** * Performs MIME ID "arithmetic" on a given ID. * @@ -643,4 +369,46 @@ static public function uudecode($input) return iterator_to_array($input); } + /** + * @deprecated + */ + static public $brokenRFC2231 = false; + + /** + * @deprecated + */ + const MIME_PARAM_QUOTED = '/[\x01-\x20\x22\x28\x29\x2c\x2f\x3a-\x40\x5b-\x5d]/'; + + /** + * @deprecated Use Horde_Mime_ContentParam#encode() instead. + */ + static public function encodeParam($name, $val, array $opts = array()) + { + $cp = new Horde_Mime_ContentParam(array($name => $val)); + + return $cp->encode(array_merge(array( + 'broken_rfc2231' => self::$brokenRFC2231 + ), $opts)); + } + + /** + * @deprecated Use Horde_Mime_ContentParam instead. + */ + static public function decodeParam($type, $data) + { + $cp = new Horde_Mime_ContentParam(); + $cp->decode($data); + + if (!strlen($cp->value)) { + $cp->value = (Horde_String::lower($type) == 'content-type') + ? 'text/plain' + : 'attachment'; + } + + return array( + 'params' => $cp->params, + 'val' => $cp->value + ); + } + } diff --git a/framework/Mime/lib/Horde/Mime/ContentParam.php b/framework/Mime/lib/Horde/Mime/ContentParam.php new file mode 100644 index 00000000000..384a13d3164 --- /dev/null +++ b/framework/Mime/lib/Horde/Mime/ContentParam.php @@ -0,0 +1,280 @@ + + * @category Horde + * @copyright 2014 Horde LLC + * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1 + * @package Mime + * @since 2.5.0 + */ +class Horde_Mime_ContentParam +{ + /** + * Valid atext but not tspecials characters. + * + * See RFC 2045 [5.1] + */ + const ATEXT_NON_TSPECIAL = '!#$%&\'*+-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ^_`abcdefghijklmnopqrstuvwxyz{|}~'; + + /** + * Content-Type parameters. + * + * @var array + */ + public $params = array(); + + /** + * Content-Type value. + * + * @var string + */ + public $value; + + /** + * Constructor. + * + * @param mixed $data Either an array (interpreted as a list of + * parameters) or a string (interpreted as a RFC + * encoded parameter list). + */ + public function __construct($data = null) + { + if (!is_null($data)) { + if (is_array($data)) { + $this->params = $data; + } else { + $this->decode($data); + } + } + } + + /** + * Encodes a MIME content parameter string pursuant to RFC 2183 & 2231 + * (Content-Type and Content-Disposition headers). + * + * @param array $opts Options: + * - broken_rfc2231: (boolean) Attempt to work around non-RFC + * 2231-compliant MUAs by generating both a RFC + * 2047-like parameter name and also the correct RFC + * 2231 parameter + * DEFAULT: false + * - charset: (string) The charset to encode to. + * DEFAULT: UTF-8 + * - lang: (string) The language to use when encoding. + * DEFAULT: None specified + * + * @return array The encoded parameter string (US-ASCII). + */ + public function encode(array $opts = array()) + { + $opts = array_merge(array( + 'charset' => 'UTF-8', + ), $opts); + + $out = array(); + + foreach ($this->params as $key => $val) { + $out = array_merge($out, $this->_encode($key, $val, $opts)); + } + + return $out; + } + + /** + * @see encode() + */ + protected function _encode($name, $val, $opts) + { + $curr = 0; + $encode = $wrap = false; + $out = array(); + + // 2 = '=', ';' + $pre_len = strlen($name) + 2; + + /* Several possibilities: + * - String is ASCII. Output as ASCII (duh). + * - Language information has been provided. We MUST encode output + * to include this information. + * - String is non-ASCII, but can losslessly translate to ASCII. + * Output as ASCII (most efficient). + * - String is in non-ASCII, but doesn't losslessly translate to + * ASCII. MUST encode output (duh). */ + if (empty($opts['lang']) && !Horde_Mime::is8bit($val, 'UTF-8')) { + $string = $val; + } else { + $cval = Horde_String::convertCharset($val, 'UTF-8', $opts['charset']); + $string = Horde_String::lower($opts['charset']) . '\'' . (empty($opts['lang']) ? '' : Horde_String::lower($opts['lang'])) . '\'' . rawurlencode($cval); + $encode = true; + /* Account for trailing '*'. */ + ++$pre_len; + } + + if (($pre_len + strlen($string)) > 75) { + /* Account for continuation '*'. */ + ++$pre_len; + $wrap = true; + + while ($string) { + $chunk = 75 - $pre_len - strlen($curr); + $pos = min($chunk, strlen($string) - 1); + + /* Don't split in the middle of an encoded char. */ + if (($chunk == $pos) && ($pos > 2)) { + for ($i = 0; $i <= 2; ++$i) { + if ($string[$pos - $i] == '%') { + $pos -= $i + 1; + break; + } + } + } + + $lines[] = substr($string, 0, $pos + 1); + $string = substr($string, $pos + 1); + ++$curr; + } + } else { + $lines = array($string); + } + + foreach ($lines as $i => $line) { + $out[$name . (($wrap) ? ('*' . $i) : '') . (($encode) ? '*' : '')] = $line; + } + + if (!empty($opts['broken_rfc2231']) && !isset($out[$name])) { + $out = array_merge(array( + $name => Horde_Mime::encode($val, $opts['charset']) + ), $out); + } + + /* Escape certain characters in params (See RFC 2045 [Appendix A]). + * Must be quoted-string if one of these exists. + * Forbidden: SPACE, CTLs, ()<>@,;:\"/[]?= */ + foreach ($out as $k => $v) { + if (strlen($v) !== strspn($v, self::ATEXT_NON_TSPECIAL)) { + $out[$k] = '"' . addcslashes($v, '\\"') . '"'; + } + } + + return $out; + } + + /** + * Decodes a MIME content parameter string pursuant to RFC 2183 & 2231 + * (Content-Type and Content-Disposition headers). + * + * Stores value/parameter data in the current object. + * + * @param mixed $data Parameter data. Either an array or a string. + */ + public function decode($data) + { + $convert = array(); + + $this->params = array(); + $this->value = null; + + if (is_array($data)) { + $params = $data; + } else { + $parts = explode(';', $data, 2); + if (count($parts) === 1) { + $param = $parts[0]; + } else { + $value = trim($parts[0]); + if (strlen($value)) { + $this->decode($parts[0]); + if (empty($this->params)) { + $this->value = $value; + } + } + $param = $parts[1]; + } + + $decode = new Horde_Mime_ContentParam_Decode(); + $params = $decode->decode($param); + } + + /* Sort the params list. Prevents us from having to manually keep + * track of continuation values below. */ + uksort($params, 'strnatcasecmp'); + + foreach ($params as $name => $val) { + /* Asterisk at end indicates encoded value. */ + if (substr($name, -1) == '*') { + $name = substr($name, 0, -1); + $encoded = true; + } else { + $encoded = false; + } + + /* This asterisk indicates continuation parameter. */ + if ((($pos = strrpos($name, '*')) !== false) && + is_numeric(substr($name, $pos + 1))) { + $name = substr($name, 0, $pos); + } + + if (isset($this->params[$name])) { + $this->params[$name] .= $val; + } else { + $this->params[$name] = $val; + } + + if ($encoded) { + $convert[$name] = true; + } + } + + foreach (array_keys($convert) as $name) { + $val = $this->params[$name]; + $quote = strpos($val, "'"); + + if ($quote === false) { + $this->params[$name] = urldecode($val); + } else { + $orig_charset = substr($val, 0, $quote); + if (Horde_String::lower($orig_charset) == 'iso-8859-1') { + $orig_charset = 'windows-1252'; + } + + /* Ignore language. */ + $quote = strpos($val, "'", $quote + 1); + substr($val, $quote + 1); + $this->params[$name] = Horde_String::convertCharset( + urldecode(substr($val, $quote + 1)), + $orig_charset, + 'UTF-8' + ); + } + } + + /* 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 and didn't send any parameters RFC 2045 encoded. */ + if (empty($convert)) { + foreach ($this->params as $key => $val) { + $this->params[$key] = Horde_Mime::decode($val); + } + } + + if (empty($this->params) && is_string($data)) { + $this->value = trim($parts[0]); + } + } + +} diff --git a/framework/Mime/lib/Horde/Mime/ContentParam/Decode.php b/framework/Mime/lib/Horde/Mime/ContentParam/Decode.php new file mode 100644 index 00000000000..764b6a0707c --- /dev/null +++ b/framework/Mime/lib/Horde/Mime/ContentParam/Decode.php @@ -0,0 +1,103 @@ + + * + * @category Horde + * @copyright 2002-2014 Timo Sirainen + * @copyright 2014 Horde LLC + * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1 + * @package Mime + */ + +/** + * Decode MIME content parameter data (RFC 2045; 2183; 2231). + * + * @author Timo Sirainen + * @author Michael Slusarz + * @category Horde + * @copyright 2002-2014 Timo Sirainen + * @copyright 2014 Horde LLC + * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1 + * @package Mime + * @since 2.5.0 + */ +class Horde_Mime_ContentParam_Decode extends Horde_Mail_Rfc822 +{ + /** + * Decode content parameter data. + * + * @param string $data Parameter data. + * + * @return array List of parameter key/value combinations. + */ + public function decode($data) + { + $out = array(); + + $this->_data = $data; + $this->_datalen = strlen($data); + $this->_ptr = 0; + + while ($this->_curr() !== false) { + $this->_rfc822SkipLwsp(); + + $this->_rfc822ParseMimeToken($param); + + if (is_null($param) || ($this->_curr() != '=')) { + break; + } + + ++$this->_ptr; + $this->_rfc822SkipLwsp(); + + $value = ''; + + if ($this->_curr() == '"') { + try { + $this->_rfc822ParseQuotedString($value); + } catch (Horde_Mail_Exception $e) { + break; + } + } else { + $this->_rfc822ParseMimeToken($value); + if (is_null($value)) { + break; + } + } + + $out[$param] = $value; + + $this->_rfc822SkipLwsp(); + if ($this->_curr() != ';') { + break; + } + + ++$this->_ptr; + } + + return $out; + } + + /** + */ + protected function _rfc822ParseMimeToken(&$str) + { + $length = strspn($this->_data, Horde_Mime_ContentParam::ATEXT_NON_TSPECIAL, $this->_ptr); + + if ($length) { + $str = substr($this->_data, $this->_ptr, $length); + $this->_ptr += $length; + $this->_rfc822SkipLwsp(); + } else { + $str = null; + } + } + +} diff --git a/framework/Mime/lib/Horde/Mime/Headers.php b/framework/Mime/lib/Horde/Mime/Headers.php index b8eb9506f4a..42e8334237b 100644 --- a/framework/Mime/lib/Horde/Mime/Headers.php +++ b/framework/Mime/lib/Horde/Mime/Headers.php @@ -138,12 +138,13 @@ public function toArray(array $opts = array()) 'idn' => true )); } elseif (in_array($header, $mime) && !empty($ob['p'])) { - /* MIME encoded headers (RFC 2231). */ + /* MIME encoded parameters (RFC 2045/2183/2231). */ + $cp = new Horde_Mime_ContentParam($ob['p']); + $cp_encode = $cp->encode(array('charset' => $charset)); + $text = $val[$key]; - foreach ($ob['p'] as $name => $param) { - foreach (Horde_Mime::encodeParam($name, $param, array('charset' => $charset, 'escape' => true)) as $name2 => $param2) { - $text .= '; ' . $name2 . '=' . $param2; - } + foreach ($cp_encode as $key2 => $val2) { + $text .= '; ' . $key2 . '=' . $val2; } } else { $text = is_null($charset) @@ -707,9 +708,9 @@ static public function parseHeaders($text) } if (in_array(Horde_String::lower($val[0]), $mime)) { - $res = Horde_Mime::decodeParam($val[0], $val[1]); - $headers->addHeader($val[0], $res['val'], array( - 'params' => $res['params'], + $cp = new Horde_Mime_ContentParam($val[1]); + $headers->addHeader($val[0], $cp->value, array( + 'params' => $cp->params, 'sanity_check' => true )); } else { diff --git a/framework/Mime/package.xml b/framework/Mime/package.xml index 3c0da004aaa..e92824a2eaa 100644 --- a/framework/Mime/package.xml +++ b/framework/Mime/package.xml @@ -17,7 +17,7 @@ slusarz@horde.org yes - 2014-09-20 + 2014-09-24 2.5.0 2.5.0 @@ -28,6 +28,8 @@ LGPL-2.1 +* [mms] Use string-based ABNF-based parser for scanning MIME content parameters instead of a regular expression (Bug #13587). +* [mms] Moved content parameter handling methods out of Horde_Mime and into Horde_Mime_ContentParam. * [mms] Deprecated Horde_Mime::generateMessageId() and move to Horde_Mime_Headers. * [mms] Deprecated Horde_Mime::uudecode() and move to new Horde_Mime_Uudecode class. * [mms] Add Auto-Submitted header to outgoing MDN messages. @@ -45,6 +47,10 @@ + + + + @@ -325,6 +331,9 @@ + + + @@ -345,6 +354,7 @@ + @@ -469,6 +479,7 @@ + @@ -479,6 +490,7 @@ + @@ -568,6 +580,7 @@ + @@ -577,6 +590,7 @@ + @@ -1433,9 +1447,11 @@ stable stable - 2014-09-20 + 2014-09-24 LGPL-2.1 +* [mms] Use string-based ABNF-based parser for scanning MIME content parameters instead of a regular expression (Bug #13587). +* [mms] Moved content parameter handling methods out of Horde_Mime and into Horde_Mime_ContentParam. * [mms] Deprecated Horde_Mime::generateMessageId() and move to Horde_Mime_Headers. * [mms] Deprecated Horde_Mime::uudecode() and move to new Horde_Mime_Uudecode class. * [mms] Add Auto-Submitted header to outgoing MDN messages. diff --git a/framework/Mime/test/Horde/Mime/ContentParam/DecodeTest.php b/framework/Mime/test/Horde/Mime/ContentParam/DecodeTest.php new file mode 100644 index 00000000000..853e437ce4c --- /dev/null +++ b/framework/Mime/test/Horde/Mime/ContentParam/DecodeTest.php @@ -0,0 +1,78 @@ + + * @category Horde + * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1 + * @internal + * @package Mime + * @subpackage UnitTests + */ +class Horde_Mime_ContentParam_DecodeTest extends PHPUnit_Framework_TestCase +{ + /** + * @dataProvider decodeProvider + */ + public function testDecode($string, $expected) + { + $decode = new Horde_Mime_ContentParam_Decode(); + $res = $decode->decode($string); + + ksort($res); + + $this->assertEquals( + $expected, + $res + ); + } + + public function decodeProvider() + { + return array( + array( + 'foo=bar', + array( + 'foo' => 'bar' + ) + ), + array( + 'foofoo=b', + array( + 'foofoo' => 'b' + ) + ), + array( + 'f=barbar', + array( + 'f' => 'barbar' + ) + ), + array( + 'foo=bar; a=b', + array( + 'a' => 'b', + 'foo' => 'bar' + ) + ), + array( + ' foo = bar ; a =b ;c = d ', + array( + 'a' => 'b', + 'c' => 'd', + 'foo' => 'bar' + ) + ) + ); + } + +} diff --git a/framework/Mime/test/Horde/Mime/ContentParamTest.php b/framework/Mime/test/Horde/Mime/ContentParamTest.php new file mode 100644 index 00000000000..fba6cf87736 --- /dev/null +++ b/framework/Mime/test/Horde/Mime/ContentParamTest.php @@ -0,0 +1,189 @@ + + * @category Horde + * @copyright 2010-2014 Horde LLC + * @internal + * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1 + * @package Mime + * @subpackage UnitTests + */ +class Horde_Mime_ContentParamTest extends PHPUnit_Framework_TestCase +{ + /** + * @dataProvider encodeProvider + */ + public function testEncode($params, $opts, $expected) + { + $cp = new Horde_Mime_ContentParam($params); + + $this->assertEquals( + $expected, + $cp->encode($opts) + ); + } + + public function encodeProvider() + { + return array( + array( + array( + 'bar' => 'foo', + 'test' => str_repeat('a', 100) . '.txt' + ), + array( + 'broken_rfc2231' => true + ), + array( + 'bar' => 'foo', + 'test' => 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.txt', + 'test*0' =>'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + 'test*1' => 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.txt' + ) + ), + array( + array( + 'bar' => 'foo', + 'test' => str_repeat('a', 100) . '.txt' + ), + array( + 'broken_rfc2231' => false + ), + array( + 'bar' => 'foo', + 'test*0' =>'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + 'test*1' => 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.txt' + ) + ), + array( + array( + 'foo' => "\x01" + ), + array(), + array( + 'foo' => "\"\x01\"" + ) + ), + // Bug #12127 (part 1) + array( + array( + 'foo' => 'test' + ), + array( + 'broken_rfc2231' => true, + 'charset' => 'UTF-16LE' + ), + array( + 'foo' => 'test' + ) + ), + // Bug #12127 (part 2) + array( + array( + 'foo' => 'ā' + ), + array( + 'broken_rfc2231' => true, + 'charset' => 'UTF-16LE' + ), + array( + 'foo*' => "utf-16le''%01%01", + 'foo' => '"=?utf-16le?b?AQE=?="' + ) + ) + ); + } + + /** + * @dataProvider decodeProvider + */ + public function testDecode($in, $val_expected, $params_expected) + { + $cp = new Horde_Mime_ContentParam($in); + + $this->assertEquals( + $val_expected, + $cp->value + ); + + $this->assertEquals( + $params_expected, + $cp->params + ); + } + + public function decodeProvider() + { + return array( + array( + 'foo', + 'foo', + array() + ), + array( + 'foo=bar', + null, + array( + 'foo' => 'bar' + ) + ), + array( + 'test ; foo = bar ; baz = "goo"', + 'test', + array( + 'baz' => 'goo', + 'foo' => 'bar' + ) + ), + array( + 'test ; foo*1=B; foo*0="A"; foo*3=D; foo*2="C";foo*5=F;' . + 'foo*4="E"; foo*7=H; foo*6="G"; foo*9=J; foo*8=I; foo*11=L; ' . + 'bar = Z ; foo*10=K;', + 'test', + array( + 'bar' => 'Z', + 'foo' => 'ABCDEFGHIJKL' + ) + ), + // Bug #13587 + array( + "multipart/mixed; boundary=\"EPOC32-8'4Lqb7RwmJkJ+8bx'NRLMC2SXt1Ls'Gfpd0RMtxgP6JQFKj\"", + 'multipart/mixed', + array( + 'boundary' => "EPOC32-8'4Lqb7RwmJkJ+8bx'NRLMC2SXt1Ls'Gfpd0RMtxgP6JQFKj" + ) + ), + // Adapted from Dovecot's src/lib-mail/test-rfc2231-parser.c + array( + "key4*=us-ascii''foo" . + "; key*2=ba%" . + "; key2*0=a" . + "; key3*0*=us-ascii'en'xyz" . + "; key*0=\"foo\"" . + "; key2*1*=b%25" . + "; key3*1=plop%" . + "; key*1=baz", + null, + array( + 'key' => 'foobazba%', + 'key2' => 'ab%', + 'key3' => 'xyzplop%', + 'key4' => 'foo' + ) + ) + ); + } + +} diff --git a/framework/Mime/test/Horde/Mime/MimeTest.php b/framework/Mime/test/Horde/Mime/MimeTest.php index f14c04832e9..350eed41b62 100644 --- a/framework/Mime/test/Horde/Mime/MimeTest.php +++ b/framework/Mime/test/Horde/Mime/MimeTest.php @@ -20,30 +20,6 @@ */ class Horde_Mime_MimeTest extends PHPUnit_Framework_TestCase { - public function setUp() - { - Horde_Mime::$brokenRFC2231 = false; - } - - public function testRfc2231() - { - // Horde_Mime RFC 2231 & workaround for broken MUA's - $pname = 'test'; - $str = str_repeat('a', 100) . '.txt'; - $expected = array( - 'test' => 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.txt', - 'test*0' =>'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', - 'test*1' => 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.txt' - ); - - Horde_Mime::$brokenRFC2231 = true; - $this->assertEquals(Horde_Mime::encodeParam($pname, $str), $expected); - - Horde_Mime::$brokenRFC2231 = false; - unset($expected['test']); - $this->assertEquals(Horde_Mime::encodeParam($pname, $str), $expected); - } - public function testDecode() { $this->assertEquals( @@ -68,38 +44,6 @@ public function testIsChild() $this->assertFalse(Horde_Mime::isChild('1', '10.0')); } - public function testEncodeParamQuotesQuote() - { - $this->assertEquals( - array('foo' => "\"\x01\""), - Horde_Mime::encodeParam('foo', "\x01") - ); - } - - public function testBug12127() - { - Horde_Mime::$brokenRFC2231 = true; - - $this->assertEquals( - array( - 'foo' => 'test' - ), - Horde_Mime::encodeParam('foo', 'test', array( - 'charset' => 'UTF-16LE' - )) - ); - - $this->assertEquals( - array( - 'foo*' => "utf-16le''%01%01", - 'foo' => '"=?utf-16le?b?AQE=?="' - ), - Horde_Mime::encodeParam('foo', 'ā', array( - 'charset' => 'UTF-16LE' - )) - ); - } - public function testNullCharacterInEncodeOutput() { $this->assertEquals(