OSDN Git Service

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