- Timestamp:
- 2011/01/17 14:34:18 (12 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
branches/version-2_5-dev/data/module/Mail/mimeDecode.php
r16300 r19941 1 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 2 /** 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 */ 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$ 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 */ 93 91 class Mail_mimeDecode extends PEAR 94 92 { 95 93 /** 96 94 * The raw email to decode 95 * 97 96 * @var string 97 * @access private 98 98 */ 99 99 var $_input; … … 101 101 /** 102 102 * The header part of the input 103 * 103 104 * @var string 105 * @access private 104 106 */ 105 107 var $_header; … … 107 109 /** 108 110 * The body part of the input 111 * 109 112 * @var string 113 * @access private 110 114 */ 111 115 var $_body; … … 113 117 /** 114 118 * If an error occurs, this is used to store the message 119 * 115 120 * @var string 121 * @access private 116 122 */ 117 123 var $_error; … … 120 126 * Flag to determine whether to include bodies in the 121 127 * returned object. 128 * 122 129 * @var boolean 130 * @access private 123 131 */ 124 132 var $_include_bodies; … … 126 134 /** 127 135 * Flag to determine whether to decode bodies 136 * 128 137 * @var boolean 138 * @access private 129 139 */ 130 140 var $_decode_bodies; … … 132 142 /** 133 143 * Flag to determine whether to decode headers 144 * 134 145 * @var boolean 146 * @access private 135 147 */ 136 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; 137 158 138 159 /** … … 154 175 $this->_decode_bodies = false; 155 176 $this->_include_bodies = true; 177 $this->_rfc822_bodies = false; 156 178 } 157 179 … … 176 198 { 177 199 // determine if this method has been called statically 178 $isStatic = !(isset($this) && get_class($this) ==__CLASS__);200 $isStatic = empty($this) || !is_a($this, __CLASS__); 179 201 180 202 // Have we been called statically? … … 197 219 $this->_decode_headers = isset($params['decode_headers']) ? 198 220 $params['decode_headers'] : false; 221 $this->_rfc822_bodies = isset($params['rfc_822bodies']) ? 222 $params['rfc_822bodies'] : false; 199 223 200 224 $structure = $this->_decode($this->_header, $this->_body); … … 224 248 225 249 foreach ($headers as $value) { 250 $value['value'] = $this->_decode_headers ? $this->_decodeHeader($value['value']) : $value['value']; 226 251 if (isset($return->headers[strtolower($value['name'])]) AND !is_array($return->headers[strtolower($value['name'])])) { 227 252 $return->headers[strtolower($value['name'])] = array($return->headers[strtolower($value['name'])]); … … 236 261 } 237 262 238 reset($headers); 239 while (list($key, $value) = each($headers)) {263 264 foreach ($headers as $key => $value) { 240 265 $headers[$key]['name'] = strtolower($headers[$key]['name']); 241 266 switch ($headers[$key]['name']) { … … 250 275 251 276 if (isset($content_type['other'])) { 252 while (list($p_name, $p_value) = each($content_type['other'])) {277 foreach($content_type['other'] as $p_name => $p_value) { 253 278 $return->ctype_parameters[$p_name] = $p_value; 254 279 } … … 260 285 $return->disposition = $content_disposition['value']; 261 286 if (isset($content_disposition['other'])) { 262 while (list($p_name, $p_value) = each($content_disposition['other'])) {287 foreach($content_disposition['other'] as $p_name => $p_value) { 263 288 $return->d_parameters[$p_name] = $p_value; 264 289 } … … 285 310 286 311 case 'multipart/parallel': 312 case 'multipart/appledouble': // Appledouble mail 287 313 case 'multipart/report': // RFC1892 288 314 case 'multipart/signed': // PGP … … 291 317 case 'multipart/related': 292 318 case 'multipart/mixed': 319 case 'application/vnd.wap.multipart.related': 293 320 if(!isset($content_type['other']['boundary'])){ 294 321 $this->_error = 'No boundary found for ' . $content_type['value'] . ' part'; … … 309 336 310 337 case 'message/rfc822': 311 $obj = &new Mail_mimeDecode($body); 338 if ($this->_rfc822_bodies) { 339 $encoding = isset($content_transfer_encoding) ? $content_transfer_encoding['value'] : '7bit'; 340 $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $encoding) : $body); 341 } 342 $obj = new Mail_mimeDecode($body); 312 343 $return->parts[] = $obj->decode(array('include_bodies' => $this->_include_bodies, 313 344 'decode_bodies' => $this->_decode_bodies, … … 389 420 return array($match[1], $match[2]); 390 421 } 422 // bug #17325 - empty bodies are allowed. - we just check that at least one line 423 // of headers exist.. 424 if (count(explode("\n",$input))) { 425 return array($input, ''); 426 } 391 427 $this->_error = 'Could not split header and body'; 392 428 return false; … … 407 443 // Unfold the input 408 444 $input = preg_replace("/\r?\n/", "\r\n", $input); 445 //#7065 - wrapping.. with encoded stuff.. - probably not needed, 446 // wrapping space should only get removed if the trailing item on previous line is a 447 // encoded character 448 $input = preg_replace("/=\r\n(\t| )+/", '=', $input); 409 449 $input = preg_replace("/\r\n(\t| )+/", ' ', $input); 450 410 451 $headers = explode("\r\n", trim($input)); 411 452 … … 418 459 $return[] = array( 419 460 'name' => $hdr_name, 420 'value' => $this->_decode_headers ? $this->_decodeHeader($hdr_value) :$hdr_value461 'value' => $hdr_value 421 462 ); 422 463 } … … 442 483 { 443 484 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]; 485 if (($pos = strpos($input, ';')) === false) { 486 $input = $this->_decode_headers ? $this->_decodeHeader($input) : $input; 487 $return['value'] = trim($input); 488 return $return; 489 } 490 491 492 493 $value = substr($input, 0, $pos); 494 $value = $this->_decode_headers ? $this->_decodeHeader($value) : $value; 495 $return['value'] = trim($value); 496 $input = trim(substr($input, $pos+1)); 497 498 if (!strlen($input) > 0) { 499 return $return; 500 } 501 // at this point input contains xxxx=".....";zzzz="...." 502 // since we are dealing with quoted strings, we need to handle this properly.. 503 $i = 0; 504 $l = strlen($input); 505 $key = ''; 506 $val = false; // our string - including quotes.. 507 $q = false; // in quote.. 508 $lq = ''; // last quote.. 509 510 while ($i < $l) { 511 512 $c = $input[$i]; 513 //var_dump(array('i'=>$i,'c'=>$c,'q'=>$q, 'lq'=>$lq, 'key'=>$key, 'val' =>$val)); 514 515 $escaped = false; 516 if ($c == '\\') { 517 $i++; 518 if ($i == $l-1) { // end of string. 519 break; 520 } 521 $escaped = true; 522 $c = $input[$i]; 523 } 524 525 526 // state - in key.. 527 if ($val === false) { 528 if (!$escaped && $c == '=') { 529 $val = ''; 530 $key = trim($key); 531 $i++; 532 continue; 533 } 534 if (!$escaped && $c == ';') { 535 if ($key) { // a key without a value.. 536 $key= trim($key); 537 $return['other'][$key] = ''; 538 $return['other'][strtolower($key)] = ''; 461 539 } 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); 540 $key = ''; 541 } 542 $key .= $c; 543 $i++; 544 continue; 545 } 546 547 // state - in value.. (as $val is set..) 548 549 if ($q === false) { 550 // not in quote yet. 551 if ((!strlen($val) || $lq !== false) && $c == ' ' || $c == "\t") { 552 $i++; 553 continue; // skip leading spaces after '=' or after '"' 554 } 555 if (!$escaped && ($c == '"' || $c == "'")) { 556 // start quoted area.. 557 $q = $c; 558 // in theory should not happen raw text in value part.. 559 // but we will handle it as a merged part of the string.. 560 $val = !strlen(trim($val)) ? '' : trim($val); 561 $i++; 562 continue; 563 } 564 // got end.... 565 if (!$escaped && $c == ';') { 566 567 $val = trim($val); 568 $added = false; 569 if (preg_match('/\*[0-9]+$/', $key)) { 570 // this is the extended aaa*0=...;aaa*1=.... code 571 // it assumes the pieces arrive in order, and are valid... 572 $key = preg_replace('/\*[0-9]+$/', '', $key); 573 if (isset($return['other'][$key])) { 574 $return['other'][$key] .= $val; 575 if (strtolower($key) != $key) { 576 $return['other'][strtolower($key)] .= $val; 577 } 578 $added = true; 579 } 580 // continue and use standard setters.. 470 581 } 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 582 if (!$added) { 583 $return['other'][$key] = $val; 584 $return['other'][strtolower($key)] = $val; 585 } 586 $val = false; 587 $key = ''; 588 $lq = false; 589 $i++; 590 continue; 591 } 592 593 $val .= $c; 594 $i++; 595 continue; 596 } 597 598 // state - in quote.. 599 if (!$escaped && $c == $q) { // potential exit state.. 600 601 // end of quoted string.. 602 $lq = $q; 603 $q = false; 604 $i++; 605 continue; 606 } 607 608 // normal char inside of quoted string.. 609 $val.= $c; 610 $i++; 611 } 612 613 // do we have anything left.. 614 if (strlen(trim($key)) || $val !== false) { 615 616 $val = trim($val); 617 $added = false; 618 if ($val !== false && preg_match('/\*[0-9]+$/', $key)) { 619 // no dupes due to our crazy regexp. 620 $key = preg_replace('/\*[0-9]+$/', '', $key); 621 if (isset($return['other'][$key])) { 622 $return['other'][$key] .= $val; 623 if (strtolower($key) != $key) { 624 $return['other'][strtolower($key)] .= $val; 625 } 626 $added = true; 627 } 628 // continue and use standard setters.. 629 } 630 if (!$added) { 631 $return['other'][$key] = $val; 632 $return['other'][strtolower($key)] = $val; 633 } 634 } 635 // decode values. 636 foreach($return['other'] as $key =>$val) { 637 $return['other'][$key] = $this->_decode_headers ? $this->_decodeHeader($val) : $val; 638 } 639 //print_r($return); 479 640 return $return; 480 641 } … … 498 659 $boundary = $bs_possible; 499 660 } 500 501 $tmp = explode('--' . $boundary, $input); 502 503 for ($i = 1; $i < count($tmp) - 1; $i++) { 504 $parts[] = $tmp[$i]; 505 } 506 661 $tmp = preg_split("/--".preg_quote($boundary, '/')."((?=\s)|--)/", $input); 662 663 $len = count($tmp) -1; 664 for ($i = 1; $i < $len; $i++) { 665 if (strlen(trim($tmp[$i]))) { 666 $parts[] = $tmp[$i]; 667 } 668 } 669 670 // add the last part on if it does not end with the 'closing indicator' 671 if (!empty($tmp[$len]) && strlen(trim($tmp[$len])) && $tmp[$len][0] != '-') { 672 $parts[] = $tmp[$len]; 673 } 507 674 return $parts; 508 675 } … … 707 874 case "cc": 708 875 case "bcc": 709 $to = ",".$item['value'];876 $to .= ",".$item['value']; 710 877 default: 711 878 break; … … 835 1002 836 1003 } // End of class 837 ?>
Note: See TracChangeset
for help on using the changeset viewer.