3 * Zend Framework (http://framework.zend.com/)
5 * @link http://github.com/zendframework/zf2 for the canonical source repository
6 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
7 * @license http://framework.zend.com/license/new-bsd New BSD License
12 use Zend\Mail\Headers
;
13 use Zend\Stdlib\ErrorHandler
;
18 * Explode MIME multipart string into separate parts
20 * Parts consist of the header and the body of each MIME part.
22 * @param string $body raw body of message
23 * @param string $boundary boundary as found in content-type
24 * @return array parts with content of each part, empty if no parts found
25 * @throws Exception\RuntimeException
27 public static function splitMime($body, $boundary)
29 // TODO: we're ignoring \r for now - is this function fast enough and is it safe to assume noone needs \r?
30 $body = str_replace("\r", '', $body);
34 // find every mime part limiter and cut out the
36 // the part before the first boundary string is discarded:
37 $p = strpos($body, '--' . $boundary . "\n", $start);
43 // position after first boundary line
44 $start = $p +
3 +
strlen($boundary);
46 while (($p = strpos($body, '--' . $boundary . "\n", $start)) !== false) {
47 $res[] = substr($body, $start, $p-$start);
48 $start = $p +
3 +
strlen($boundary);
51 // no more parts, find end boundary
52 $p = strpos($body, '--' . $boundary . '--', $start);
54 throw new Exception\
RuntimeException('Not a valid Mime Message: End Missing');
57 // the remaining part also needs to be parsed:
58 $res[] = substr($body, $start, $p-$start);
63 * decodes a mime encoded String and returns a
64 * struct of parts with header and body
66 * @param string $message raw message content
67 * @param string $boundary boundary as found in content-type
68 * @param string $EOL EOL string; defaults to {@link Zend\Mime\Mime::LINEEND}
69 * @return array|null parts as array('header' => array(name => value), 'body' => content), null if no parts found
70 * @throws Exception\RuntimeException
72 public static function splitMessageStruct($message, $boundary, $EOL = Mime
::LINEEND
)
74 $parts = static::splitMime($message, $boundary);
75 if (count($parts) <= 0) {
79 $headers = null; // "Declare" variable before the first usage "for reading"
80 $body = null; // "Declare" variable before the first usage "for reading"
81 foreach ($parts as $part) {
82 static::splitMessage($part, $headers, $body, $EOL);
83 $result[] = array('header' => $headers,
90 * split a message in header and body part, if no header or an
91 * invalid header is found $headers is empty
93 * The charset of the returned headers depend on your iconv settings.
95 * @param string|Headers $message raw message with header and optional content
96 * @param Headers $headers output param, headers container
97 * @param string $body output param, content of message
98 * @param string $EOL EOL string; defaults to {@link Zend\Mime\Mime::LINEEND}
99 * @param bool $strict enable strict mode for parsing message
102 public static function splitMessage($message, &$headers, &$body, $EOL = Mime
::LINEEND
, $strict = false)
104 if ($message instanceof Headers
) {
105 $message = $message->toString();
107 // check for valid header at first line
108 $firstline = strtok($message, "\n");
109 if (!preg_match('%^[^\s]+[^:]*:%', $firstline)) {
111 // TODO: we're ignoring \r for now - is this function fast enough and is it safe to assume noone needs \r?
112 $body = str_replace(array("\r", "\n"), array('', $EOL), $message);
116 // see @ZF2-372, pops the first line off a message if it doesn't contain a header
118 $parts = explode(':', $firstline, 2);
119 if (count($parts) != 2) {
120 $message = substr($message, strpos($message, $EOL)+
1);
124 // find an empty line between headers and body
125 // default is set new line
126 if (strpos($message, $EOL . $EOL)) {
127 list($headers, $body) = explode($EOL . $EOL, $message, 2);
128 // next is the standard new line
129 } elseif ($EOL != "\r\n" && strpos($message, "\r\n\r\n")) {
130 list($headers, $body) = explode("\r\n\r\n", $message, 2);
131 // next is the other "standard" new line
132 } elseif ($EOL != "\n" && strpos($message, "\n\n")) {
133 list($headers, $body) = explode("\n\n", $message, 2);
134 // at last resort find anything that looks like a new line
136 ErrorHandler
::start(E_NOTICE|E_WARNING
);
137 list($headers, $body) = preg_split("%([\r\n]+)\\1%U", $message, 2);
138 ErrorHandler
::stop();
141 $headers = Headers
::fromString($headers, $EOL);
145 * split a content type in its different parts
147 * @param string $type content-type
148 * @param string $wantedPart the wanted part, else an array with all parts is returned
149 * @return string|array wanted part or all parts as array('type' => content-type, partname => value)
151 public static function splitContentType($type, $wantedPart = null)
153 return static::splitHeaderField($type, $wantedPart, 'type');
157 * split a header field like content type in its different parts
159 * @param string $field header field
160 * @param string $wantedPart the wanted part, else an array with all parts is returned
161 * @param string $firstName key name for the first part
162 * @return string|array wanted part or all parts as array($firstName => firstPart, partname => value)
163 * @throws Exception\RuntimeException
165 public static function splitHeaderField($field, $wantedPart = null, $firstName = '0')
167 $wantedPart = strtolower($wantedPart);
168 $firstName = strtolower($firstName);
170 // special case - a bit optimized
171 if ($firstName === $wantedPart) {
172 $field = strtok($field, ';');
173 return $field[0] == '"' ?
substr($field, 1, -1) : $field;
176 $field = $firstName . '=' . $field;
177 if (!preg_match_all('%([^=\s]+)\s*=\s*("[^"]+"|[^;]+)(;\s*|$)%', $field, $matches)) {
178 throw new Exception\
RuntimeException('not a valid header field');
182 foreach ($matches[1] as $key => $name) {
183 if (strcasecmp($name, $wantedPart)) {
186 if ($matches[2][$key][0] != '"') {
187 return $matches[2][$key];
189 return substr($matches[2][$key], 1, -1);
195 foreach ($matches[1] as $key => $name) {
196 $name = strtolower($name);
197 if ($matches[2][$key][0] == '"') {
198 $split[$name] = substr($matches[2][$key], 1, -1);
200 $split[$name] = $matches[2][$key];
208 * decode a quoted printable encoded string
210 * The charset of the returned string depends on your iconv settings.
212 * @param string $string encoded string
213 * @return string decoded string
215 public static function decodeQuotedPrintable($string)
217 return iconv_mime_decode($string, ICONV_MIME_DECODE_CONTINUE_ON_ERROR
, 'UTF-8');