|
1 <?php |
|
2 /** |
|
3 * Zend Framework |
|
4 * |
|
5 * LICENSE |
|
6 * |
|
7 * This source file is subject to the new BSD license that is bundled |
|
8 * with this package in the file LICENSE.txt. |
|
9 * It is also available through the world-wide-web at this URL: |
|
10 * http://framework.zend.com/license/new-bsd |
|
11 * If you did not receive a copy of the license and are unable to |
|
12 * obtain it through the world-wide-web, please send an email |
|
13 * to license@zend.com so we can send you a copy immediately. |
|
14 * |
|
15 * @category Zend |
|
16 * @package Zend_Mime |
|
17 * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) |
|
18 * @license http://framework.zend.com/license/new-bsd New BSD License |
|
19 * @version $Id: Decode.php 20096 2010-01-06 02:05:09Z bkarwin $ |
|
20 */ |
|
21 |
|
22 /** |
|
23 * @see Zend_Mime |
|
24 */ |
|
25 require_once 'Zend/Mime.php'; |
|
26 |
|
27 /** |
|
28 * @category Zend |
|
29 * @package Zend_Mime |
|
30 * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) |
|
31 * @license http://framework.zend.com/license/new-bsd New BSD License |
|
32 */ |
|
33 class Zend_Mime_Decode |
|
34 { |
|
35 /** |
|
36 * Explode MIME multipart string into seperate parts |
|
37 * |
|
38 * Parts consist of the header and the body of each MIME part. |
|
39 * |
|
40 * @param string $body raw body of message |
|
41 * @param string $boundary boundary as found in content-type |
|
42 * @return array parts with content of each part, empty if no parts found |
|
43 * @throws Zend_Exception |
|
44 */ |
|
45 public static function splitMime($body, $boundary) |
|
46 { |
|
47 // TODO: we're ignoring \r for now - is this function fast enough and is it safe to asume noone needs \r? |
|
48 $body = str_replace("\r", '', $body); |
|
49 |
|
50 $start = 0; |
|
51 $res = array(); |
|
52 // find every mime part limiter and cut out the |
|
53 // string before it. |
|
54 // the part before the first boundary string is discarded: |
|
55 $p = strpos($body, '--' . $boundary . "\n", $start); |
|
56 if ($p === false) { |
|
57 // no parts found! |
|
58 return array(); |
|
59 } |
|
60 |
|
61 // position after first boundary line |
|
62 $start = $p + 3 + strlen($boundary); |
|
63 |
|
64 while (($p = strpos($body, '--' . $boundary . "\n", $start)) !== false) { |
|
65 $res[] = substr($body, $start, $p-$start); |
|
66 $start = $p + 3 + strlen($boundary); |
|
67 } |
|
68 |
|
69 // no more parts, find end boundary |
|
70 $p = strpos($body, '--' . $boundary . '--', $start); |
|
71 if ($p===false) { |
|
72 throw new Zend_Exception('Not a valid Mime Message: End Missing'); |
|
73 } |
|
74 |
|
75 // the remaining part also needs to be parsed: |
|
76 $res[] = substr($body, $start, $p-$start); |
|
77 return $res; |
|
78 } |
|
79 |
|
80 /** |
|
81 * decodes a mime encoded String and returns a |
|
82 * struct of parts with header and body |
|
83 * |
|
84 * @param string $message raw message content |
|
85 * @param string $boundary boundary as found in content-type |
|
86 * @param string $EOL EOL string; defaults to {@link Zend_Mime::LINEEND} |
|
87 * @return array|null parts as array('header' => array(name => value), 'body' => content), null if no parts found |
|
88 * @throws Zend_Exception |
|
89 */ |
|
90 public static function splitMessageStruct($message, $boundary, $EOL = Zend_Mime::LINEEND) |
|
91 { |
|
92 $parts = self::splitMime($message, $boundary); |
|
93 if (count($parts) <= 0) { |
|
94 return null; |
|
95 } |
|
96 $result = array(); |
|
97 foreach ($parts as $part) { |
|
98 self::splitMessage($part, $headers, $body, $EOL); |
|
99 $result[] = array('header' => $headers, |
|
100 'body' => $body ); |
|
101 } |
|
102 return $result; |
|
103 } |
|
104 |
|
105 /** |
|
106 * split a message in header and body part, if no header or an |
|
107 * invalid header is found $headers is empty |
|
108 * |
|
109 * The charset of the returned headers depend on your iconv settings. |
|
110 * |
|
111 * @param string $message raw message with header and optional content |
|
112 * @param array $headers output param, array with headers as array(name => value) |
|
113 * @param string $body output param, content of message |
|
114 * @param string $EOL EOL string; defaults to {@link Zend_Mime::LINEEND} |
|
115 * @return null |
|
116 */ |
|
117 public static function splitMessage($message, &$headers, &$body, $EOL = Zend_Mime::LINEEND) |
|
118 { |
|
119 // check for valid header at first line |
|
120 $firstline = strtok($message, "\n"); |
|
121 if (!preg_match('%^[^\s]+[^:]*:%', $firstline)) { |
|
122 $headers = array(); |
|
123 // TODO: we're ignoring \r for now - is this function fast enough and is it safe to asume noone needs \r? |
|
124 $body = str_replace(array("\r", "\n"), array('', $EOL), $message); |
|
125 return; |
|
126 } |
|
127 |
|
128 // find an empty line between headers and body |
|
129 // default is set new line |
|
130 if (strpos($message, $EOL . $EOL)) { |
|
131 list($headers, $body) = explode($EOL . $EOL, $message, 2); |
|
132 // next is the standard new line |
|
133 } else if ($EOL != "\r\n" && strpos($message, "\r\n\r\n")) { |
|
134 list($headers, $body) = explode("\r\n\r\n", $message, 2); |
|
135 // next is the other "standard" new line |
|
136 } else if ($EOL != "\n" && strpos($message, "\n\n")) { |
|
137 list($headers, $body) = explode("\n\n", $message, 2); |
|
138 // at last resort find anything that looks like a new line |
|
139 } else { |
|
140 @list($headers, $body) = @preg_split("%([\r\n]+)\\1%U", $message, 2); |
|
141 } |
|
142 |
|
143 $headers = iconv_mime_decode_headers($headers, ICONV_MIME_DECODE_CONTINUE_ON_ERROR); |
|
144 |
|
145 if ($headers === false ) { |
|
146 // an error occurs during the decoding |
|
147 return; |
|
148 } |
|
149 |
|
150 // normalize header names |
|
151 foreach ($headers as $name => $header) { |
|
152 $lower = strtolower($name); |
|
153 if ($lower == $name) { |
|
154 continue; |
|
155 } |
|
156 unset($headers[$name]); |
|
157 if (!isset($headers[$lower])) { |
|
158 $headers[$lower] = $header; |
|
159 continue; |
|
160 } |
|
161 if (is_array($headers[$lower])) { |
|
162 $headers[$lower][] = $header; |
|
163 continue; |
|
164 } |
|
165 $headers[$lower] = array($headers[$lower], $header); |
|
166 } |
|
167 } |
|
168 |
|
169 /** |
|
170 * split a content type in its different parts |
|
171 * |
|
172 * @param string $type content-type |
|
173 * @param string $wantedPart the wanted part, else an array with all parts is returned |
|
174 * @return string|array wanted part or all parts as array('type' => content-type, partname => value) |
|
175 */ |
|
176 public static function splitContentType($type, $wantedPart = null) |
|
177 { |
|
178 return self::splitHeaderField($type, $wantedPart, 'type'); |
|
179 } |
|
180 |
|
181 /** |
|
182 * split a header field like content type in its different parts |
|
183 * |
|
184 * @param string $type header field |
|
185 * @param string $wantedPart the wanted part, else an array with all parts is returned |
|
186 * @param string $firstName key name for the first part |
|
187 * @return string|array wanted part or all parts as array($firstName => firstPart, partname => value) |
|
188 * @throws Zend_Exception |
|
189 */ |
|
190 public static function splitHeaderField($field, $wantedPart = null, $firstName = 0) |
|
191 { |
|
192 $wantedPart = strtolower($wantedPart); |
|
193 $firstName = strtolower($firstName); |
|
194 |
|
195 // special case - a bit optimized |
|
196 if ($firstName === $wantedPart) { |
|
197 $field = strtok($field, ';'); |
|
198 return $field[0] == '"' ? substr($field, 1, -1) : $field; |
|
199 } |
|
200 |
|
201 $field = $firstName . '=' . $field; |
|
202 if (!preg_match_all('%([^=\s]+)\s*=\s*("[^"]+"|[^;]+)(;\s*|$)%', $field, $matches)) { |
|
203 throw new Zend_Exception('not a valid header field'); |
|
204 } |
|
205 |
|
206 if ($wantedPart) { |
|
207 foreach ($matches[1] as $key => $name) { |
|
208 if (strcasecmp($name, $wantedPart)) { |
|
209 continue; |
|
210 } |
|
211 if ($matches[2][$key][0] != '"') { |
|
212 return $matches[2][$key]; |
|
213 } |
|
214 return substr($matches[2][$key], 1, -1); |
|
215 } |
|
216 return null; |
|
217 } |
|
218 |
|
219 $split = array(); |
|
220 foreach ($matches[1] as $key => $name) { |
|
221 $name = strtolower($name); |
|
222 if ($matches[2][$key][0] == '"') { |
|
223 $split[$name] = substr($matches[2][$key], 1, -1); |
|
224 } else { |
|
225 $split[$name] = $matches[2][$key]; |
|
226 } |
|
227 } |
|
228 |
|
229 return $split; |
|
230 } |
|
231 |
|
232 /** |
|
233 * decode a quoted printable encoded string |
|
234 * |
|
235 * The charset of the returned string depends on your iconv settings. |
|
236 * |
|
237 * @param string encoded string |
|
238 * @return string decoded string |
|
239 */ |
|
240 public static function decodeQuotedPrintable($string) |
|
241 { |
|
242 return iconv_mime_decode($string, ICONV_MIME_DECODE_CONTINUE_ON_ERROR); |
|
243 } |
|
244 } |