* The Horde_Mime:: class provides methods for dealing with various MIME (see,
* e.g., RFC 2045-2049; 2183; 2231) standards.
*
+ * -----
+ *
+ * This file contains code adapted from PEAR's Mail_mimeDecode library (v1.5).
+ *
+ * http://pear.php.net/package/Mail_mime
+ *
+ * This code appears in Horde_Mime::decodeParam().
+ *
+ * This code was originally released under this license:
+ *
+ * LICENSE: This LICENSE is in the BSD license style.
+ * Copyright (c) 2002-2003, Richard Heyes <richard@phpguru.org>
+ * Copyright (c) 2003-2006, PEAR <pear-group@php.net>
+ * 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-2009 The Horde Project (http://www.horde.org/)
*
* See the enclosed file COPYING for license information (LGPL). If you
{
$convert = array();
$ret = array('params' => array(), 'val' => '');
+ $splitRegex = '/([^;\'"]*[\'"]([^\'"]*([^\'"]*)*)[\'"][^;\'"]*|([^;]+))(;|$)/';
+ $type = Horde_String::lower($type);
if (is_array($data)) {
// Use dummy base values
- $ret['val'] = (Horde_String::lower($type) == 'content-type')
+ $ret['val'] = ($type == 'content-type')
? 'text/plain'
: 'attachment';
$params = $data;
} else {
- /* Give $string a bogus body part or else decode() will
- * complain. */
- $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 {
+ /* 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));
+
+ if (strlen($data) > 0) {
+ $params = $tmp = array();
+
+ /* 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 ($p_val[0] == '"') {
+ $p_val = substr($param_value, 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
*/
class Horde_Mime_Headers
{
+ /* Constants for getValue(). */
+ const VALUE_STRING = 1;
+ const VALUE_BASE = 2;
+ const VALUE_PARAMS = 3;
+
/**
* The default charset to use when parsing text parts with no charset
* information.
* The values are not MIME encoded.
*
* @param string $header The header to search for.
+ * @param integer $type The type of return:
+ * <pre>
+ * VALUE_STRING - Returns a string representation of the entire header.
+ * VALUE_BASE - Returns a string representation of the base value of the
+ * header. If this is not a header that allows parameters,
+ * this will be equivalent to VALUE_BASE.
+ * VALUE_PARAMS - Returns the list of parameters for this header. If this
+ * is not a header that allows parameters, this will be
+ * an empty array.
+ * </pre>
*
* @return mixed The value for the given header.
* If the header is not found, returns null.
*/
- public function getValue($header)
+ public function getValue($header, $type = self::VALUE_STRING)
{
- $entry = null;
$header = Horde_String::lower($header);
- if (isset($this->_headers[$header])) {
- $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;
- }
- }
+ if (!isset($this->_headers[$header])) {
+ return null;
}
- return $entry;
+ $ptr = &$this->_headers[$header];
+ $base = (is_array($ptr['value']) && in_array($header, $this->singleFields(true)))
+ ? $ptr['value'][0]
+ : $ptr['value'];
+ $params = isset($ptr['params']) ? $ptr['params'] : array();
+
+ switch ($type) {
+ case self::VALUE_BASE:
+ return $base;
+
+ case self::VALUE_PARAMS:
+ return $params;
+
+ case self::VALUE_STRING:
+ foreach ($params as $key => $val) {
+ $base .= '; ' . $key . '=' . $val;
+ }
+ return $base;
+ }
}
/**
$currtext .= ' ' . ltrim($val);
} else {
if (!is_null($currheader)) {
- if (in_array(Horde_String::lower($currheader), $mime)) {
- $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));
- }
+ $to_process[] = array($currheader, $currtext);
}
+
$pos = strpos($val, ':');
$currheader = substr($val, 0, $pos);
$currtext = ltrim(substr($val, $pos + 1));
}
}
- $to_process[] = array($currheader, $currtext, array('decode' => true));
+
+ if (!is_null($currheader)) {
+ $to_process[] = array($currheader, $currtext);
+ }
$headers = new Horde_Mime_Headers();
$eightbit_check = (self::$defaultCharset != 'us-ascii');
if ($eightbit_check && Horde_Mime::is8bit($val[1])) {
$val[1] = Horde_String::convertCharset($val[1], self::$defaultCharset);
}
- $headers->addHeader($val[0], $val[1], $val[2]);
+
+ if (in_array(Horde_String::lower($val[0]), $mime)) {
+ $res = Horde_Mime::decodeParam($val[0], $val[1]);
+ $headers->addHeader($val[0], $res['val'], array('decode' => true, 'params' => $res['params']));
+ } else {
+ $headers->addHeader($val[0], $val[1], array('decode' => true));
+ }
}
return $headers;
*/
static public function parseMessage($text, $options = array())
{
- /* Set up the options for the mimeDecode class. */
- $decode_args = array(
- 'include_bodies' => true,
- 'decode_bodies' => false,
- 'decode_headers' => false
- );
+ /* Find the header. */
+ list($hdr_pos, $eol) = self::_findHeader($text);
- $mimeDecode = new Mail_mimeDecode($text, Horde_Mime_Part::EOL);
- if (!($ob = $mimeDecode->decode($decode_args))) {
- throw new Horde_Mime_Exception('Could not decode MIME message.');
- }
-
- $ob = self::_convertMimeDecodeData($ob);
+ $ob = self::_getStructure(substr($text, 0, $hdr_pos), substr($text, $hdr_pos + $eol));
return empty($options['structure'])
? self::parseStructure($ob)
}
/**
- * Convert the output from Mail_mimeDecode::decode() into a structure that
- * parseStructure() can handle.
+ * Creates a structure object from the text of one part of a MIME message.
*
- * @param stdClass $ob The output from Mail_mimeDecode::decode().
+ * @param string $header The header text.
+ * @param string $body The body text.
+ * @param string $ctype The default content-type.
*
- * @return array An array of structure information.
+ * @return array See Horde_Mime_Part::parseStructure().
*/
- static protected function _convertMimeDecodeData($ob)
+ static protected function _getStructure($header, $body,
+ $ctype = 'application/octet-stream')
{
- /* Primary content-type. */
- if (isset($ob->ctype_primary)) {
- $part = array(
- 'type' => strtolower($ob->ctype_primary),
- 'subtype' => isset($ob->ctype_secondary) ? strtolower($ob->ctype_secondary) : 'x-unknown'
- );
- } else {
- $part = array(
- 'type' => 'application',
- 'subtype' => 'octet-stream'
- );
+ $part = array('parts' => array());
+
+ /* Parse headers text into a Horde_Mime_Headers object. */
+ $hdrs = Horde_Mime_Headers::parseHeaders($header);
+
+ /* Content type. */
+ $tmp = $hdrs->getValue('content-type', Horde_Mime_Headers::VALUE_BASE);
+ if (!$tmp) {
+ $tmp = $ctype;
}
+ list($part['type'], $part['subtype']) = explode('/', strtolower($tmp), 2);
/* Content transfer encoding. */
- if (isset($ob->headers['content-transfer-encoding'])) {
- $part['encoding'] = strtolower($ob->headers['content-transfer-encoding']);
+ $tmp = $hdrs->getValue('content-transfer-encoding');
+ if ($tmp) {
+ $part['encoding'] = strtolower($tmp);
}
- /* Content-type and Disposition parameters. */
- $param_types = array(
- 'ctype_parameters' => 'parameters',
- 'd_parameters' => 'dparameters'
- );
-
- foreach ($param_types as $param_key => $param_value) {
- if (isset($ob->$param_key)) {
- $part[$param_value] = array();
- foreach ($ob->$param_key as $key => $val) {
- $part[$param_value][strtolower($key)] = $val;
- }
- }
- }
+ /* Content-Type and Disposition parameters. */
+ $part['dparameters'] = $hdrs->getValue('content-disposition', Horde_Mime_Headers::VALUE_PARAMS);
+ $part['parameters'] = $hdrs->getValue('content-type', Horde_Mime_Headers::VALUE_PARAMS);
/* Content-Description. */
- if (isset($ob->headers['content-description'])) {
- $part['description'] = $ob->headers['content-description'];
+ $tmp = $hdrs->getValue('content-description');
+ if ($tmp) {
+ $part['description'] = $tmp;
}
/* Content-Disposition. */
- if (isset($ob->headers['content-disposition'])) {
- $hdr = $ob->headers['content-disposition'];
- $pos = strpos($hdr, ';');
- if ($pos !== false) {
- $hdr = substr($hdr, 0, $pos);
- }
- $part['disposition'] = strtolower($hdr);
+ $tmp = $hdrs->getValue('content-disposition', Horde_Mime_Headers::VALUE_BASE);
+ if ($tmp) {
+ $part['disposition'] = strtolower($tmp);
}
/* Content-ID. */
- if (isset($ob->headers['content-id'])) {
- $part['id'] = $ob->headers['content-id'];
+ $tmp = $hdrs->getValue('content-id');
+ if ($tmp) {
+ $part['id'] = $tmp;
}
/* Get file size (if 'body' text is set). */
- if (isset($ob->body)) {
- $part['contents'] = $ob->body;
+ if (!empty($body)) {
+ $part['contents'] = $body;
if (($part['type'] != 'message') &&
($part['subtype'] != 'rfc822')) {
- /* Mail_mimeDecode puts an extra linebreak at the end of body
- * text. */
- $size = strlen(str_replace(array("\r\n", "\n"), array("\n", "\r\n"), $ob->body)) - 2;
- $part['size'] = ($size < 0) ? 0 : $size;
+ $part['size'] = strlen(str_replace(array("\r\n", "\n"), array("\n", "\r\n"), $body));
}
}
- /* Process parts also. */
- if (isset($ob->parts)) {
- $part['parts'] = array();
- reset($ob->parts);
- while (list($key,) = each($ob->parts)) {
- $part['parts'][] = self::_convertMimeDecodeData($ob->parts[$key]);
+ /* Process subparts. */
+ switch ($part['type']) {
+ case 'message':
+ if ($part['subtype'] == 'rfc822') {
+ $part['parts'][] = self::parseMessage($body, array('structure' => true));
+ }
+ break;
+
+ case 'multipart':
+ $tmp = $hdrs->getValue('content-type', Horde_Mime_Headers::VALUE_PARAMS);
+ if (isset($tmp['boundary'])) {
+ $b_find = self::_findBoundary($body, 0, $tmp['boundary']);
+ foreach ($b_find as $val) {
+ $subpart = substr($body, $val['start'], $val['length']);
+ list($hdr_pos, $eol) = self::_findHeader($subpart);
+ $part['parts'][] = self::_getStructure(substr($subpart, 0, $hdr_pos), substr($subpart, $hdr_pos + $eol), ($part['subtype'] == 'digest') ? 'message/rfc822' : 'text/plain');
+ }
}
+ break;
}
return $part;
*/
static public function getRawPartText($text, $type, $id)
{
- return self::_getRawPartText($text, $type, $id, null);
- }
-
- /**
- * Obtain the raw text of a MIME part.
- *
- * @param string $text The full text of the MIME message.
- * @param string $type Either 'header' or 'body'.
- * @param string $id The MIME ID.
- * @param string $boundary The boundary string.
- *
- * @return string The raw text.
- * @throws Horde_Mime_Exception
- */
- static protected function _getRawPartText($text, $type, $id,
- $boundary = null)
- {
/* We need to carry around the trailing "\n" because this is needed
* to correctly find the boundary string. */
- $hdr_pos = strpos($text, "\n\n");
- if ($hdr_pos === false) {
- $hdr_pos = strpos($text, "\r\n\r\n");
- $curr_pos = $hdr_pos + 3;
- } else {
- $curr_pos = $hdr_pos + 1;
- }
+ list($hdr_pos, $eol) = self::_findHeader($text);
+ $curr_pos = $hdr_pos + $eol - 1;
if ($id == 0) {
switch ($type) {
case 'body':
- if (is_null($boundary)) {
- return substr($text, $curr_pos + 1);
- }
- $end_boundary = strpos($text, "\n--" . $boundary, $curr_pos);
- if ($end_boundary === false) {
- throw new Horde_Mime_Exception('Could not find MIME part.');
- }
- return substr($text, $curr_pos + 1, $end_boundary - $curr_pos);
+ return substr($text, $curr_pos + 1);
case 'header':
return trim(substr($text, 0, $hdr_pos));
}
}
+ $hdr_ob = Horde_Mime_Headers::parseHeaders(trim(substr($text, 0, $hdr_pos)));
+
+ /* If this is a message/rfc822, pass the body into the next loop.
+ * Don't decrement the ID here. */
+ if ($hdr_ob->getValue('Content-Type', Horde_Mime_Headers::VALUE_BASE) == 'message/rfc822') {
+ return self::getRawPartText(substr($text, $curr_pos + 1), $type, $id);
+ }
+
$base_pos = strpos($id, '.');
if ($base_pos !== false) {
$base_pos = substr($id, 0, $base_pos);
- $id = substr($id, $base_pos + 1);
+ $id = substr($id, $base_pos);
} else {
$base_pos = $id;
$id = 0;
}
- $hdr_ob = Horde_Mime_Headers::parseHeaders(trim(substr($text, 0, $hdr_pos)));
- $params = Horde_Mime::decodeParam('content-type', $hdr_ob->getValue('Content-Type'));
- if (!isset($params['params']['boundary'])) {
+ $params = $hdr_ob->getValue('Content-Type', Horde_Mime_Headers::VALUE_PARAMS);
+ if (!isset($params['boundary'])) {
throw new Horde_Mime_Exception('Could not find MIME part.');
}
- $search = "\n--" . $params['params']['boundary'];
+ $b_find = self::_findBoundary($text, $curr_pos, $params['boundary'], $base_pos);
+
+ if (!isset($b_find[$base_pos])) {
+ throw new Horde_Mime_Exception('Could not find MIME part.');
+ }
+
+ return self::getRawPartText(substr($text, $b_find[$base_pos]['start'], $b_find[$base_pos]['length']), $type, $id);
+ }
+
+ /**
+ * Find the location of the end of the header text.
+ *
+ * @param string $text The text to search.
+ *
+ * @return array 1st element: Header position, 2nd element: Lenght of
+ * trailing EOL.
+ */
+ static protected function _findHeader($text)
+ {
+ $hdr_pos = strpos($text, "\r\n\r\n");
+ if ($hdr_pos !== false) {
+ return array($hdr_pos, 4);
+ }
+
+ $hdr_pos = strpos($text, "\n\n");
+ return ($hdr_pos === false)
+ ? array(strlen($text), 0)
+ : array($hdr_pos, 2);
+ }
+
+ /**
+ * Find the location of the next boundary string.
+ *
+ * @param string $text The text to search.
+ * @param integer $pos The current position in $text.
+ * @param string $boundary The boundary string.
+ * @param integer $end If set, return after matching this many
+ * boundaries.
+ *
+ * @return array Keys are the boundary number, values are an array with
+ * two elements: 'start' and 'length'.
+ */
+ static protected function _findBoundary($text, $pos, $boundary,
+ $end = null)
+ {
+ $i = 0;
+ $out = array();
+
+ $search = "\n--" . $boundary;
$search_len = strlen($search);
- for ($i = 0; $i < $base_pos; ++$i) {
- $new_pos = strpos($text, $search, $curr_pos);
- if ($new_pos !== false) {
- $curr_pos = $new_pos + $search_len;
- if (isset($text[$curr_pos + 1])) {
- switch ($text[$curr_pos + 1]) {
- case "\r":
- ++$curr_pos;
- break;
-
- case "\n":
- // noop
- break;
-
- case '-':
- throw new Horde_Mime_Exception('Could not find MIME part.');
- }
+ while (($pos = strpos($text, $search, $pos)) !== false) {
+ if (isset($out[$i])) {
+ $out[$i]['length'] = $pos - $out[$i]['start'];
+ }
+
+ if (!is_null($end) && ($end == $i)) {
+ break;
+ }
+
+ $pos += $search_len;
+ if (isset($text[$pos])) {
+ switch ($text[$pos]) {
+ case "\r":
+ $pos += 2;
+ $out[++$i] = array('start' => $pos);
+ break;
+
+ case "\n":
+ $out[++$i] = array('start' => ++$pos);
+ break;
+
+ case '-':
+ return $out;
}
}
}
- return self::_getRawPartText(substr($text, $curr_pos), $type, $id, $params['params']['boundary']);
+ return $out;
}
}
<api>alpha</api>
</stability>
<license uri="http://www.gnu.org/copyleft/lesser.html">LGPL</license>
- <notes>* Added Horde_Mime::uudecode().
+ <notes>* Remove dependence on PEAR's Mail_mimeDecode::.
+ * Added Horde_Mime::uudecode().
* Remove support for deprecated mime_magic module.
* Use Gnumeric to display MS Excel documents.
* Use AbiWord to display MS Word documents (Request #8011).
<dir name="fixtures">
<file name="attachment.bin" role="test" />
<file name="bug_325.txt" role="test" />
+ <file name="sample_msg.txt" role="test" />
<file name="url1.html" role="test" />
<file name="url2.html" role="test" />
<file name="url3.html" role="test" />
<file name="mail_007.phpt" role="test" />
<file name="mail_008.phpt" role="test" />
<file name="mail_dummy.inc" role="test" />
+ <file name="parse_001.phpt" role="test" />
+ <file name="parse_002.phpt" role="test" />
+ <file name="parse_003.phpt" role="test" />
<file name="rfc2231.phpt" role="test" />
<file name="url.phpt" role="test" />
<file name="viewer_php.phpt" role="test" />
<channel>pear.horde.org</channel>
</package>
<package>
- <name>Mail_mimeDecode</name>
- <channel>pear.php.net</channel>
- </package>
- <package>
<name>Util</name>
<channel>pear.horde.org</channel>
</package>
--- /dev/null
+Return-Path: <test@example.com>
+Delivered-To: foo@example.com
+Received: from localhost (localhost [127.0.0.1])
+ by example.com (Postfix) with ESMTP id 87C5B4F220
+ for <foo@example.com>; Tue, 7 Jul 2009 11:51:18 -0600 (MDT)
+Message-ID: <20090707115118.ooc4wo40oc88@example.com>
+Date: Tue, 07 Jul 2009 11:51:18 -0600
+From: "Test Q. User" <test@example.com>
+To: foo@example.com
+Subject: Fwd: Test
+User-Agent: Internet Messaging Program (IMP) H4 (5.0-git)
+Content-Type: multipart/mixed; boundary="=_k4kgcwkwggwc"
+MIME-Version: 1.0
+Content-Transfer-Encoding: 7bit
+
+This message is in MIME format.
+
+--=_k4kgcwkwggwc
+Content-Type: text/plain; charset=UTF-8; format=flowed; DelSp=Yes
+Content-Disposition: inline
+Content-Transfer-Encoding: 7bit
+
+Test.
+
+
+--=_k4kgcwkwggwc
+Content-Type: message/rfc822; name="Forwarded Message"
+
+Return-Path: <foo@example.com>
+Delivered-To: test@example.com
+Received: from localhost (localhost [127.0.0.1])
+ by example.com (Postfix) with ESMTP id B09464F220
+ for <test@example.com>; Tue, 7 Jul 2009 11:49:00 -0600 (MDT)
+Message-ID: <20090707114900.4w4ksggowkc4@example.com>
+Date: Tue, 07 Jul 2009 11:49:00 -0600
+From: Foo <foo@example.com>
+To: test@example.com
+Subject: Test
+User-Agent: Internet Messaging Program (IMP) H4 (5.0-git)
+Content-Type: multipart/mixed; boundary="=_8w0gkwkgk44o"
+MIME-Version: 1.0
+Content-Transfer-Encoding: 7bit
+
+This message is in MIME format.
+
+--=_8w0gkwkgk44o
+Content-Type: text/plain; charset=UTF-8; format=flowed; DelSp=Yes
+Content-Disposition: inline
+Content-Transfer-Encoding: 7bit
+
+Test text.
+
+
+--=_8w0gkwkgk44o
+Content-Type: text/plain; charset=UTF-8; name=test.txt
+Content-Disposition: attachment; filename=test.txt
+Content-Transfer-Encoding: 7bit
+
+Test.
+
+--=_8w0gkwkgk44o--
+
+
+--=_k4kgcwkwggwc
+Content-Type: image/png; name=index.png
+Content-Disposition: attachment; filename=index.png
+Content-Transfer-Encoding: base64
+
+iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAABGdBTUEAAK/INwWK6QAAABl0RVh0
+U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAADoSURBVBgZBcExblNBGAbA2ceegTRBuIKO
+giihSZNTcC5LUHAihNJR0kGKCDcYJY6D3/77MdOinTvzAgCw8ysThIvn/VojIyMjIyPP+bS1sUQI
+V2s95pBDDvmbP/mdkft83tpYguZq5Jh/OeaYh+yzy8hTHvNlaxNNczm+la9OTlar1UdA/+C2A4tr
+RCnD3jS8BB1obq2Gk6GU6QbQAS4BUaYSQAf4bhhKKTFdAzrAOwAxEUAH+KEM01SY3gM6wBsEAQB0
+gJ+maZoC3gI6iPYaAIBJsiRmHU0AALOeFC3aK2cWAACUXe7+AwO0lc9eTHYTAAAAAElFTkSuQmCC
+--=_k4kgcwkwggwc--
--- /dev/null
+--TEST--
+Horde_Mime_Part::parseMessage() [Structure array] test
+--FILE--
+<?php
+
+require_once dirname(__FILE__) . '/../../../lib/Horde/Mime.php';
+require_once dirname(__FILE__) . '/../../../lib/Horde/Mime/Address.php';
+require_once dirname(__FILE__) . '/../../../lib/Horde/Mime/Headers.php';
+require_once dirname(__FILE__) . '/../../../lib/Horde/Mime/Part.php';
+require_once 'Horde/String.php';
+require_once 'Mail/RFC822.php';
+
+$data = file_get_contents(dirname(__FILE__) . '/fixtures/sample_msg.txt');
+
+print_r(Horde_Mime_Part::parseMessage($data, array('structure' => true)));
+
+?>
+--EXPECTF--
+Array
+(
+ [parts] => Array
+ (
+ [0] => Array
+ (
+ [parts] => Array
+ (
+ )
+
+ [subtype] => plain
+ [type] => text
+ [encoding] => 7bit
+ [dparameters] => Array
+ (
+ )
+
+ [parameters] => Array
+ (
+ [charset] => UTF-8
+ [DelSp] => Yes
+ [format] => flowed
+ )
+
+ [disposition] => inline
+ [contents] => Test.
+
+
+ [size] => 9
+ )
+
+ [1] => Array
+ (
+ [parts] => Array
+ (
+ [0] => Array
+ (
+ [parts] => Array
+ (
+ [0] => Array
+ (
+ [parts] => Array
+ (
+ )
+
+ [subtype] => plain
+ [type] => text
+ [encoding] => 7bit
+ [dparameters] => Array
+ (
+ )
+
+ [parameters] => Array
+ (
+ [charset] => UTF-8
+ [DelSp] => Yes
+ [format] => flowed
+ )
+
+ [disposition] => inline
+ [contents] => Test text.
+
+
+ [size] => 14
+ )
+
+ [1] => Array
+ (
+ [parts] => Array
+ (
+ )
+
+ [subtype] => plain
+ [type] => text
+ [encoding] => 7bit
+ [dparameters] => Array
+ (
+ [filename] => test.txt
+ )
+
+ [parameters] => Array
+ (
+ [charset] => UTF-8
+ [name] => test.txt
+ )
+
+ [disposition] => attachment
+ [contents] => Test.
+
+ [size] => 7
+ )
+
+ )
+
+ [subtype] => mixed
+ [type] => multipart
+ [encoding] => 7bit
+ [dparameters] =>
+ [parameters] => Array
+ (
+ [boundary] => =_8w0gkwkgk44o
+ )
+
+ [contents] => This message is in MIME format.
+
+--=_8w0gkwkgk44o
+Content-Type: text/plain; charset=UTF-8; format=flowed; DelSp=Yes
+Content-Disposition: inline
+Content-Transfer-Encoding: 7bit
+
+Test text.
+
+
+--=_8w0gkwkgk44o
+Content-Type: text/plain; charset=UTF-8; name=test.txt
+Content-Disposition: attachment; filename=test.txt
+Content-Transfer-Encoding: 7bit
+
+Test.
+
+--=_8w0gkwkgk44o--
+
+
+ [size] => 392
+ )
+
+ )
+
+ [subtype] => rfc822
+ [type] => message
+ [dparameters] =>
+ [parameters] => Array
+ (
+ [name] => Forwarded Message
+ )
+
+ [contents] => Return-Path: <foo@example.com>
+Delivered-To: test@example.com
+Received: from localhost (localhost [127.0.0.1])
+ by example.com (Postfix) with ESMTP id B09464F220
+ for <test@example.com>; Tue, 7 Jul 2009 11:49:00 -0600 (MDT)
+Message-ID: <20090707114900.4w4ksggowkc4@example.com>
+Date: Tue, 07 Jul 2009 11:49:00 -0600
+From: Foo <foo@example.com>
+To: test@example.com
+Subject: Test
+User-Agent: Internet Messaging Program (IMP) H4 (5.0-git)
+Content-Type: multipart/mixed; boundary="=_8w0gkwkgk44o"
+MIME-Version: 1.0
+Content-Transfer-Encoding: 7bit
+
+This message is in MIME format.
+
+--=_8w0gkwkgk44o
+Content-Type: text/plain; charset=UTF-8; format=flowed; DelSp=Yes
+Content-Disposition: inline
+Content-Transfer-Encoding: 7bit
+
+Test text.
+
+
+--=_8w0gkwkgk44o
+Content-Type: text/plain; charset=UTF-8; name=test.txt
+Content-Disposition: attachment; filename=test.txt
+Content-Transfer-Encoding: 7bit
+
+Test.
+
+--=_8w0gkwkgk44o--
+
+
+ )
+
+ [2] => Array
+ (
+ [parts] => Array
+ (
+ )
+
+ [subtype] => png
+ [type] => image
+ [encoding] => base64
+ [dparameters] => Array
+ (
+ [filename] => index.png
+ )
+
+ [parameters] => Array
+ (
+ [name] => index.png
+ )
+
+ [disposition] => attachment
+ [contents] => iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAABGdBTUEAAK/INwWK6QAAABl0RVh0
+U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAADoSURBVBgZBcExblNBGAbA2ceegTRBuIKO
+giihSZNTcC5LUHAihNJR0kGKCDcYJY6D3/77MdOinTvzAgCw8ysThIvn/VojIyMjIyPP+bS1sUQI
+V2s95pBDDvmbP/mdkft83tpYguZq5Jh/OeaYh+yzy8hTHvNlaxNNczm+la9OTlar1UdA/+C2A4tr
+RCnD3jS8BB1obq2Gk6GU6QbQAS4BUaYSQAf4bhhKKTFdAzrAOwAxEUAH+KEM01SY3gM6wBsEAQB0
+gJ+maZoC3gI6iPYaAIBJsiRmHU0AALOeFC3aK2cWAACUXe7+AwO0lc9eTHYTAAAAAElFTkSuQmCC
+ [size] => 466
+ )
+
+ )
+
+ [subtype] => mixed
+ [type] => multipart
+ [encoding] => 7bit
+ [dparameters] =>
+ [parameters] => Array
+ (
+ [boundary] => =_k4kgcwkwggwc
+ )
+
+ [contents] => This message is in MIME format.
+
+--=_k4kgcwkwggwc
+Content-Type: text/plain; charset=UTF-8; format=flowed; DelSp=Yes
+Content-Disposition: inline
+Content-Transfer-Encoding: 7bit
+
+Test.
+
+
+--=_k4kgcwkwggwc
+Content-Type: message/rfc822; name="Forwarded Message"
+
+Return-Path: <foo@example.com>
+Delivered-To: test@example.com
+Received: from localhost (localhost [127.0.0.1])
+ by example.com (Postfix) with ESMTP id B09464F220
+ for <test@example.com>; Tue, 7 Jul 2009 11:49:00 -0600 (MDT)
+Message-ID: <20090707114900.4w4ksggowkc4@example.com>
+Date: Tue, 07 Jul 2009 11:49:00 -0600
+From: Foo <foo@example.com>
+To: test@example.com
+Subject: Test
+User-Agent: Internet Messaging Program (IMP) H4 (5.0-git)
+Content-Type: multipart/mixed; boundary="=_8w0gkwkgk44o"
+MIME-Version: 1.0
+Content-Transfer-Encoding: 7bit
+
+This message is in MIME format.
+
+--=_8w0gkwkgk44o
+Content-Type: text/plain; charset=UTF-8; format=flowed; DelSp=Yes
+Content-Disposition: inline
+Content-Transfer-Encoding: 7bit
+
+Test text.
+
+
+--=_8w0gkwkgk44o
+Content-Type: text/plain; charset=UTF-8; name=test.txt
+Content-Disposition: attachment; filename=test.txt
+Content-Transfer-Encoding: 7bit
+
+Test.
+
+--=_8w0gkwkgk44o--
+
+
+--=_k4kgcwkwggwc
+Content-Type: image/png; name=index.png
+Content-Disposition: attachment; filename=index.png
+Content-Transfer-Encoding: base64
+
+iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAABGdBTUEAAK/INwWK6QAAABl0RVh0
+U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAADoSURBVBgZBcExblNBGAbA2ceegTRBuIKO
+giihSZNTcC5LUHAihNJR0kGKCDcYJY6D3/77MdOinTvzAgCw8ysThIvn/VojIyMjIyPP+bS1sUQI
+V2s95pBDDvmbP/mdkft83tpYguZq5Jh/OeaYh+yzy8hTHvNlaxNNczm+la9OTlar1UdA/+C2A4tr
+RCnD3jS8BB1obq2Gk6GU6QbQAS4BUaYSQAf4bhhKKTFdAzrAOwAxEUAH+KEM01SY3gM6wBsEAQB0
+gJ+maZoC3gI6iPYaAIBJsiRmHU0AALOeFC3aK2cWAACUXe7+AwO0lc9eTHYTAAAAAElFTkSuQmCC
+--=_k4kgcwkwggwc--
+
+ [size] => 1869
+)
--- /dev/null
+--TEST--
+Horde_Mime_Part::parseMessage() [Horde_Mime_Part] test
+--FILE--
+<?php
+
+require_once dirname(__FILE__) . '/../../../lib/Horde/Mime.php';
+require_once dirname(__FILE__) . '/../../../lib/Horde/Mime/Address.php';
+require_once dirname(__FILE__) . '/../../../lib/Horde/Mime/Headers.php';
+require_once dirname(__FILE__) . '/../../../lib/Horde/Mime/Part.php';
+require_once 'Horde/String.php';
+require_once 'Mail/RFC822.php';
+
+$data = file_get_contents(dirname(__FILE__) . '/fixtures/sample_msg.txt');
+
+$ob = Horde_Mime_Part::parseMessage($data);
+print_r($ob->getType());
+
+?>
+--EXPECTF--
+multipart/mixed
--- /dev/null
+--TEST--
+Horde_Mime_Part::getRawPartText() test
+--FILE--
+<?php
+
+require_once dirname(__FILE__) . '/../../../lib/Horde/Mime.php';
+require_once dirname(__FILE__) . '/../../../lib/Horde/Mime/Address.php';
+require_once dirname(__FILE__) . '/../../../lib/Horde/Mime/Headers.php';
+require_once dirname(__FILE__) . '/../../../lib/Horde/Mime/Part.php';
+require_once 'Horde/String.php';
+require_once 'Mail/RFC822.php';
+
+$data = file_get_contents(dirname(__FILE__) . '/fixtures/sample_msg.txt');
+
+print_r(Horde_Mime_Part::getRawPartText($data, 'body', '2.1'));
+echo "---\n";
+print_r(Horde_Mime_Part::getRawPartText($data, 'header', '3'));
+
+?>
+--EXPECTF--
+Test text.
+
+---
+Content-Type: image/png; name=index.png
+Content-Disposition: attachment; filename=index.png
+Content-Transfer-Encoding: base64