Remove dependency on Mail_mimeDecode.
authorMichael M Slusarz <slusarz@curecanti.org>
Tue, 7 Jul 2009 08:30:03 +0000 (02:30 -0600)
committerMichael M Slusarz <slusarz@curecanti.org>
Tue, 7 Jul 2009 19:44:05 +0000 (13:44 -0600)
Previously, we were using Mail_mimeDecode to parse MIME message text and
to parse MIME headers with parameter data.

This code is more robust, cleaner, probably faster, and doesn't thrown
errors in PHP 5 strict mode.

In transition, added ability to get params and base of MIME headers
directly.

framework/Mime/lib/Horde/Mime.php
framework/Mime/lib/Horde/Mime/Headers.php
framework/Mime/lib/Horde/Mime/Part.php
framework/Mime/package.xml
framework/Mime/test/Horde/Mime/fixtures/sample_msg.txt [new file with mode: 0644]
framework/Mime/test/Horde/Mime/parse_001.phpt [new file with mode: 0644]
framework/Mime/test/Horde/Mime/parse_002.phpt [new file with mode: 0644]
framework/Mime/test/Horde/Mime/parse_003.phpt [new file with mode: 0644]

index d58ab0f..c3e2f7e 100644 (file)
@@ -3,6 +3,48 @@
  * 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
@@ -432,29 +474,52 @@ class Horde_Mime
     {
         $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
index 294a1c4..f578d09 100644 (file)
  */
 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.
@@ -387,28 +392,47 @@ class Horde_Mime_Headers
      * 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;
+        }
     }
 
     /**
@@ -551,19 +575,18 @@ class Horde_Mime_Headers
                 $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');
@@ -573,7 +596,13 @@ class Horde_Mime_Headers
             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;
index 8d7b606..9fe8d64 100644 (file)
@@ -1734,19 +1734,10 @@ class Horde_Mime_Part
      */
     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)
@@ -1754,87 +1745,85 @@ class Horde_Mime_Part
     }
 
     /**
-     * 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;
@@ -1854,90 +1843,122 @@ class Horde_Mime_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;
     }
 
 }
index 55d7363..b24cb6d 100644 (file)
@@ -31,7 +31,8 @@ http://pear.php.net/dtd/package-2.0.xsd">
   <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).
@@ -109,6 +110,7 @@ http://pear.php.net/dtd/package-2.0.xsd">
       <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" />
@@ -130,6 +132,9 @@ http://pear.php.net/dtd/package-2.0.xsd">
       <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" />
@@ -151,10 +156,6 @@ http://pear.php.net/dtd/package-2.0.xsd">
     <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>
diff --git a/framework/Mime/test/Horde/Mime/fixtures/sample_msg.txt b/framework/Mime/test/Horde/Mime/fixtures/sample_msg.txt
new file mode 100644 (file)
index 0000000..31d82e4
--- /dev/null
@@ -0,0 +1,75 @@
+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--
diff --git a/framework/Mime/test/Horde/Mime/parse_001.phpt b/framework/Mime/test/Horde/Mime/parse_001.phpt
new file mode 100644 (file)
index 0000000..97230d9
--- /dev/null
@@ -0,0 +1,294 @@
+--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
+)
diff --git a/framework/Mime/test/Horde/Mime/parse_002.phpt b/framework/Mime/test/Horde/Mime/parse_002.phpt
new file mode 100644 (file)
index 0000000..53061ac
--- /dev/null
@@ -0,0 +1,20 @@
+--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
diff --git a/framework/Mime/test/Horde/Mime/parse_003.phpt b/framework/Mime/test/Horde/Mime/parse_003.phpt
new file mode 100644 (file)
index 0000000..fd95007
--- /dev/null
@@ -0,0 +1,26 @@
+--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