source: trunk/library/mime/mimeDecode.php @ 7673

Revision 7673, 36.5 KB checked in by douglasz, 11 years ago (diff)

Ticket #3236 - Correcoes para Performance: Function Within Loop Declaration.

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