1 | <?php
|
---|
2 | /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
|
---|
3 | // +-----------------------------------------------------------------------+
|
---|
4 | // | Copyright (c) 2002-2003 Richard Heyes |
|
---|
5 | // | Copyright (c) 2003-2005 The PHP Group |
|
---|
6 | // | All rights reserved. |
|
---|
7 | // | |
|
---|
8 | // | Redistribution and use in source and binary forms, with or without |
|
---|
9 | // | modification, are permitted provided that the following conditions |
|
---|
10 | // | are met: |
|
---|
11 | // | |
|
---|
12 | // | o Redistributions of source code must retain the above copyright |
|
---|
13 | // | notice, this list of conditions and the following disclaimer. |
|
---|
14 | // | o Redistributions in binary form must reproduce the above copyright |
|
---|
15 | // | notice, this list of conditions and the following disclaimer in the |
|
---|
16 | // | documentation and/or other materials provided with the distribution.|
|
---|
17 | // | o The names of the authors may not be used to endorse or promote |
|
---|
18 | // | products derived from this software without specific prior written |
|
---|
19 | // | permission. |
|
---|
20 | // | |
|
---|
21 | // | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|
---|
22 | // | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|
---|
23 | // | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
|
---|
24 | // | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
|
---|
25 | // | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
---|
26 | // | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|
---|
27 | // | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
|
---|
28 | // | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
|
---|
29 | // | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|
---|
30 | // | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
|
---|
31 | // | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
---|
32 | // | |
|
---|
33 | // +-----------------------------------------------------------------------+
|
---|
34 | // | Author: Richard Heyes <richard@phpguru.org> |
|
---|
35 | // +-----------------------------------------------------------------------+
|
---|
36 |
|
---|
37 | require_once dirname(__FILE__) . '/../PEAR.php';
|
---|
38 |
|
---|
39 | /**
|
---|
40 | * +----------------------------- IMPORTANT ------------------------------+
|
---|
41 | * | Usage of this class compared to native php extensions such as |
|
---|
42 | * | mailparse or imap, is slow and may be feature deficient. If available|
|
---|
43 | * | you are STRONGLY recommended to use the php extensions. |
|
---|
44 | * +----------------------------------------------------------------------+
|
---|
45 | *
|
---|
46 | * Mime Decoding class
|
---|
47 | *
|
---|
48 | * This class will parse a raw mime email and return
|
---|
49 | * the structure. Returned structure is similar to
|
---|
50 | * that returned by imap_fetchstructure().
|
---|
51 | *
|
---|
52 | * USAGE: (assume $input is your raw email)
|
---|
53 | *
|
---|
54 | * $decode = new Mail_mimeDecode($input, "\r\n");
|
---|
55 | * $structure = $decode->decode();
|
---|
56 | * print_r($structure);
|
---|
57 | *
|
---|
58 | * Or statically:
|
---|
59 | *
|
---|
60 | * $params['input'] = $input;
|
---|
61 | * $structure = Mail_mimeDecode::decode($params);
|
---|
62 | * print_r($structure);
|
---|
63 | *
|
---|
64 | * TODO:
|
---|
65 | * o Implement multipart/appledouble
|
---|
66 | * o UTF8: ???
|
---|
67 |
|
---|
68 | > 4. We have also found a solution for decoding the UTF-8
|
---|
69 | > headers. Therefore I made the following function:
|
---|
70 | >
|
---|
71 | > function decode_utf8($txt) {
|
---|
72 | > $trans=array("Å‘"=>"õ","ű"=>"û","Å"=>"Õ","Ű"
|
---|
73 | =>"Û");
|
---|
74 | > $txt=strtr($txt,$trans);
|
---|
75 | > return(utf8_decode($txt));
|
---|
76 | > }
|
---|
77 | >
|
---|
78 | > And I have inserted the following line to the class:
|
---|
79 | >
|
---|
80 | > if (strtolower($charset)=="utf-8") $text=decode_utf8($text);
|
---|
81 | >
|
---|
82 | > ... before the following one in the "_decodeHeader" function:
|
---|
83 | >
|
---|
84 | > $input = str_replace($encoded, $text, $input);
|
---|
85 | >
|
---|
86 | > This way from now on it can easily decode the UTF-8 headers too.
|
---|
87 |
|
---|
88 | *
|
---|
89 | * @author Richard Heyes <richard@phpguru.org>
|
---|
90 | * @version $Revision: 1.46 $
|
---|
91 | * @package Mail
|
---|
92 | */
|
---|
93 | class Mail_mimeDecode extends PEAR
|
---|
94 | {
|
---|
95 | /**
|
---|
96 | * The raw email to decode
|
---|
97 | * @var string
|
---|
98 | */
|
---|
99 | var $_input;
|
---|
100 |
|
---|
101 | /**
|
---|
102 | * The header part of the input
|
---|
103 | * @var string
|
---|
104 | */
|
---|
105 | var $_header;
|
---|
106 |
|
---|
107 | /**
|
---|
108 | * The body part of the input
|
---|
109 | * @var string
|
---|
110 | */
|
---|
111 | var $_body;
|
---|
112 |
|
---|
113 | /**
|
---|
114 | * If an error occurs, this is used to store the message
|
---|
115 | * @var string
|
---|
116 | */
|
---|
117 | var $_error;
|
---|
118 |
|
---|
119 | /**
|
---|
120 | * Flag to determine whether to include bodies in the
|
---|
121 | * returned object.
|
---|
122 | * @var boolean
|
---|
123 | */
|
---|
124 | var $_include_bodies;
|
---|
125 |
|
---|
126 | /**
|
---|
127 | * Flag to determine whether to decode bodies
|
---|
128 | * @var boolean
|
---|
129 | */
|
---|
130 | var $_decode_bodies;
|
---|
131 |
|
---|
132 | /**
|
---|
133 | * Flag to determine whether to decode headers
|
---|
134 | * @var boolean
|
---|
135 | */
|
---|
136 | var $_decode_headers;
|
---|
137 |
|
---|
138 | /**
|
---|
139 | * Constructor.
|
---|
140 | *
|
---|
141 | * Sets up the object, initialise the variables, and splits and
|
---|
142 | * stores the header and body of the input.
|
---|
143 | *
|
---|
144 | * @param string The input to decode
|
---|
145 | * @access public
|
---|
146 | */
|
---|
147 | function Mail_mimeDecode($input)
|
---|
148 | {
|
---|
149 | list($header, $body) = $this->_splitBodyHeader($input);
|
---|
150 |
|
---|
151 | $this->_input = $input;
|
---|
152 | $this->_header = $header;
|
---|
153 | $this->_body = $body;
|
---|
154 | $this->_decode_bodies = false;
|
---|
155 | $this->_include_bodies = true;
|
---|
156 | }
|
---|
157 |
|
---|
158 | /**
|
---|
159 | * Begins the decoding process. If called statically
|
---|
160 | * it will create an object and call the decode() method
|
---|
161 | * of it.
|
---|
162 | *
|
---|
163 | * @param array An array of various parameters that determine
|
---|
164 | * various things:
|
---|
165 | * include_bodies - Whether to include the body in the returned
|
---|
166 | * object.
|
---|
167 | * decode_bodies - Whether to decode the bodies
|
---|
168 | * of the parts. (Transfer encoding)
|
---|
169 | * decode_headers - Whether to decode headers
|
---|
170 | * input - If called statically, this will be treated
|
---|
171 | * as the input
|
---|
172 | * @return object Decoded results
|
---|
173 | * @access public
|
---|
174 | */
|
---|
175 | function decode($params = null)
|
---|
176 | {
|
---|
177 | // determine if this method has been called statically
|
---|
178 | $isStatic = !(isset($this) && get_class($this) == __CLASS__);
|
---|
179 |
|
---|
180 | // Have we been called statically?
|
---|
181 | // If so, create an object and pass details to that.
|
---|
182 | if ($isStatic AND isset($params['input'])) {
|
---|
183 |
|
---|
184 | $obj = new Mail_mimeDecode($params['input']);
|
---|
185 | $structure = $obj->decode($params);
|
---|
186 |
|
---|
187 | // Called statically but no input
|
---|
188 | } elseif ($isStatic) {
|
---|
189 | return PEAR::raiseError('Called statically and no input given');
|
---|
190 |
|
---|
191 | // Called via an object
|
---|
192 | } else {
|
---|
193 | $this->_include_bodies = isset($params['include_bodies']) ?
|
---|
194 | $params['include_bodies'] : false;
|
---|
195 | $this->_decode_bodies = isset($params['decode_bodies']) ?
|
---|
196 | $params['decode_bodies'] : false;
|
---|
197 | $this->_decode_headers = isset($params['decode_headers']) ?
|
---|
198 | $params['decode_headers'] : false;
|
---|
199 |
|
---|
200 | $structure = $this->_decode($this->_header, $this->_body);
|
---|
201 | if ($structure === false) {
|
---|
202 | $structure = $this->raiseError($this->_error);
|
---|
203 | }
|
---|
204 | }
|
---|
205 |
|
---|
206 | return $structure;
|
---|
207 | }
|
---|
208 |
|
---|
209 | /**
|
---|
210 | * Performs the decoding. Decodes the body string passed to it
|
---|
211 | * If it finds certain content-types it will call itself in a
|
---|
212 | * recursive fashion
|
---|
213 | *
|
---|
214 | * @param string Header section
|
---|
215 | * @param string Body section
|
---|
216 | * @return object Results of decoding process
|
---|
217 | * @access private
|
---|
218 | */
|
---|
219 | function _decode($headers, $body, $default_ctype = 'text/plain')
|
---|
220 | {
|
---|
221 | $return = new stdClass;
|
---|
222 | $return->headers = array();
|
---|
223 | $headers = $this->_parseHeaders($headers);
|
---|
224 |
|
---|
225 | foreach ($headers as $value) {
|
---|
226 | if (isset($return->headers[strtolower($value['name'])]) AND !is_array($return->headers[strtolower($value['name'])])) {
|
---|
227 | $return->headers[strtolower($value['name'])] = array($return->headers[strtolower($value['name'])]);
|
---|
228 | $return->headers[strtolower($value['name'])][] = $value['value'];
|
---|
229 |
|
---|
230 | } elseif (isset($return->headers[strtolower($value['name'])])) {
|
---|
231 | $return->headers[strtolower($value['name'])][] = $value['value'];
|
---|
232 |
|
---|
233 | } else {
|
---|
234 | $return->headers[strtolower($value['name'])] = $value['value'];
|
---|
235 | }
|
---|
236 | }
|
---|
237 |
|
---|
238 | reset($headers);
|
---|
239 | while (list($key, $value) = each($headers)) {
|
---|
240 | $headers[$key]['name'] = strtolower($headers[$key]['name']);
|
---|
241 | switch ($headers[$key]['name']) {
|
---|
242 |
|
---|
243 | case 'content-type':
|
---|
244 | $content_type = $this->_parseHeaderValue($headers[$key]['value']);
|
---|
245 |
|
---|
246 | if (preg_match('/([0-9a-z+.-]+)\/([0-9a-z+.-]+)/i', $content_type['value'], $regs)) {
|
---|
247 | $return->ctype_primary = $regs[1];
|
---|
248 | $return->ctype_secondary = $regs[2];
|
---|
249 | }
|
---|
250 |
|
---|
251 | if (isset($content_type['other'])) {
|
---|
252 | while (list($p_name, $p_value) = each($content_type['other'])) {
|
---|
253 | $return->ctype_parameters[$p_name] = $p_value;
|
---|
254 | }
|
---|
255 | }
|
---|
256 | break;
|
---|
257 |
|
---|
258 | case 'content-disposition':
|
---|
259 | $content_disposition = $this->_parseHeaderValue($headers[$key]['value']);
|
---|
260 | $return->disposition = $content_disposition['value'];
|
---|
261 | if (isset($content_disposition['other'])) {
|
---|
262 | while (list($p_name, $p_value) = each($content_disposition['other'])) {
|
---|
263 | $return->d_parameters[$p_name] = $p_value;
|
---|
264 | }
|
---|
265 | }
|
---|
266 | break;
|
---|
267 |
|
---|
268 | case 'content-transfer-encoding':
|
---|
269 | $content_transfer_encoding = $this->_parseHeaderValue($headers[$key]['value']);
|
---|
270 | break;
|
---|
271 | }
|
---|
272 | }
|
---|
273 |
|
---|
274 | if (isset($content_type)) {
|
---|
275 | switch (strtolower($content_type['value'])) {
|
---|
276 | case 'text/plain':
|
---|
277 | $encoding = isset($content_transfer_encoding) ? $content_transfer_encoding['value'] : '7bit';
|
---|
278 | $this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $encoding) : $body) : null;
|
---|
279 | break;
|
---|
280 |
|
---|
281 | case 'text/html':
|
---|
282 | $encoding = isset($content_transfer_encoding) ? $content_transfer_encoding['value'] : '7bit';
|
---|
283 | $this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $encoding) : $body) : null;
|
---|
284 | break;
|
---|
285 |
|
---|
286 | case 'multipart/parallel':
|
---|
287 | case 'multipart/report': // RFC1892
|
---|
288 | case 'multipart/signed': // PGP
|
---|
289 | case 'multipart/digest':
|
---|
290 | case 'multipart/alternative':
|
---|
291 | case 'multipart/related':
|
---|
292 | case 'multipart/mixed':
|
---|
293 | if(!isset($content_type['other']['boundary'])){
|
---|
294 | $this->_error = 'No boundary found for ' . $content_type['value'] . ' part';
|
---|
295 | return false;
|
---|
296 | }
|
---|
297 |
|
---|
298 | $default_ctype = (strtolower($content_type['value']) === 'multipart/digest') ? 'message/rfc822' : 'text/plain';
|
---|
299 |
|
---|
300 | $parts = $this->_boundarySplit($body, $content_type['other']['boundary']);
|
---|
301 | for ($i = 0; $i < count($parts); $i++) {
|
---|
302 | list($part_header, $part_body) = $this->_splitBodyHeader($parts[$i]);
|
---|
303 | $part = $this->_decode($part_header, $part_body, $default_ctype);
|
---|
304 | if($part === false)
|
---|
305 | $part = $this->raiseError($this->_error);
|
---|
306 | $return->parts[] = $part;
|
---|
307 | }
|
---|
308 | break;
|
---|
309 |
|
---|
310 | case 'message/rfc822':
|
---|
311 | $obj = &new Mail_mimeDecode($body);
|
---|
312 | $return->parts[] = $obj->decode(array('include_bodies' => $this->_include_bodies,
|
---|
313 | 'decode_bodies' => $this->_decode_bodies,
|
---|
314 | 'decode_headers' => $this->_decode_headers));
|
---|
315 | unset($obj);
|
---|
316 | break;
|
---|
317 |
|
---|
318 | default:
|
---|
319 | if(!isset($content_transfer_encoding['value']))
|
---|
320 | $content_transfer_encoding['value'] = '7bit';
|
---|
321 | $this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $content_transfer_encoding['value']) : $body) : null;
|
---|
322 | break;
|
---|
323 | }
|
---|
324 |
|
---|
325 | } else {
|
---|
326 | $ctype = explode('/', $default_ctype);
|
---|
327 | $return->ctype_primary = $ctype[0];
|
---|
328 | $return->ctype_secondary = $ctype[1];
|
---|
329 | $this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body) : $body) : null;
|
---|
330 | }
|
---|
331 |
|
---|
332 | return $return;
|
---|
333 | }
|
---|
334 |
|
---|
335 | /**
|
---|
336 | * Given the output of the above function, this will return an
|
---|
337 | * array of references to the parts, indexed by mime number.
|
---|
338 | *
|
---|
339 | * @param object $structure The structure to go through
|
---|
340 | * @param string $mime_number Internal use only.
|
---|
341 | * @return array Mime numbers
|
---|
342 | */
|
---|
343 | function &getMimeNumbers(&$structure, $no_refs = false, $mime_number = '', $prepend = '')
|
---|
344 | {
|
---|
345 | $return = array();
|
---|
346 | if (!empty($structure->parts)) {
|
---|
347 | if ($mime_number != '') {
|
---|
348 | $structure->mime_id = $prepend . $mime_number;
|
---|
349 | $return[$prepend . $mime_number] = &$structure;
|
---|
350 | }
|
---|
351 | for ($i = 0; $i < count($structure->parts); $i++) {
|
---|
352 |
|
---|
353 |
|
---|
354 | if (!empty($structure->headers['content-type']) AND substr(strtolower($structure->headers['content-type']), 0, 8) == 'message/') {
|
---|
355 | $prepend = $prepend . $mime_number . '.';
|
---|
356 | $_mime_number = '';
|
---|
357 | } else {
|
---|
358 | $_mime_number = ($mime_number == '' ? $i + 1 : sprintf('%s.%s', $mime_number, $i + 1));
|
---|
359 | }
|
---|
360 |
|
---|
361 | $arr = &Mail_mimeDecode::getMimeNumbers($structure->parts[$i], $no_refs, $_mime_number, $prepend);
|
---|
362 | foreach ($arr as $key => $val) {
|
---|
363 | $no_refs ? $return[$key] = '' : $return[$key] = &$arr[$key];
|
---|
364 | }
|
---|
365 | }
|
---|
366 | } else {
|
---|
367 | if ($mime_number == '') {
|
---|
368 | $mime_number = '1';
|
---|
369 | }
|
---|
370 | $structure->mime_id = $prepend . $mime_number;
|
---|
371 | $no_refs ? $return[$prepend . $mime_number] = '' : $return[$prepend . $mime_number] = &$structure;
|
---|
372 | }
|
---|
373 |
|
---|
374 | return $return;
|
---|
375 | }
|
---|
376 |
|
---|
377 | /**
|
---|
378 | * Given a string containing a header and body
|
---|
379 | * section, this function will split them (at the first
|
---|
380 | * blank line) and return them.
|
---|
381 | *
|
---|
382 | * @param string Input to split apart
|
---|
383 | * @return array Contains header and body section
|
---|
384 | * @access private
|
---|
385 | */
|
---|
386 | function _splitBodyHeader($input)
|
---|
387 | {
|
---|
388 | if (preg_match("/^(.*?)\r?\n\r?\n(.*)/s", $input, $match)) {
|
---|
389 | return array($match[1], $match[2]);
|
---|
390 | }
|
---|
391 | $this->_error = 'Could not split header and body';
|
---|
392 | return false;
|
---|
393 | }
|
---|
394 |
|
---|
395 | /**
|
---|
396 | * Parse headers given in $input and return
|
---|
397 | * as assoc array.
|
---|
398 | *
|
---|
399 | * @param string Headers to parse
|
---|
400 | * @return array Contains parsed headers
|
---|
401 | * @access private
|
---|
402 | */
|
---|
403 | function _parseHeaders($input)
|
---|
404 | {
|
---|
405 |
|
---|
406 | if ($input !== '') {
|
---|
407 | // Unfold the input
|
---|
408 | $input = preg_replace("/\r?\n/", "\r\n", $input);
|
---|
409 | $input = preg_replace("/\r\n(\t| )+/", ' ', $input);
|
---|
410 | $headers = explode("\r\n", trim($input));
|
---|
411 |
|
---|
412 | foreach ($headers as $value) {
|
---|
413 | $hdr_name = substr($value, 0, $pos = strpos($value, ':'));
|
---|
414 | $hdr_value = substr($value, $pos+1);
|
---|
415 | if($hdr_value[0] == ' ')
|
---|
416 | $hdr_value = substr($hdr_value, 1);
|
---|
417 |
|
---|
418 | $return[] = array(
|
---|
419 | 'name' => $hdr_name,
|
---|
420 | 'value' => $this->_decode_headers ? $this->_decodeHeader($hdr_value) : $hdr_value
|
---|
421 | );
|
---|
422 | }
|
---|
423 | } else {
|
---|
424 | $return = array();
|
---|
425 | }
|
---|
426 |
|
---|
427 | return $return;
|
---|
428 | }
|
---|
429 |
|
---|
430 | /**
|
---|
431 | * Function to parse a header value,
|
---|
432 | * extract first part, and any secondary
|
---|
433 | * parts (after ;) This function is not as
|
---|
434 | * robust as it could be. Eg. header comments
|
---|
435 | * in the wrong place will probably break it.
|
---|
436 | *
|
---|
437 | * @param string Header value to parse
|
---|
438 | * @return array Contains parsed result
|
---|
439 | * @access private
|
---|
440 | */
|
---|
441 | function _parseHeaderValue($input)
|
---|
442 | {
|
---|
443 |
|
---|
444 | if (($pos = strpos($input, ';')) !== false) {
|
---|
445 |
|
---|
446 | $return['value'] = trim(substr($input, 0, $pos));
|
---|
447 | $input = trim(substr($input, $pos+1));
|
---|
448 |
|
---|
449 | if (strlen($input) > 0) {
|
---|
450 |
|
---|
451 | // This splits on a semi-colon, if there's no preceeding backslash
|
---|
452 | // Now works with quoted values; had to glue the \; breaks in PHP
|
---|
453 | // the regex is already bordering on incomprehensible
|
---|
454 | $splitRegex = '/([^;\'"]*[\'"]([^\'"]*([^\'"]*)*)[\'"][^;\'"]*|([^;]+))(;|$)/';
|
---|
455 | preg_match_all($splitRegex, $input, $matches);
|
---|
456 | $parameters = array();
|
---|
457 | for ($i=0; $i<count($matches[0]); $i++) {
|
---|
458 | $param = $matches[0][$i];
|
---|
459 | while (substr($param, -2) == '\;') {
|
---|
460 | $param .= $matches[0][++$i];
|
---|
461 | }
|
---|
462 | $parameters[] = $param;
|
---|
463 | }
|
---|
464 |
|
---|
465 | for ($i = 0; $i < count($parameters); $i++) {
|
---|
466 | $param_name = trim(substr($parameters[$i], 0, $pos = strpos($parameters[$i], '=')), "'\";\t\\ ");
|
---|
467 | $param_value = trim(str_replace('\;', ';', substr($parameters[$i], $pos + 1)), "'\";\t\\ ");
|
---|
468 | if ($param_value[0] == '"') {
|
---|
469 | $param_value = substr($param_value, 1, -1);
|
---|
470 | }
|
---|
471 | $return['other'][$param_name] = $param_value;
|
---|
472 | $return['other'][strtolower($param_name)] = $param_value;
|
---|
473 | }
|
---|
474 | }
|
---|
475 | } else {
|
---|
476 | $return['value'] = trim($input);
|
---|
477 | }
|
---|
478 |
|
---|
479 | return $return;
|
---|
480 | }
|
---|
481 |
|
---|
482 | /**
|
---|
483 | * This function splits the input based
|
---|
484 | * on the given boundary
|
---|
485 | *
|
---|
486 | * @param string Input to parse
|
---|
487 | * @return array Contains array of resulting mime parts
|
---|
488 | * @access private
|
---|
489 | */
|
---|
490 | function _boundarySplit($input, $boundary)
|
---|
491 | {
|
---|
492 | $parts = array();
|
---|
493 |
|
---|
494 | $bs_possible = substr($boundary, 2, -2);
|
---|
495 | $bs_check = '\"' . $bs_possible . '\"';
|
---|
496 |
|
---|
497 | if ($boundary == $bs_check) {
|
---|
498 | $boundary = $bs_possible;
|
---|
499 | }
|
---|
500 |
|
---|
501 | $tmp = explode('--' . $boundary, $input);
|
---|
502 |
|
---|
503 | for ($i = 1; $i < count($tmp) - 1; $i++) {
|
---|
504 | $parts[] = $tmp[$i];
|
---|
505 | }
|
---|
506 |
|
---|
507 | return $parts;
|
---|
508 | }
|
---|
509 |
|
---|
510 | /**
|
---|
511 | * Given a header, this function will decode it
|
---|
512 | * according to RFC2047. Probably not *exactly*
|
---|
513 | * conformant, but it does pass all the given
|
---|
514 | * examples (in RFC2047).
|
---|
515 | *
|
---|
516 | * @param string Input header value to decode
|
---|
517 | * @return string Decoded header value
|
---|
518 | * @access private
|
---|
519 | */
|
---|
520 | function _decodeHeader($input)
|
---|
521 | {
|
---|
522 | // Remove white space between encoded-words
|
---|
523 | $input = preg_replace('/(=\?[^?]+\?(q|b)\?[^?]*\?=)(\s)+=\?/i', '\1=?', $input);
|
---|
524 |
|
---|
525 | // For each encoded-word...
|
---|
526 | while (preg_match('/(=\?([^?]+)\?(q|b)\?([^?]*)\?=)/i', $input, $matches)) {
|
---|
527 |
|
---|
528 | $encoded = $matches[1];
|
---|
529 | $charset = $matches[2];
|
---|
530 | $encoding = $matches[3];
|
---|
531 | $text = $matches[4];
|
---|
532 |
|
---|
533 | switch (strtolower($encoding)) {
|
---|
534 | case 'b':
|
---|
535 | $text = base64_decode($text);
|
---|
536 | break;
|
---|
537 |
|
---|
538 | case 'q':
|
---|
539 | $text = str_replace('_', ' ', $text);
|
---|
540 | preg_match_all('/=([a-f0-9]{2})/i', $text, $matches);
|
---|
541 | foreach($matches[1] as $value)
|
---|
542 | $text = str_replace('='.$value, chr(hexdec($value)), $text);
|
---|
543 | break;
|
---|
544 | }
|
---|
545 |
|
---|
546 | $input = str_replace($encoded, $text, $input);
|
---|
547 | }
|
---|
548 |
|
---|
549 | return $input;
|
---|
550 | }
|
---|
551 |
|
---|
552 | /**
|
---|
553 | * Given a body string and an encoding type,
|
---|
554 | * this function will decode and return it.
|
---|
555 | *
|
---|
556 | * @param string Input body to decode
|
---|
557 | * @param string Encoding type to use.
|
---|
558 | * @return string Decoded body
|
---|
559 | * @access private
|
---|
560 | */
|
---|
561 | function _decodeBody($input, $encoding = '7bit')
|
---|
562 | {
|
---|
563 | switch (strtolower($encoding)) {
|
---|
564 | case '7bit':
|
---|
565 | return $input;
|
---|
566 | break;
|
---|
567 |
|
---|
568 | case 'quoted-printable':
|
---|
569 | return $this->_quotedPrintableDecode($input);
|
---|
570 | break;
|
---|
571 |
|
---|
572 | case 'base64':
|
---|
573 | return base64_decode($input);
|
---|
574 | break;
|
---|
575 |
|
---|
576 | default:
|
---|
577 | return $input;
|
---|
578 | }
|
---|
579 | }
|
---|
580 |
|
---|
581 | /**
|
---|
582 | * Given a quoted-printable string, this
|
---|
583 | * function will decode and return it.
|
---|
584 | *
|
---|
585 | * @param string Input body to decode
|
---|
586 | * @return string Decoded body
|
---|
587 | * @access private
|
---|
588 | */
|
---|
589 | function _quotedPrintableDecode($input)
|
---|
590 | {
|
---|
591 | // Remove soft line breaks
|
---|
592 | $input = preg_replace("/=\r?\n/", '', $input);
|
---|
593 |
|
---|
594 | // Replace encoded characters
|
---|
595 | $input = preg_replace('/=([a-f0-9]{2})/ie', "chr(hexdec('\\1'))", $input);
|
---|
596 |
|
---|
597 | return $input;
|
---|
598 | }
|
---|
599 |
|
---|
600 | /**
|
---|
601 | * Checks the input for uuencoded files and returns
|
---|
602 | * an array of them. Can be called statically, eg:
|
---|
603 | *
|
---|
604 | * $files =& Mail_mimeDecode::uudecode($some_text);
|
---|
605 | *
|
---|
606 | * It will check for the begin 666 ... end syntax
|
---|
607 | * however and won't just blindly decode whatever you
|
---|
608 | * pass it.
|
---|
609 | *
|
---|
610 | * @param string Input body to look for attahcments in
|
---|
611 | * @return array Decoded bodies, filenames and permissions
|
---|
612 | * @access public
|
---|
613 | * @author Unknown
|
---|
614 | */
|
---|
615 | function &uudecode($input)
|
---|
616 | {
|
---|
617 | // Find all uuencoded sections
|
---|
618 | preg_match_all("/begin ([0-7]{3}) (.+)\r?\n(.+)\r?\nend/Us", $input, $matches);
|
---|
619 |
|
---|
620 | for ($j = 0; $j < count($matches[3]); $j++) {
|
---|
621 |
|
---|
622 | $str = $matches[3][$j];
|
---|
623 | $filename = $matches[2][$j];
|
---|
624 | $fileperm = $matches[1][$j];
|
---|
625 |
|
---|
626 | $file = '';
|
---|
627 | $str = preg_split("/\r?\n/", trim($str));
|
---|
628 | $strlen = count($str);
|
---|
629 |
|
---|
630 | for ($i = 0; $i < $strlen; $i++) {
|
---|
631 | $pos = 1;
|
---|
632 | $d = 0;
|
---|
633 | $len=(int)(((ord(substr($str[$i],0,1)) -32) - ' ') & 077);
|
---|
634 |
|
---|
635 | while (($d + 3 <= $len) AND ($pos + 4 <= strlen($str[$i]))) {
|
---|
636 | $c0 = (ord(substr($str[$i],$pos,1)) ^ 0x20);
|
---|
637 | $c1 = (ord(substr($str[$i],$pos+1,1)) ^ 0x20);
|
---|
638 | $c2 = (ord(substr($str[$i],$pos+2,1)) ^ 0x20);
|
---|
639 | $c3 = (ord(substr($str[$i],$pos+3,1)) ^ 0x20);
|
---|
640 | $file .= chr(((($c0 - ' ') & 077) << 2) | ((($c1 - ' ') & 077) >> 4));
|
---|
641 |
|
---|
642 | $file .= chr(((($c1 - ' ') & 077) << 4) | ((($c2 - ' ') & 077) >> 2));
|
---|
643 |
|
---|
644 | $file .= chr(((($c2 - ' ') & 077) << 6) | (($c3 - ' ') & 077));
|
---|
645 |
|
---|
646 | $pos += 4;
|
---|
647 | $d += 3;
|
---|
648 | }
|
---|
649 |
|
---|
650 | if (($d + 2 <= $len) && ($pos + 3 <= strlen($str[$i]))) {
|
---|
651 | $c0 = (ord(substr($str[$i],$pos,1)) ^ 0x20);
|
---|
652 | $c1 = (ord(substr($str[$i],$pos+1,1)) ^ 0x20);
|
---|
653 | $c2 = (ord(substr($str[$i],$pos+2,1)) ^ 0x20);
|
---|
654 | $file .= chr(((($c0 - ' ') & 077) << 2) | ((($c1 - ' ') & 077) >> 4));
|
---|
655 |
|
---|
656 | $file .= chr(((($c1 - ' ') & 077) << 4) | ((($c2 - ' ') & 077) >> 2));
|
---|
657 |
|
---|
658 | $pos += 3;
|
---|
659 | $d += 2;
|
---|
660 | }
|
---|
661 |
|
---|
662 | if (($d + 1 <= $len) && ($pos + 2 <= strlen($str[$i]))) {
|
---|
663 | $c0 = (ord(substr($str[$i],$pos,1)) ^ 0x20);
|
---|
664 | $c1 = (ord(substr($str[$i],$pos+1,1)) ^ 0x20);
|
---|
665 | $file .= chr(((($c0 - ' ') & 077) << 2) | ((($c1 - ' ') & 077) >> 4));
|
---|
666 |
|
---|
667 | }
|
---|
668 | }
|
---|
669 | $files[] = array('filename' => $filename, 'fileperm' => $fileperm, 'filedata' => $file);
|
---|
670 | }
|
---|
671 |
|
---|
672 | return $files;
|
---|
673 | }
|
---|
674 |
|
---|
675 | /**
|
---|
676 | * getSendArray() returns the arguments required for Mail::send()
|
---|
677 | * used to build the arguments for a mail::send() call
|
---|
678 | *
|
---|
679 | * Usage:
|
---|
680 | * $mailtext = Full email (for example generated by a template)
|
---|
681 | * $decoder = new Mail_mimeDecode($mailtext);
|
---|
682 | * $parts = $decoder->getSendArray();
|
---|
683 | * if (!PEAR::isError($parts) {
|
---|
684 | * list($recipents,$headers,$body) = $parts;
|
---|
685 | * $mail = Mail::factory('smtp');
|
---|
686 | * $mail->send($recipents,$headers,$body);
|
---|
687 | * } else {
|
---|
688 | * echo $parts->message;
|
---|
689 | * }
|
---|
690 | * @return mixed array of recipeint, headers,body or Pear_Error
|
---|
691 | * @access public
|
---|
692 | * @author Alan Knowles <alan@akbkhome.com>
|
---|
693 | */
|
---|
694 | function getSendArray()
|
---|
695 | {
|
---|
696 | // prevent warning if this is not set
|
---|
697 | $this->_decode_headers = FALSE;
|
---|
698 | $headerlist =$this->_parseHeaders($this->_header);
|
---|
699 | $to = "";
|
---|
700 | if (!$headerlist) {
|
---|
701 | return $this->raiseError("Message did not contain headers");
|
---|
702 | }
|
---|
703 | foreach($headerlist as $item) {
|
---|
704 | $header[$item['name']] = $item['value'];
|
---|
705 | switch (strtolower($item['name'])) {
|
---|
706 | case "to":
|
---|
707 | case "cc":
|
---|
708 | case "bcc":
|
---|
709 | $to = ",".$item['value'];
|
---|
710 | default:
|
---|
711 | break;
|
---|
712 | }
|
---|
713 | }
|
---|
714 | if ($to == "") {
|
---|
715 | return $this->raiseError("Message did not contain any recipents");
|
---|
716 | }
|
---|
717 | $to = substr($to,1);
|
---|
718 | return array($to,$header,$this->_body);
|
---|
719 | }
|
---|
720 |
|
---|
721 | /**
|
---|
722 | * Returns a xml copy of the output of
|
---|
723 | * Mail_mimeDecode::decode. Pass the output in as the
|
---|
724 | * argument. This function can be called statically. Eg:
|
---|
725 | *
|
---|
726 | * $output = $obj->decode();
|
---|
727 | * $xml = Mail_mimeDecode::getXML($output);
|
---|
728 | *
|
---|
729 | * The DTD used for this should have been in the package. Or
|
---|
730 | * alternatively you can get it from cvs, or here:
|
---|
731 | * http://www.phpguru.org/xmail/xmail.dtd.
|
---|
732 | *
|
---|
733 | * @param object Input to convert to xml. This should be the
|
---|
734 | * output of the Mail_mimeDecode::decode function
|
---|
735 | * @return string XML version of input
|
---|
736 | * @access public
|
---|
737 | */
|
---|
738 | function getXML($input)
|
---|
739 | {
|
---|
740 | $crlf = "\r\n";
|
---|
741 | $output = '<?xml version=\'1.0\'?>' . $crlf .
|
---|
742 | '<!DOCTYPE email SYSTEM "http://www.phpguru.org/xmail/xmail.dtd">' . $crlf .
|
---|
743 | '<email>' . $crlf .
|
---|
744 | Mail_mimeDecode::_getXML($input) .
|
---|
745 | '</email>';
|
---|
746 |
|
---|
747 | return $output;
|
---|
748 | }
|
---|
749 |
|
---|
750 | /**
|
---|
751 | * Function that does the actual conversion to xml. Does a single
|
---|
752 | * mimepart at a time.
|
---|
753 | *
|
---|
754 | * @param object Input to convert to xml. This is a mimepart object.
|
---|
755 | * It may or may not contain subparts.
|
---|
756 | * @param integer Number of tabs to indent
|
---|
757 | * @return string XML version of input
|
---|
758 | * @access private
|
---|
759 | */
|
---|
760 | function _getXML($input, $indent = 1)
|
---|
761 | {
|
---|
762 | $htab = "\t";
|
---|
763 | $crlf = "\r\n";
|
---|
764 | $output = '';
|
---|
765 | $headers = @(array)$input->headers;
|
---|
766 |
|
---|
767 | foreach ($headers as $hdr_name => $hdr_value) {
|
---|
768 |
|
---|
769 | // Multiple headers with this name
|
---|
770 | if (is_array($headers[$hdr_name])) {
|
---|
771 | for ($i = 0; $i < count($hdr_value); $i++) {
|
---|
772 | $output .= Mail_mimeDecode::_getXML_helper($hdr_name, $hdr_value[$i], $indent);
|
---|
773 | }
|
---|
774 |
|
---|
775 | // Only one header of this sort
|
---|
776 | } else {
|
---|
777 | $output .= Mail_mimeDecode::_getXML_helper($hdr_name, $hdr_value, $indent);
|
---|
778 | }
|
---|
779 | }
|
---|
780 |
|
---|
781 | if (!empty($input->parts)) {
|
---|
782 | for ($i = 0; $i < count($input->parts); $i++) {
|
---|
783 | $output .= $crlf . str_repeat($htab, $indent) . '<mimepart>' . $crlf .
|
---|
784 | Mail_mimeDecode::_getXML($input->parts[$i], $indent+1) .
|
---|
785 | str_repeat($htab, $indent) . '</mimepart>' . $crlf;
|
---|
786 | }
|
---|
787 | } elseif (isset($input->body)) {
|
---|
788 | $output .= $crlf . str_repeat($htab, $indent) . '<body><![CDATA[' .
|
---|
789 | $input->body . ']]></body>' . $crlf;
|
---|
790 | }
|
---|
791 |
|
---|
792 | return $output;
|
---|
793 | }
|
---|
794 |
|
---|
795 | /**
|
---|
796 | * Helper function to _getXML(). Returns xml of a header.
|
---|
797 | *
|
---|
798 | * @param string Name of header
|
---|
799 | * @param string Value of header
|
---|
800 | * @param integer Number of tabs to indent
|
---|
801 | * @return string XML version of input
|
---|
802 | * @access private
|
---|
803 | */
|
---|
804 | function _getXML_helper($hdr_name, $hdr_value, $indent)
|
---|
805 | {
|
---|
806 | $htab = "\t";
|
---|
807 | $crlf = "\r\n";
|
---|
808 | $return = '';
|
---|
809 |
|
---|
810 | $new_hdr_value = ($hdr_name != 'received') ? Mail_mimeDecode::_parseHeaderValue($hdr_value) : array('value' => $hdr_value);
|
---|
811 | $new_hdr_name = str_replace(' ', '-', ucwords(str_replace('-', ' ', $hdr_name)));
|
---|
812 |
|
---|
813 | // Sort out any parameters
|
---|
814 | if (!empty($new_hdr_value['other'])) {
|
---|
815 | foreach ($new_hdr_value['other'] as $paramname => $paramvalue) {
|
---|
816 | $params[] = str_repeat($htab, $indent) . $htab . '<parameter>' . $crlf .
|
---|
817 | str_repeat($htab, $indent) . $htab . $htab . '<paramname>' . htmlspecialchars($paramname) . '</paramname>' . $crlf .
|
---|
818 | str_repeat($htab, $indent) . $htab . $htab . '<paramvalue>' . htmlspecialchars($paramvalue) . '</paramvalue>' . $crlf .
|
---|
819 | str_repeat($htab, $indent) . $htab . '</parameter>' . $crlf;
|
---|
820 | }
|
---|
821 |
|
---|
822 | $params = implode('', $params);
|
---|
823 | } else {
|
---|
824 | $params = '';
|
---|
825 | }
|
---|
826 |
|
---|
827 | $return = str_repeat($htab, $indent) . '<header>' . $crlf .
|
---|
828 | str_repeat($htab, $indent) . $htab . '<headername>' . htmlspecialchars($new_hdr_name) . '</headername>' . $crlf .
|
---|
829 | str_repeat($htab, $indent) . $htab . '<headervalue>' . htmlspecialchars($new_hdr_value['value']) . '</headervalue>' . $crlf .
|
---|
830 | $params .
|
---|
831 | str_repeat($htab, $indent) . '</header>' . $crlf;
|
---|
832 |
|
---|
833 | return $return;
|
---|
834 | }
|
---|
835 |
|
---|
836 | } // End of class
|
---|
837 | ?>
|
---|