* @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:
+ * <pre>
+ * '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
+ * </pre>
*
* @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();
$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;
$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.
*
* 'val' - (string) The header's "base" value.
* </pre>
*/
- 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
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) {
$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;
}
/* 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;
}
}
} 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));
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);
}
}
}
}
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);
}
}