File indexing completed on 2024-05-12 06:02:47

0001 <?php
0002 /**
0003  * Zend Framework
0004  *
0005  * LICENSE
0006  *
0007  * This source file is subject to the new BSD license that is bundled
0008  * with this package in the file LICENSE.txt.
0009  * It is also available through the world-wide-web at this URL:
0010  * http://framework.zend.com/license/new-bsd
0011  * If you did not receive a copy of the license and are unable to
0012  * obtain it through the world-wide-web, please send an email
0013  * to license@zend.com so we can send you a copy immediately.
0014  *
0015  * @category   Zend
0016  * @package    Zend_Mime
0017  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0018  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0019  * @version    $Id$
0020  */
0021 
0022 /**
0023  * @see Zend_Mime
0024  */
0025 // require_once 'Zend/Mime.php';
0026 
0027 /**
0028  * @category   Zend
0029  * @package    Zend_Mime
0030  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0031  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0032  */
0033 class Zend_Mime_Decode
0034 {
0035     /**
0036      * Explode MIME multipart string into seperate parts
0037      *
0038      * Parts consist of the header and the body of each MIME part.
0039      *
0040      * @param  string $body     raw body of message
0041      * @param  string $boundary boundary as found in content-type
0042      * @return array parts with content of each part, empty if no parts found
0043      * @throws Zend_Exception
0044      */
0045     public static function splitMime($body, $boundary)
0046     {
0047         // TODO: we're ignoring \r for now - is this function fast enough and is it safe to asume noone needs \r?
0048         $body = str_replace("\r", '', $body);
0049 
0050         $start = 0;
0051         $res   = array();
0052         // find every mime part limiter and cut out the
0053         // string before it.
0054         // the part before the first boundary string is discarded:
0055         $p = strpos($body, '--' . $boundary . "\n", $start);
0056         if ($p === false) {
0057             // no parts found!
0058             return array();
0059         }
0060 
0061         // position after first boundary line
0062         $start = $p + 3 + strlen($boundary);
0063 
0064         while (($p = strpos($body, '--' . $boundary . "\n", $start)) !== false) {
0065             $res[] = substr($body, $start, $p-$start);
0066             $start = $p + 3 + strlen($boundary);
0067         }
0068 
0069         // no more parts, find end boundary
0070         $p = strpos($body, '--' . $boundary . '--', $start);
0071         if ($p === false) {
0072             throw new Zend_Exception('Not a valid Mime Message: End Missing');
0073         }
0074 
0075         // the remaining part also needs to be parsed:
0076         $res[] = substr($body, $start, $p - $start);
0077 
0078         return $res;
0079     }
0080 
0081     /**
0082      * decodes a mime encoded String and returns a
0083      * struct of parts with header and body
0084      *
0085      * @param  string $message  raw message content
0086      * @param  string $boundary boundary as found in content-type
0087      * @param  string $EOL      EOL string; defaults to {@link Zend_Mime::LINEEND}
0088      * @return array|null parts as array('header' => array(name => value), 'body' => content), null if no parts found
0089      * @throws Zend_Exception
0090      */
0091     public static function splitMessageStruct(
0092         $message, $boundary, $EOL = Zend_Mime::LINEEND
0093     )
0094     {
0095         $parts = self::splitMime($message, $boundary);
0096         if (count($parts) <= 0) {
0097             return null;
0098         }
0099         $result = array();
0100         foreach ($parts as $part) {
0101             self::splitMessage($part, $headers, $body, $EOL);
0102             $result[] = array(
0103                 'header' => $headers,
0104                 'body'   => $body
0105             );
0106         }
0107 
0108         return $result;
0109     }
0110 
0111     /**
0112      * split a message in header and body part, if no header or an
0113      * invalid header is found $headers is empty
0114      *
0115      * The charset of the returned headers depend on your iconv settings.
0116      *
0117      * @param  string $message raw message with header and optional content
0118      * @param  array  $headers output param, array with headers as array(name => value)
0119      * @param  string $body    output param, content of message
0120      * @param  string $EOL     EOL string; defaults to {@link Zend_Mime::LINEEND}
0121      * @return null
0122      */
0123     public static function splitMessage(
0124         $message, &$headers, &$body, $EOL = Zend_Mime::LINEEND
0125     )
0126     {
0127         // check for valid header at first line
0128         $firstline = strtok($message, "\n");
0129         if (!preg_match('%^[^\s]+[^:]*:%', $firstline)) {
0130             $headers = array();
0131             // TODO: we're ignoring \r for now - is this function fast enough and is it safe to asume noone needs \r?
0132             $body = str_replace(
0133                 array(
0134                     "\r",
0135                     "\n"
0136                 ), array(
0137                     '',
0138                     $EOL
0139                 ), $message
0140             );
0141 
0142             return;
0143         }
0144 
0145         // find an empty line between headers and body
0146         // default is set new line
0147         if (strpos($message, $EOL . $EOL)) {
0148             list($headers, $body) = explode($EOL . $EOL, $message, 2);
0149             // next is the standard new line
0150         } else {
0151             if ($EOL != "\r\n" && strpos($message, "\r\n\r\n")) {
0152                 list($headers, $body) = explode("\r\n\r\n", $message, 2);
0153                 // next is the other "standard" new line
0154             } else {
0155                 if ($EOL != "\n" && strpos($message, "\n\n")) {
0156                     list($headers, $body) = explode("\n\n", $message, 2);
0157                     // at last resort find anything that looks like a new line
0158                 } else {
0159                     @list($headers, $body) =
0160                         @preg_split("%([\r\n]+)\\1%U", $message, 2);
0161                 }
0162             }
0163         }
0164 
0165         $headers = iconv_mime_decode_headers(
0166             $headers, ICONV_MIME_DECODE_CONTINUE_ON_ERROR
0167         );
0168 
0169         if ($headers === false) {
0170             // an error occurs during the decoding
0171             return;
0172         }
0173 
0174         // normalize header names
0175         foreach ($headers as $name => $header) {
0176             $lower = strtolower($name);
0177             if ($lower == $name) {
0178                 continue;
0179             }
0180             unset($headers[$name]);
0181             if (!isset($headers[$lower])) {
0182                 $headers[$lower] = $header;
0183                 continue;
0184             }
0185             if (is_array($headers[$lower])) {
0186                 $headers[$lower][] = $header;
0187                 continue;
0188             }
0189             $headers[$lower] = array(
0190                 $headers[$lower],
0191                 $header
0192             );
0193         }
0194     }
0195 
0196     /**
0197      * split a content type in its different parts
0198      *
0199      * @param  string $type       content-type
0200      * @param  string $wantedPart the wanted part, else an array with all parts is returned
0201      * @return string|array wanted part or all parts as array('type' => content-type, partname => value)
0202      */
0203     public static function splitContentType($type, $wantedPart = null)
0204     {
0205         return self::splitHeaderField($type, $wantedPart, 'type');
0206     }
0207 
0208     /**
0209      * split a header field like content type in its different parts
0210      *
0211      * @param  string     $field
0212      * @param  string     $wantedPart the wanted part, else an array with all parts is returned
0213      * @param  int|string $firstName  key name for the first part
0214      * @throws Zend_Exception
0215      * @return string|array wanted part or all parts as array($firstName => firstPart, partname => value)
0216      */
0217     public static function splitHeaderField(
0218         $field, $wantedPart = null, $firstName = 0
0219     )
0220     {
0221         $wantedPart = strtolower($wantedPart);
0222         $firstName  = strtolower($firstName);
0223 
0224         // special case - a bit optimized
0225         if ($firstName === $wantedPart) {
0226             $field = strtok($field, ';');
0227 
0228             return $field[0] == '"' ? substr($field, 1, -1) : $field;
0229         }
0230 
0231         $field = $firstName . '=' . $field;
0232         if (!preg_match_all('%([^=\s]+)\s*=\s*("[^"]+"|[^;]+)(;\s*|$)%', $field, $matches)) {
0233             throw new Zend_Exception('not a valid header field');
0234         }
0235 
0236         if ($wantedPart) {
0237             foreach ($matches[1] as $key => $name) {
0238                 if (strcasecmp($name, $wantedPart)) {
0239                     continue;
0240                 }
0241                 if ($matches[2][$key][0] != '"') {
0242                     return $matches[2][$key];
0243                 }
0244 
0245                 return substr($matches[2][$key], 1, -1);
0246             }
0247 
0248             return null;
0249         }
0250 
0251         $split = array();
0252         foreach ($matches[1] as $key => $name) {
0253             $name = strtolower($name);
0254             if ($matches[2][$key][0] == '"') {
0255                 $split[$name] = substr($matches[2][$key], 1, -1);
0256             } else {
0257                 $split[$name] = $matches[2][$key];
0258             }
0259         }
0260 
0261         return $split;
0262     }
0263 
0264     /**
0265      * decode a quoted printable encoded string
0266      *
0267      * The charset of the returned string depends on your iconv settings.
0268      *
0269      * @param  string $string Encoded string
0270      * @return string         Decoded string
0271      */
0272     public static function decodeQuotedPrintable($string)
0273     {
0274         return quoted_printable_decode($string);
0275     }
0276 }