Changeset 4898 for contrib/z-push/include/mimeDecode.php
- Timestamp:
- 08/03/11 12:00:08 (13 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
contrib/z-push/include/mimeDecode.php
r4613 r4898 102 102 class Mail_mimeDecode 103 103 { 104 /** 105 * The raw email to decode 106 * 107 * @var string 108 * @access private 109 */ 110 var $_input; 111 112 /** 113 * The header part of the input 114 * 115 * @var string 116 * @access private 117 */ 118 var $_header; 119 120 /** 121 * The body part of the input 122 * 123 * @var string 124 * @access private 125 */ 126 var $_body; 127 128 /** 129 * If an error occurs, this is used to store the message 130 * 131 * @var string 132 * @access private 133 */ 134 var $_error; 135 136 /** 137 * Flag to determine whether to include bodies in the 138 * returned object. 139 * 140 * @var boolean 141 * @access private 142 */ 143 var $_include_bodies; 144 145 /** 146 * Flag to determine whether to decode bodies 147 * 148 * @var boolean 149 * @access private 150 */ 151 var $_decode_bodies; 152 153 /** 154 * Flag to determine whether to decode headers 155 * 156 * @var boolean 157 * @access private 158 */ 159 var $_decode_headers; 160 161 /** 162 * Flag to determine whether to include attached messages 163 * as body in the returned object. Depends on $_include_bodies 164 * 165 * @var boolean 166 * @access private 167 */ 168 var $_rfc822_bodies; 169 170 /** 171 * Constructor. 172 * 173 * Sets up the object, initialise the variables, and splits and 174 * stores the header and body of the input. 175 * 176 * @param string The input to decode 177 * @access public 178 */ 179 function Mail_mimeDecode($input, $deprecated_linefeed = '') 180 { 181 list($header, $body) = $this->_splitBodyHeader($input); 182 183 $this->_input = $input; 184 $this->_header = $header; 185 $this->_body = $body; 186 $this->_decode_bodies = false; 187 $this->_include_bodies = true; 188 $this->_rfc822_bodies = false; 189 } 190 191 /** 192 * Begins the decoding process. If called statically 193 * it will create an object and call the decode() method 194 * of it. 195 * 196 * @param array An array of various parameters that determine 197 * various things: 198 * include_bodies - Whether to include the body in the returned 199 * object. 200 * decode_bodies - Whether to decode the bodies 201 * of the parts. (Transfer encoding) 202 * decode_headers - Whether to decode headers 203 * input - If called statically, this will be treated 204 * as the input 205 * charset - convert all data to this charset 206 * @return object Decoded results 207 * @access public 208 */ 209 function decode($params = null) 210 { 211 // determine if this method has been called statically 212 $isStatic = !(isset($this) && get_class($this) == __CLASS__); 213 214 // Have we been called statically? 215 // If so, create an object and pass details to that. 216 if ($isStatic AND isset($params['input'])) { 217 218 $obj = new Mail_mimeDecode($params['input']); 219 $structure = $obj->decode($params); 220 221 // Called statically but no input 222 } elseif ($isStatic) { 223 return $this->raiseError('Called statically and no input given'); 224 225 // Called via an object 226 } else { 227 $this->_include_bodies = isset($params['include_bodies']) ? 228 $params['include_bodies'] : false; 229 $this->_decode_bodies = isset($params['decode_bodies']) ? 230 $params['decode_bodies'] : false; 231 $this->_decode_headers = isset($params['decode_headers']) ? 232 $params['decode_headers'] : false; 233 $this->_rfc822_bodies = isset($params['rfc_822bodies']) ? 234 $params['rfc_822bodies'] : false; 235 $this->_charset = isset($params['charset']) ? 236 strtolower($params['charset']) : 'utf-8'; 237 238 $structure = $this->_decode($this->_header, $this->_body); 239 if ($structure === false) { 240 $structure = $this->raiseError($this->_error); 241 } 242 } 243 244 return $structure; 245 } 246 247 /** 248 * Performs the decoding. Decodes the body string passed to it 249 * If it finds certain content-types it will call itself in a 250 * recursive fashion 251 * 252 * @param string Header section 253 * @param string Body section 254 * @return object Results of decoding process 255 * @access private 256 */ 257 function _decode($headers, $body, $default_ctype = 'text/plain') 258 { 259 $return = new stdClass; 260 $return->headers = array(); 261 $headers = $this->_parseHeaders($headers); 262 263 foreach ($headers as $value) { 264 if (isset($return->headers[strtolower($value['name'])]) AND !is_array($return->headers[strtolower($value['name'])])) { 265 $return->headers[strtolower($value['name'])] = array($return->headers[strtolower($value['name'])]); 266 $return->headers[strtolower($value['name'])][] = $value['value']; 267 268 } elseif (isset($return->headers[strtolower($value['name'])])) { 269 $return->headers[strtolower($value['name'])][] = $value['value']; 270 271 } else { 272 $return->headers[strtolower($value['name'])] = $value['value']; 273 } 274 } 275 276 reset($headers); 277 while (list($key, $value) = each($headers)) { 278 $headers[$key]['name'] = strtolower($headers[$key]['name']); 279 switch ($headers[$key]['name']) { 280 281 case 'content-type': 282 $content_type = $this->_parseHeaderValue($headers[$key]['value']); 283 284 if (preg_match('/([0-9a-z+.-]+)\/([0-9a-z+.-]+)/i', $content_type['value'], $regs)) { 285 $return->ctype_primary = $regs[1]; 286 $return->ctype_secondary = $regs[2]; 287 } 288 289 if (isset($content_type['other'])) { 290 while (list($p_name, $p_value) = each($content_type['other'])) { 291 $return->ctype_parameters[$p_name] = $p_value; 292 } 293 } 294 break; 295 296 case 'content-disposition': 297 $content_disposition = $this->_parseHeaderValue($headers[$key]['value']); 298 $return->disposition = $content_disposition['value']; 299 if (isset($content_disposition['other'])) { 300 while (list($p_name, $p_value) = each($content_disposition['other'])) { 301 $return->d_parameters[$p_name] = $p_value; 302 } 303 } 304 break; 305 306 case 'content-transfer-encoding': 307 $content_transfer_encoding = $this->_parseHeaderValue($headers[$key]['value']); 308 break; 309 } 310 } 311 312 if (isset($content_type)) { 313 switch (strtolower($content_type['value'])) { 314 case 'text/plain': 315 $encoding = isset($content_transfer_encoding) ? $content_transfer_encoding['value'] : '7bit'; 316 $charset = isset($return->ctype_parameters['charset']) ? $return->ctype_parameters['charset'] : $this->_charset; 317 $this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $encoding, $charset) : $body) : null; 318 break; 319 320 case 'text/html': 321 $encoding = isset($content_transfer_encoding) ? $content_transfer_encoding['value'] : '7bit'; 322 $charset = isset($return->ctype_parameters['charset']) ? $return->ctype_parameters['charset'] : $this->_charset; 323 $this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $encoding, $charset) : $body) : null; 324 break; 325 326 case 'multipart/parallel': 327 case 'multipart/appledouble': // Appledouble mail 328 case 'multipart/report': // RFC1892 329 case 'multipart/signed': // PGP 330 case 'multipart/digest': 331 case 'multipart/alternative': 332 case 'multipart/related': 333 case 'multipart/mixed': 334 if(!isset($content_type['other']['boundary'])){ 335 $this->_error = 'No boundary found for ' . $content_type['value'] . ' part'; 336 return false; 337 } 338 339 $default_ctype = (strtolower($content_type['value']) === 'multipart/digest') ? 'message/rfc822' : 'text/plain'; 340 341 $parts = $this->_boundarySplit($body, $content_type['other']['boundary']); 342 for ($i = 0; $i < count($parts); $i++) { 343 list($part_header, $part_body) = $this->_splitBodyHeader($parts[$i]); 344 $part = $this->_decode($part_header, $part_body, $default_ctype); 345 if($part === false) 346 $part = $this->raiseError($this->_error); 347 $return->parts[] = $part; 348 } 349 break; 350 351 case 'message/rfc822': 352 if ($this->_rfc822_bodies) { 353 $encoding = isset($content_transfer_encoding) ? $content_transfer_encoding['value'] : '7bit'; 354 $charset = isset($return->ctype_parameters['charset']) ? $return->ctype_parameters['charset'] : $this->_charset; 355 $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $encoding, $charset) : $body); 356 } 357 358 $obj = new Mail_mimeDecode($body); 359 $return->parts[] = $obj->decode(array('include_bodies' => $this->_include_bodies, 360 'decode_bodies' => $this->_decode_bodies, 361 'decode_headers' => $this->_decode_headers)); 362 unset($obj); 363 break; 364 365 default: 366 if(!isset($content_transfer_encoding['value'])) 367 $content_transfer_encoding['value'] = '7bit'; 368 // if there is no explicit charset, then don't try to convert to default charset, and make sure that only text mimetypes are converted 369 $charset = (isset($return->ctype_parameters['charset']) && ((isset($return->ctype_primary) && $return->ctype_primary == 'text') || !isset($return->ctype_primary)) )? $return->ctype_parameters['charset']: ''; 370 $this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $content_transfer_encoding['value'], $charset) : $body) : null; 371 break; 372 } 373 374 } else { 375 $ctype = explode('/', $default_ctype); 376 $return->ctype_primary = $ctype[0]; 377 $return->ctype_secondary = $ctype[1]; 378 $this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body) : $body) : null; 379 } 380 381 return $return; 382 } 383 384 /** 385 * Given the output of the above function, this will return an 386 * array of references to the parts, indexed by mime number. 387 * 388 * @param object $structure The structure to go through 389 * @param string $mime_number Internal use only. 390 * @return array Mime numbers 391 */ 392 function &getMimeNumbers(&$structure, $no_refs = false, $mime_number = '', $prepend = '') 393 { 394 $return = array(); 395 if (!empty($structure->parts)) { 396 if ($mime_number != '') { 397 $structure->mime_id = $prepend . $mime_number; 398 $return[$prepend . $mime_number] = &$structure; 399 } 400 for ($i = 0; $i < count($structure->parts); $i++) { 401 402 403 if (!empty($structure->headers['content-type']) AND substr(strtolower($structure->headers['content-type']), 0, 8) == 'message/') { 404 $prepend = $prepend . $mime_number . '.'; 405 $_mime_number = ''; 406 } else { 407 $_mime_number = ($mime_number == '' ? $i + 1 : sprintf('%s.%s', $mime_number, $i + 1)); 408 } 409 410 $arr = &Mail_mimeDecode::getMimeNumbers($structure->parts[$i], $no_refs, $_mime_number, $prepend); 411 foreach ($arr as $key => $val) { 412 $no_refs ? $return[$key] = '' : $return[$key] = &$arr[$key]; 413 } 414 } 415 } else { 416 if ($mime_number == '') { 417 $mime_number = '1'; 418 } 419 $structure->mime_id = $prepend . $mime_number; 420 $no_refs ? $return[$prepend . $mime_number] = '' : $return[$prepend . $mime_number] = &$structure; 421 } 422 423 return $return; 424 } 425 426 /** 427 * Given a string containing a header and body 428 * section, this function will split them (at the first 429 * blank line) and return them. 430 * 431 * @param string Input to split apart 432 * @return array Contains header and body section 433 * @access private 434 */ 435 function _splitBodyHeader($input) 436 { 437 if (preg_match("/^(.*?)\r?\n\r?\n(.*)/s", $input, $match)) { 438 return array($match[1], $match[2]); 439 } 440 $this->_error = 'Could not split header and body'; 441 return false; 442 } 443 444 /** 445 * Parse headers given in $input and return 446 * as assoc array. 447 * 448 * @param string Headers to parse 449 * @return array Contains parsed headers 450 * @access private 451 */ 452 function _parseHeaders($input) 453 { 454 455 if ($input !== '') { 456 // Unfold the input 457 $input = preg_replace("/\r?\n/", "\r\n", $input); 458 $input = preg_replace("/\r\n(\t| )+/", ' ', $input); 459 $headers = explode("\r\n", trim($input)); 460 461 foreach ($headers as $value) { 462 $hdr_name = substr($value, 0, $pos = strpos($value, ':')); 463 $hdr_value = substr($value, $pos+1); 464 if($hdr_value[0] == ' ') 465 $hdr_value = substr($hdr_value, 1); 466 467 $return[] = array( 468 'name' => $hdr_name, 469 'value' => $this->_decode_headers ? $this->_decodeHeader($hdr_value) : $hdr_value 470 ); 471 } 472 } else { 473 $return = array(); 474 } 475 476 return $return; 477 } 478 479 /** 480 * Function to parse a header value, 481 * extract first part, and any secondary 482 * parts (after ;) This function is not as 483 * robust as it could be. Eg. header comments 484 * in the wrong place will probably break it. 485 * 486 * @param string Header value to parse 487 * @return array Contains parsed result 488 * @access private 489 */ 490 function _parseHeaderValue($input) 491 { 492 493 if (($pos = strpos($input, ';')) !== false) { 494 495 $return['value'] = trim(substr($input, 0, $pos)); 496 $input = trim(substr($input, $pos+1)); 497 498 if (strlen($input) > 0) { 499 500 // This splits on a semi-colon, if there's no preceeding backslash 501 // Now works with quoted values; had to glue the \; breaks in PHP 502 // the regex is already bordering on incomprehensible 503 //$splitRegex = '/([^;\'"]*[\'"]([^\'"]*([^\'"]*)*)[\'"][^;\'"]*|([^;]+))(;|$)/'; 504 // simplyfied RegEx - Nokia Mail2 sends boundaries containing ' which break the above regex 505 $splitRegex = '/([^;\'"]*[\'"]([^\'"]*)[\'"][^;\'"]*|([^;]+))(;|$)/'; 506 preg_match_all($splitRegex, $input, $matches); 507 508 $parameters = array(); 509 for ($i=0; $i<count($matches[0]); $i++) { 510 $param = $matches[0][$i]; 511 while (substr($param, -2) == '\;') { 512 $param .= $matches[0][++$i]; 513 } 514 $parameters[] = $param; 515 } 516 517 for ($i = 0; $i < count($parameters); $i++) { 518 $param_name = trim(substr($parameters[$i], 0, $pos = strpos($parameters[$i], '=')), "'\";\t\\ "); 519 $param_value = trim(str_replace('\;', ';', substr($parameters[$i], $pos + 1)), "'\";\t\\ "); 520 if (!empty($param_value[0]) && $param_value[0] == '"') { 521 $param_value = substr($param_value, 1, -1); 522 } 523 $return['other'][$param_name] = $param_value; 524 $return['other'][strtolower($param_name)] = $param_value; 525 } 526 } 527 } else { 528 $return['value'] = trim($input); 529 } 530 531 return $return; 532 } 533 534 /** 535 * This function splits the input based 536 * on the given boundary 537 * 538 * @param string Input to parse 539 * @return array Contains array of resulting mime parts 540 * @access private 541 */ 542 function _boundarySplit($input, $boundary) 543 { 544 $parts = array(); 545 546 $bs_possible = substr($boundary, 2, -2); 547 $bs_check = '\"' . $bs_possible . '\"'; 548 549 if ($boundary == $bs_check) { 550 $boundary = $bs_possible; 551 } 552 553 $tmp = explode('--' . $boundary, $input); 554 555 for ($i = 1; $i < count($tmp) - 1; $i++) { 556 $parts[] = $tmp[$i]; 557 } 558 559 return $parts; 560 } 561 562 /** 563 * Given a header, this function will decode it 564 * according to RFC2047. Probably not *exactly* 565 * conformant, but it does pass all the given 566 * examples (in RFC2047). 567 * 568 * @param string Input header value to decode 569 * @return string Decoded header value 570 * @access private 571 */ 572 function _decodeHeader($input) 573 { 574 // Remove white space between encoded-words 575 $input = preg_replace('/(=\?[^?]+\?(q|b)\?[^?]*\?=)(\s)+=\?/i', '\1=?', $input); 576 577 // For each encoded-word... 578 while (preg_match('/(=\?([^?]+)\?(q|b)\?([^?]*)\?=)/i', $input, $matches)) { 579 580 $encoded = $matches[1]; 581 $charset = $matches[2]; 582 $encoding = $matches[3]; 583 $text = $matches[4]; 584 585 switch (strtolower($encoding)) { 586 case 'b': 587 $text = base64_decode($text); 588 break; 589 590 case 'q': 591 $text = str_replace('_', ' ', $text); 592 preg_match_all('/=([a-f0-9]{2})/i', $text, $matches); 593 foreach($matches[1] as $value) 594 $text = str_replace('='.$value, chr(hexdec($value)), $text); 595 break; 596 } 597 598 $input = str_replace($encoded, $this->_fromCharset($charset, $text), $input); 599 } 600 601 return $input; 602 } 603 604 /** 605 * Given a body string and an encoding type, 606 * this function will decode and return it. 607 * 608 * @param string Input body to decode 609 * @param string Encoding type to use. 610 * @return string Decoded body 611 * @access private 612 */ 613 function _decodeBody($input, $encoding = '7bit', $charset = '') 614 { 615 switch (strtolower($encoding)) { 616 case '7bit': 617 return $this->_fromCharset($charset, $input);; 618 break; 619 620 case '8bit': 621 return $this->_fromCharset($charset, $input); 622 break; 623 624 case 'quoted-printable': 625 return $this->_fromCharset($charset, $this->_quotedPrintableDecode($input)); 626 break; 627 628 case 'base64': 629 return $this->_fromCharset($charset, base64_decode($input)); 630 break; 631 632 default: 633 return $input; 634 } 635 } 636 637 /** 638 * Given a quoted-printable string, this 639 * function will decode and return it. 640 * 641 * @param string Input body to decode 642 * @return string Decoded body 643 * @access private 644 */ 645 function _quotedPrintableDecode($input) 646 { 647 // Remove soft line breaks 648 $input = preg_replace("/=\r?\n/", '', $input); 649 650 // Replace encoded characters 651 $input = preg_replace('/=([a-f0-9]{2})/ie', "chr(hexdec('\\1'))", $input); 652 653 return $input; 654 } 655 656 /** 657 * Checks the input for uuencoded files and returns 658 * an array of them. Can be called statically, eg: 659 * 660 * $files =& Mail_mimeDecode::uudecode($some_text); 661 * 662 * It will check for the begin 666 ... end syntax 663 * however and won't just blindly decode whatever you 664 * pass it. 665 * 666 * @param string Input body to look for attahcments in 667 * @return array Decoded bodies, filenames and permissions 668 * @access public 669 * @author Unknown 670 */ 671 function &uudecode($input) 672 { 673 // Find all uuencoded sections 674 preg_match_all("/begin ([0-7]{3}) (.+)\r?\n(.+)\r?\nend/Us", $input, $matches); 675 676 for ($j = 0; $j < count($matches[3]); $j++) { 677 678 $str = $matches[3][$j]; 679 $filename = $matches[2][$j]; 680 $fileperm = $matches[1][$j]; 681 682 $file = ''; 683 $str = preg_split("/\r?\n/", trim($str)); 684 $strlen = count($str); 685 686 for ($i = 0; $i < $strlen; $i++) { 687 $pos = 1; 688 $d = 0; 689 $len=(int)(((ord(substr($str[$i],0,1)) -32) - ' ') & 077); 690 691 while (($d + 3 <= $len) AND ($pos + 4 <= strlen($str[$i]))) { 692 $c0 = (ord(substr($str[$i],$pos,1)) ^ 0x20); 693 $c1 = (ord(substr($str[$i],$pos+1,1)) ^ 0x20); 694 $c2 = (ord(substr($str[$i],$pos+2,1)) ^ 0x20); 695 $c3 = (ord(substr($str[$i],$pos+3,1)) ^ 0x20); 696 $file .= chr(((($c0 - ' ') & 077) << 2) | ((($c1 - ' ') & 077) >> 4)); 697 698 $file .= chr(((($c1 - ' ') & 077) << 4) | ((($c2 - ' ') & 077) >> 2)); 699 700 $file .= chr(((($c2 - ' ') & 077) << 6) | (($c3 - ' ') & 077)); 701 702 $pos += 4; 703 $d += 3; 704 } 705 706 if (($d + 2 <= $len) && ($pos + 3 <= strlen($str[$i]))) { 707 $c0 = (ord(substr($str[$i],$pos,1)) ^ 0x20); 708 $c1 = (ord(substr($str[$i],$pos+1,1)) ^ 0x20); 709 $c2 = (ord(substr($str[$i],$pos+2,1)) ^ 0x20); 710 $file .= chr(((($c0 - ' ') & 077) << 2) | ((($c1 - ' ') & 077) >> 4)); 711 712 $file .= chr(((($c1 - ' ') & 077) << 4) | ((($c2 - ' ') & 077) >> 2)); 713 714 $pos += 3; 715 $d += 2; 716 } 717 718 if (($d + 1 <= $len) && ($pos + 2 <= strlen($str[$i]))) { 719 $c0 = (ord(substr($str[$i],$pos,1)) ^ 0x20); 720 $c1 = (ord(substr($str[$i],$pos+1,1)) ^ 0x20); 721 $file .= chr(((($c0 - ' ') & 077) << 2) | ((($c1 - ' ') & 077) >> 4)); 722 723 } 724 } 725 $files[] = array('filename' => $filename, 'fileperm' => $fileperm, 'filedata' => $file); 726 } 727 728 return $files; 729 } 730 731 /** 732 * getSendArray() returns the arguments required for Mail::send() 733 * used to build the arguments for a mail::send() call 734 * 735 * Usage: 736 * $mailtext = Full email (for example generated by a template) 737 * $decoder = new Mail_mimeDecode($mailtext); 738 * $parts = $decoder->getSendArray(); 739 * if (!PEAR::isError($parts) { 740 * list($recipents,$headers,$body) = $parts; 741 * $mail = Mail::factory('smtp'); 742 * $mail->send($recipents,$headers,$body); 743 * } else { 744 * echo $parts->message; 745 * } 746 * @return mixed array of recipeint, headers,body or Pear_Error 747 * @access public 748 * @author Alan Knowles <alan@akbkhome.com> 749 */ 750 function getSendArray() 751 { 752 // prevent warning if this is not set 753 $this->_decode_headers = FALSE; 754 $headerlist =$this->_parseHeaders($this->_header); 755 $to = ""; 756 if (!$headerlist) { 757 return $this->raiseError("Message did not contain headers"); 758 } 759 foreach($headerlist as $item) { 760 $header[$item['name']] = $item['value']; 761 switch (strtolower($item['name'])) { 762 case "to": 763 case "cc": 764 case "bcc": 765 $to .= ",".$item['value']; 766 default: 767 break; 768 } 769 } 770 if ($to == "") { 771 return $this->raiseError("Message did not contain any recipents"); 772 } 773 $to = substr($to,1); 774 return array($to,$header,$this->_body); 775 } 776 777 /** 778 * Returns a xml copy of the output of 779 * Mail_mimeDecode::decode. Pass the output in as the 780 * argument. This function can be called statically. Eg: 781 * 782 * $output = $obj->decode(); 783 * $xml = Mail_mimeDecode::getXML($output); 784 * 785 * The DTD used for this should have been in the package. Or 786 * alternatively you can get it from cvs, or here: 787 * http://www.phpguru.org/xmail/xmail.dtd. 788 * 789 * @param object Input to convert to xml. This should be the 790 * output of the Mail_mimeDecode::decode function 791 * @return string XML version of input 792 * @access public 793 */ 794 function getXML($input) 795 { 796 $crlf = "\r\n"; 797 $output = '<?xml version=\'1.0\'?>' . $crlf . 104 /** 105 * The raw email to decode 106 * 107 * @var string 108 * @access private 109 */ 110 var $_input; 111 112 /** 113 * The header part of the input 114 * 115 * @var string 116 * @access private 117 */ 118 var $_header; 119 120 /** 121 * The body part of the input 122 * 123 * @var string 124 * @access private 125 */ 126 var $_body; 127 128 /** 129 * If an error occurs, this is used to store the message 130 * 131 * @var string 132 * @access private 133 */ 134 var $_error; 135 136 /** 137 * Flag to determine whether to include bodies in the 138 * returned object. 139 * 140 * @var boolean 141 * @access private 142 */ 143 var $_include_bodies; 144 145 /** 146 * Flag to determine whether to decode bodies 147 * 148 * @var boolean 149 * @access private 150 */ 151 var $_decode_bodies; 152 153 /** 154 * Flag to determine whether to decode headers 155 * 156 * @var boolean 157 * @access private 158 */ 159 var $_decode_headers; 160 161 /** 162 * Flag to determine whether to include attached messages 163 * as body in the returned object. Depends on $_include_bodies 164 * 165 * @var boolean 166 * @access private 167 */ 168 var $_rfc822_bodies; 169 170 /** 171 * Constructor. 172 * 173 * Sets up the object, initialise the variables, and splits and 174 * stores the header and body of the input. 175 * 176 * @param string The input to decode 177 * @access public 178 */ 179 function Mail_mimeDecode($input, $deprecated_linefeed = '') 180 { 181 list($header, $body) = $this->_splitBodyHeader($input); 182 183 $this->_input = $input; 184 $this->_header = $header; 185 $this->_body = $body; 186 $this->_decode_bodies = false; 187 $this->_include_bodies = true; 188 $this->_rfc822_bodies = false; 189 } 190 191 /** 192 * Begins the decoding process. If called statically 193 * it will create an object and call the decode() method 194 * of it. 195 * 196 * @param array An array of various parameters that determine 197 * various things: 198 * include_bodies - Whether to include the body in the returned 199 * object. 200 * decode_bodies - Whether to decode the bodies 201 * of the parts. (Transfer encoding) 202 * decode_headers - Whether to decode headers 203 * input - If called statically, this will be treated 204 * as the input 205 * charset - convert all data to this charset 206 * @return object Decoded results 207 * @access public 208 */ 209 function decode($params = null) 210 { 211 // determine if this method has been called statically 212 $isStatic = !(isset($this) && get_class($this) == __CLASS__); 213 214 // Have we been called statically? 215 // If so, create an object and pass details to that. 216 if ($isStatic AND isset($params['input'])) { 217 218 $obj = new Mail_mimeDecode($params['input']); 219 $structure = $obj->decode($params); 220 221 // Called statically but no input 222 } elseif ($isStatic) { 223 return $this->raiseError('Called statically and no input given'); 224 225 // Called via an object 226 } else { 227 $this->_include_bodies = isset($params['include_bodies']) ? 228 $params['include_bodies'] : false; 229 $this->_decode_bodies = isset($params['decode_bodies']) ? 230 $params['decode_bodies'] : false; 231 $this->_decode_headers = isset($params['decode_headers']) ? 232 $params['decode_headers'] : false; 233 $this->_rfc822_bodies = isset($params['rfc_822bodies']) ? 234 $params['rfc_822bodies'] : false; 235 $this->_charset = isset($params['charset']) ? 236 strtolower($params['charset']) : 'utf-8'; 237 238 $structure = $this->_decode($this->_header, $this->_body); 239 if ($structure === false) { 240 $structure = $this->raiseError($this->_error); 241 } 242 } 243 244 return $structure; 245 } 246 247 /** 248 * Performs the decoding. Decodes the body string passed to it 249 * If it finds certain content-types it will call itself in a 250 * recursive fashion 251 * 252 * @param string Header section 253 * @param string Body section 254 * @return object Results of decoding process 255 * @access private 256 */ 257 function _decode($headers, $body, $default_ctype = 'text/plain') 258 { 259 $return = new stdClass; 260 $return->headers = array(); 261 $headers = $this->_parseHeaders($headers); 262 263 foreach ($headers as $value) { 264 if (isset($return->headers[strtolower($value['name'])]) AND !is_array($return->headers[strtolower($value['name'])])) { 265 $return->headers[strtolower($value['name'])] = array($return->headers[strtolower($value['name'])]); 266 $return->headers[strtolower($value['name'])][] = $value['value']; 267 268 } elseif (isset($return->headers[strtolower($value['name'])])) { 269 $return->headers[strtolower($value['name'])][] = $value['value']; 270 271 } else { 272 $return->headers[strtolower($value['name'])] = $value['value']; 273 } 274 } 275 276 reset($headers); 277 while (list($key, $value) = each($headers)) { 278 $headers[$key]['name'] = strtolower($headers[$key]['name']); 279 switch ($headers[$key]['name']) { 280 281 case 'content-type': 282 $content_type = $this->_parseHeaderValue($headers[$key]['value']); 283 284 if (preg_match('/([0-9a-z+.-]+)\/([0-9a-z+.-]+)/i', $content_type['value'], $regs)) { 285 $return->ctype_primary = $regs[1]; 286 $return->ctype_secondary = $regs[2]; 287 } 288 289 if (isset($content_type['other'])) { 290 while (list($p_name, $p_value) = each($content_type['other'])) { 291 $return->ctype_parameters[$p_name] = $p_value; 292 } 293 } 294 break; 295 296 case 'content-disposition': 297 $content_disposition = $this->_parseHeaderValue($headers[$key]['value']); 298 $return->disposition = $content_disposition['value']; 299 if (isset($content_disposition['other'])) { 300 while (list($p_name, $p_value) = each($content_disposition['other'])) { 301 $return->d_parameters[$p_name] = $p_value; 302 } 303 } 304 break; 305 306 case 'content-transfer-encoding': 307 $content_transfer_encoding = $this->_parseHeaderValue($headers[$key]['value']); 308 break; 309 } 310 } 311 312 if (isset($content_type)) { 313 switch (strtolower($content_type['value'])) { 314 case 'text/plain': 315 $encoding = isset($content_transfer_encoding) ? $content_transfer_encoding['value'] : '7bit'; 316 $charset = isset($return->ctype_parameters['charset']) ? $return->ctype_parameters['charset'] : $this->_charset; 317 $this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $encoding, $charset) : $body) : null; 318 break; 319 320 case 'text/html': 321 $encoding = isset($content_transfer_encoding) ? $content_transfer_encoding['value'] : '7bit'; 322 $charset = isset($return->ctype_parameters['charset']) ? $return->ctype_parameters['charset'] : $this->_charset; 323 $this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $encoding, $charset) : $body) : null; 324 break; 325 326 case 'multipart/parallel': 327 case 'multipart/appledouble': // Appledouble mail 328 case 'multipart/report': // RFC1892 329 case 'multipart/signed': // PGP 330 case 'multipart/digest': 331 case 'multipart/alternative': 332 case 'multipart/related': 333 case 'multipart/mixed': 334 if(!isset($content_type['other']['boundary'])){ 335 $this->_error = 'No boundary found for ' . $content_type['value'] . ' part'; 336 return false; 337 } 338 339 $default_ctype = (strtolower($content_type['value']) === 'multipart/digest') ? 'message/rfc822' : 'text/plain'; 340 341 $parts = $this->_boundarySplit($body, $content_type['other']['boundary']); 342 for ($i = 0; $i < count($parts); $i++) { 343 list($part_header, $part_body) = $this->_splitBodyHeader($parts[$i]); 344 $part = $this->_decode($part_header, $part_body, $default_ctype); 345 if($part === false) 346 $part = $this->raiseError($this->_error); 347 $return->parts[] = $part; 348 } 349 break; 350 351 case 'message/rfc822': 352 if ($this->_rfc822_bodies) { 353 $encoding = isset($content_transfer_encoding) ? $content_transfer_encoding['value'] : '7bit'; 354 $charset = isset($return->ctype_parameters['charset']) ? $return->ctype_parameters['charset'] : $this->_charset; 355 $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $encoding, $charset) : $body); 356 } 357 358 $obj = new Mail_mimeDecode($body); 359 $return->parts[] = $obj->decode(array('include_bodies' => $this->_include_bodies, 360 'decode_bodies' => $this->_decode_bodies, 361 'decode_headers' => $this->_decode_headers)); 362 unset($obj); 363 break; 364 365 default: 366 if(!isset($content_transfer_encoding['value'])) 367 $content_transfer_encoding['value'] = '7bit'; 368 369 // if there is no explicit charset, then don't try to convert to default charset, and make sure that only text mimetypes are converted 370 $charset = (isset($return->ctype_parameters['charset']) && ((isset($return->ctype_primary) && $return->ctype_primary == 'text') || !isset($return->ctype_primary)) ) ? $return->ctype_parameters['charset']: ''; 371 $this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $content_transfer_encoding['value'], $charset) : $body) : null; 372 break; 373 } 374 375 } else { 376 $ctype = explode('/', $default_ctype); 377 $return->ctype_primary = $ctype[0]; 378 $return->ctype_secondary = $ctype[1]; 379 $this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body) : $body) : null; 380 } 381 382 return $return; 383 } 384 385 /** 386 * Given the output of the above function, this will return an 387 * array of references to the parts, indexed by mime number. 388 * 389 * @param object $structure The structure to go through 390 * @param string $mime_number Internal use only. 391 * @return array Mime numbers 392 */ 393 function &getMimeNumbers(&$structure, $no_refs = false, $mime_number = '', $prepend = '') 394 { 395 $return = array(); 396 if (!empty($structure->parts)) { 397 if ($mime_number != '') { 398 $structure->mime_id = $prepend . $mime_number; 399 $return[$prepend . $mime_number] = &$structure; 400 } 401 for ($i = 0; $i < count($structure->parts); $i++) { 402 if (!empty($structure->headers['content-type']) AND substr(strtolower($structure->headers['content-type']), 0, 8) == 'message/') { 403 $prepend = $prepend . $mime_number . '.'; 404 $_mime_number = ''; 405 } else { 406 $_mime_number = ($mime_number == '' ? $i + 1 : sprintf('%s.%s', $mime_number, $i + 1)); 407 } 408 409 $arr = &Mail_mimeDecode::getMimeNumbers($structure->parts[$i], $no_refs, $_mime_number, $prepend); 410 foreach ($arr as $key => $val) { 411 $no_refs ? $return[$key] = '' : $return[$key] = &$arr[$key]; 412 } 413 } 414 } else { 415 if ($mime_number == '') { 416 $mime_number = '1'; 417 } 418 $structure->mime_id = $prepend . $mime_number; 419 $no_refs ? $return[$prepend . $mime_number] = '' : $return[$prepend . $mime_number] = &$structure; 420 } 421 422 return $return; 423 } 424 425 /** 426 * Given a string containing a header and body 427 * section, this function will split them (at the first 428 * blank line) and return them. 429 * 430 * @param string Input to split apart 431 * @return array Contains header and body section 432 * @access private 433 */ 434 function _splitBodyHeader($input) 435 { 436 if (preg_match("/^(.*?)\r?\n\r?\n(.*)/s", $input, $match)) { 437 return array($match[1], $match[2]); 438 } 439 $this->_error = 'Could not split header and body'; 440 return false; 441 } 442 443 /** 444 * Parse headers given in $input and return 445 * as assoc array. 446 * 447 * @param string Headers to parse 448 * @return array Contains parsed headers 449 * @access private 450 */ 451 function _parseHeaders($input) 452 { 453 454 if ($input !== '') { 455 // Unfold the input 456 $input = preg_replace("/\r?\n/", "\r\n", $input); 457 $input = preg_replace("/\r\n(\t| )+/", ' ', $input); 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' => $this->_decode_headers ? $this->_decodeHeader($hdr_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 if (($pos = strpos($input, ';')) !== false) { 492 $return['value'] = trim(substr($input, 0, $pos)); 493 $input = trim(substr($input, $pos+1)); 494 495 if (strlen($input) > 0) { 496 // This splits on a semi-colon, if there's no preceeding backslash 497 // Now works with quoted values; had to glue the \; breaks in PHP 498 // the regex is already bordering on incomprehensible 499 //$splitRegex = '/([^;\'"]*[\'"]([^\'"]*([^\'"]*)*)[\'"][^;\'"]*|([^;]+))(;|$)/'; 500 // simplyfied RegEx - Nokia Mail2 sends boundaries containing ' which break the above regex 501 $splitRegex = '/([^;\'"]*[\'"]([^\'"]*)[\'"][^;\'"]*|([^;]+))(;|$)/'; 502 preg_match_all($splitRegex, $input, $matches); 503 504 $parameters = array(); 505 for ($i=0; $i<count($matches[0]); $i++) { 506 $param = $matches[0][$i]; 507 while (substr($param, -2) == '\;') { 508 $param .= $matches[0][++$i]; 509 } 510 $parameters[] = $param; 511 } 512 513 for ($i = 0; $i < count($parameters); $i++) { 514 $param_name = trim(substr($parameters[$i], 0, $pos = strpos($parameters[$i], '=')), "'\";\t\\ "); 515 $param_value = trim(str_replace('\;', ';', substr($parameters[$i], $pos + 1)), "'\";\t\\ "); 516 if (!empty($param_value[0]) && $param_value[0] == '"') { 517 $param_value = substr($param_value, 1, -1); 518 } 519 $return['other'][$param_name] = $param_value; 520 $return['other'][strtolower($param_name)] = $param_value; 521 } 522 } 523 } else { 524 $return['value'] = trim($input); 525 } 526 527 return $return; 528 } 529 530 /** 531 * This function splits the input based 532 * on the given boundary 533 * 534 * @param string Input to parse 535 * @return array Contains array of resulting mime parts 536 * @access private 537 */ 538 function _boundarySplit($input, $boundary) 539 { 540 $parts = array(); 541 542 $bs_possible = substr($boundary, 2, -2); 543 $bs_check = '\"' . $bs_possible . '\"'; 544 545 if ($boundary == $bs_check) { 546 $boundary = $bs_possible; 547 } 548 549 $tmp = explode('--' . $boundary, $input); 550 551 for ($i = 1; $i < count($tmp) - 1; $i++) { 552 $parts[] = $tmp[$i]; 553 } 554 555 return $parts; 556 } 557 558 /** 559 * Given a header, this function will decode it 560 * according to RFC2047. Probably not *exactly* 561 * conformant, but it does pass all the given 562 * examples (in RFC2047). 563 * 564 * @param string Input header value to decode 565 * @return string Decoded header value 566 * @access private 567 */ 568 function _decodeHeader($input) 569 { 570 // Remove white space between encoded-words 571 $input = preg_replace('/(=\?[^?]+\?(q|b)\?[^?]*\?=)(\s)+=\?/i', '\1=?', $input); 572 573 // For each encoded-word... 574 while (preg_match('/(=\?([^?]+)\?(q|b)\?([^?]*)\?=)/i', $input, $matches)) { 575 $encoded = $matches[1]; 576 $charset = $matches[2]; 577 $encoding = $matches[3]; 578 $text = $matches[4]; 579 580 switch (strtolower($encoding)) { 581 case 'b': 582 $text = base64_decode($text); 583 break; 584 585 case 'q': 586 $text = str_replace('_', ' ', $text); 587 preg_match_all('/=([a-f0-9]{2})/i', $text, $matches); 588 foreach($matches[1] as $value) 589 $text = str_replace('='.$value, chr(hexdec($value)), $text); 590 break; 591 } 592 593 $input = str_replace($encoded, $this->_fromCharset($charset, $text), $input); 594 } 595 596 return $input; 597 } 598 599 /** 600 * Given a body string and an encoding type, 601 * this function will decode and return it. 602 * 603 * @param string Input body to decode 604 * @param string Encoding type to use. 605 * @return string Decoded body 606 * @access private 607 */ 608 function _decodeBody($input, $encoding = '7bit', $charset = '') 609 { 610 switch (strtolower($encoding)) { 611 case '7bit': 612 return $this->_fromCharset($charset, $input);; 613 break; 614 615 case '8bit': 616 return $this->_fromCharset($charset, $input); 617 break; 618 619 case 'quoted-printable': 620 return $this->_fromCharset($charset, $this->_quotedPrintableDecode($input)); 621 break; 622 623 case 'base64': 624 return $this->_fromCharset($charset, base64_decode($input)); 625 break; 626 627 default: 628 return $input; 629 } 630 } 631 632 /** 633 * Given a quoted-printable string, this 634 * function will decode and return it. 635 * 636 * @param string Input body to decode 637 * @return string Decoded body 638 * @access private 639 */ 640 function _quotedPrintableDecode($input) 641 { 642 // Remove soft line breaks 643 $input = preg_replace("/=\r?\n/", '', $input); 644 645 // Replace encoded characters 646 $input = preg_replace('/=([a-f0-9]{2})/ie', "chr(hexdec('\\1'))", $input); 647 648 return $input; 649 } 650 651 /** 652 * Checks the input for uuencoded files and returns 653 * an array of them. Can be called statically, eg: 654 * 655 * $files =& Mail_mimeDecode::uudecode($some_text); 656 * 657 * It will check for the begin 666 ... end syntax 658 * however and won't just blindly decode whatever you 659 * pass it. 660 * 661 * @param string Input body to look for attahcments in 662 * @return array Decoded bodies, filenames and permissions 663 * @access public 664 * @author Unknown 665 */ 666 function &uudecode($input) 667 { 668 // Find all uuencoded sections 669 preg_match_all("/begin ([0-7]{3}) (.+)\r?\n(.+)\r?\nend/Us", $input, $matches); 670 671 for ($j = 0; $j < count($matches[3]); $j++) { 672 $str = $matches[3][$j]; 673 $filename = $matches[2][$j]; 674 $fileperm = $matches[1][$j]; 675 676 $file = ''; 677 $str = preg_split("/\r?\n/", trim($str)); 678 $strlen = count($str); 679 680 for ($i = 0; $i < $strlen; $i++) { 681 $pos = 1; 682 $d = 0; 683 $len=(int)(((ord(substr($str[$i],0,1)) -32) - ' ') & 077); 684 685 while (($d + 3 <= $len) AND ($pos + 4 <= strlen($str[$i]))) { 686 $c0 = (ord(substr($str[$i],$pos,1)) ^ 0x20); 687 $c1 = (ord(substr($str[$i],$pos+1,1)) ^ 0x20); 688 $c2 = (ord(substr($str[$i],$pos+2,1)) ^ 0x20); 689 $c3 = (ord(substr($str[$i],$pos+3,1)) ^ 0x20); 690 $file .= chr(((($c0 - ' ') & 077) << 2) | ((($c1 - ' ') & 077) >> 4)); 691 692 $file .= chr(((($c1 - ' ') & 077) << 4) | ((($c2 - ' ') & 077) >> 2)); 693 694 $file .= chr(((($c2 - ' ') & 077) << 6) | (($c3 - ' ') & 077)); 695 696 $pos += 4; 697 $d += 3; 698 } 699 700 if (($d + 2 <= $len) && ($pos + 3 <= strlen($str[$i]))) { 701 $c0 = (ord(substr($str[$i],$pos,1)) ^ 0x20); 702 $c1 = (ord(substr($str[$i],$pos+1,1)) ^ 0x20); 703 $c2 = (ord(substr($str[$i],$pos+2,1)) ^ 0x20); 704 $file .= chr(((($c0 - ' ') & 077) << 2) | ((($c1 - ' ') & 077) >> 4)); 705 706 $file .= chr(((($c1 - ' ') & 077) << 4) | ((($c2 - ' ') & 077) >> 2)); 707 708 $pos += 3; 709 $d += 2; 710 } 711 712 if (($d + 1 <= $len) && ($pos + 2 <= strlen($str[$i]))) { 713 $c0 = (ord(substr($str[$i],$pos,1)) ^ 0x20); 714 $c1 = (ord(substr($str[$i],$pos+1,1)) ^ 0x20); 715 $file .= chr(((($c0 - ' ') & 077) << 2) | ((($c1 - ' ') & 077) >> 4)); 716 } 717 } 718 $files[] = array('filename' => $filename, 'fileperm' => $fileperm, 'filedata' => $file); 719 } 720 721 return $files; 722 } 723 724 /** 725 * getSendArray() returns the arguments required for Mail::send() 726 * used to build the arguments for a mail::send() call 727 * 728 * Usage: 729 * $mailtext = Full email (for example generated by a template) 730 * $decoder = new Mail_mimeDecode($mailtext); 731 * $parts = $decoder->getSendArray(); 732 * if (!PEAR::isError($parts) { 733 * list($recipents,$headers,$body) = $parts; 734 * $mail = Mail::factory('smtp'); 735 * $mail->send($recipents,$headers,$body); 736 * } else { 737 * echo $parts->message; 738 * } 739 * @return mixed array of recipeint, headers,body or Pear_Error 740 * @access public 741 * @author Alan Knowles <alan@akbkhome.com> 742 */ 743 function getSendArray() 744 { 745 // prevent warning if this is not set 746 $this->_decode_headers = FALSE; 747 $headerlist =$this->_parseHeaders($this->_header); 748 $to = ""; 749 if (!$headerlist) { 750 return $this->raiseError("Message did not contain headers"); 751 } 752 foreach($headerlist as $item) { 753 $header[$item['name']] = $item['value']; 754 switch (strtolower($item['name'])) { 755 case "to": 756 case "cc": 757 case "bcc": 758 $to .= ",".$item['value']; 759 default: 760 break; 761 } 762 } 763 if ($to == "") { 764 return $this->raiseError("Message did not contain any recipents"); 765 } 766 $to = substr($to,1); 767 return array($to,$header,$this->_body); 768 } 769 770 /** 771 * Returns a xml copy of the output of 772 * Mail_mimeDecode::decode. Pass the output in as the 773 * argument. This function can be called statically. Eg: 774 * 775 * $output = $obj->decode(); 776 * $xml = Mail_mimeDecode::getXML($output); 777 * 778 * The DTD used for this should have been in the package. Or 779 * alternatively you can get it from cvs, or here: 780 * http://www.phpguru.org/xmail/xmail.dtd. 781 * 782 * @param object Input to convert to xml. This should be the 783 * output of the Mail_mimeDecode::decode function 784 * @return string XML version of input 785 * @access public 786 */ 787 function getXML($input) 788 { 789 $crlf = "\r\n"; 790 $output = '<?xml version=\'1.0\'?>' . $crlf . 798 791 '<!DOCTYPE email SYSTEM "http://www.phpguru.org/xmail/xmail.dtd">' . $crlf . 799 792 '<email>' . $crlf . 800 793 Mail_mimeDecode::_getXML($input) . 801 794 '</email>'; 802 795 803 return $output; 804 } 805 806 /** 807 * Function that does the actual conversion to xml. Does a single 808 * mimepart at a time. 809 * 810 * @param object Input to convert to xml. This is a mimepart object. 811 * It may or may not contain subparts. 812 * @param integer Number of tabs to indent 813 * @return string XML version of input 814 * @access private 815 */ 816 function _getXML($input, $indent = 1) 817 { 818 $htab = "\t"; 819 $crlf = "\r\n"; 820 $output = ''; 821 $headers = @(array)$input->headers; 822 823 foreach ($headers as $hdr_name => $hdr_value) { 824 825 // Multiple headers with this name 826 if (is_array($headers[$hdr_name])) { 827 for ($i = 0; $i < count($hdr_value); $i++) { 828 $output .= Mail_mimeDecode::_getXML_helper($hdr_name, $hdr_value[$i], $indent); 829 } 830 831 // Only one header of this sort 832 } else { 833 $output .= Mail_mimeDecode::_getXML_helper($hdr_name, $hdr_value, $indent); 834 } 835 } 836 837 if (!empty($input->parts)) { 838 for ($i = 0; $i < count($input->parts); $i++) { 839 $output .= $crlf . str_repeat($htab, $indent) . '<mimepart>' . $crlf . 840 Mail_mimeDecode::_getXML($input->parts[$i], $indent+1) . 841 str_repeat($htab, $indent) . '</mimepart>' . $crlf; 842 } 843 } elseif (isset($input->body)) { 844 $output .= $crlf . str_repeat($htab, $indent) . '<body><![CDATA[' . 845 $input->body . ']]></body>' . $crlf; 846 } 847 848 return $output; 849 } 850 851 /** 852 * Helper function to _getXML(). Returns xml of a header. 853 * 854 * @param string Name of header 855 * @param string Value of header 856 * @param integer Number of tabs to indent 857 * @return string XML version of input 858 * @access private 859 */ 860 function _getXML_helper($hdr_name, $hdr_value, $indent) 861 { 862 $htab = "\t"; 863 $crlf = "\r\n"; 864 $return = ''; 865 866 $new_hdr_value = ($hdr_name != 'received') ? Mail_mimeDecode::_parseHeaderValue($hdr_value) : array('value' => $hdr_value); 867 $new_hdr_name = str_replace(' ', '-', ucwords(str_replace('-', ' ', $hdr_name))); 868 869 // Sort out any parameters 870 if (!empty($new_hdr_value['other'])) { 871 foreach ($new_hdr_value['other'] as $paramname => $paramvalue) { 872 $params[] = str_repeat($htab, $indent) . $htab . '<parameter>' . $crlf . 873 str_repeat($htab, $indent) . $htab . $htab . '<paramname>' . htmlspecialchars($paramname) . '</paramname>' . $crlf . 874 str_repeat($htab, $indent) . $htab . $htab . '<paramvalue>' . htmlspecialchars($paramvalue) . '</paramvalue>' . $crlf . 875 str_repeat($htab, $indent) . $htab . '</parameter>' . $crlf; 876 } 877 878 $params = implode('', $params); 879 } else { 880 $params = ''; 881 } 882 883 $return = str_repeat($htab, $indent) . '<header>' . $crlf . 884 str_repeat($htab, $indent) . $htab . '<headername>' . htmlspecialchars($new_hdr_name) . '</headername>' . $crlf . 885 str_repeat($htab, $indent) . $htab . '<headervalue>' . htmlspecialchars($new_hdr_value['value']) . '</headervalue>' . $crlf . 886 $params . 887 str_repeat($htab, $indent) . '</header>' . $crlf; 888 889 return $return; 890 } 891 892 /** 893 * Z-Push helper to decode text 894 * 895 * @param string current charset of input 896 * @param string input 897 * @return string XML version of input 898 * @access private 899 */ 900 function _fromCharset($charset, $input) { 901 if($charset == '' || (strtolower($charset) == $this->_charset)) 902 return $input; 903 904 return @iconv($charset, $this->_charset. "//TRANSLIT", $input); 905 } 906 907 /** 908 * Z-Push helper for error logging 909 * removing PEAR dependency 910 * 911 * @param string debug message 912 * @return boolean always false as there was an error 913 * @access private 914 */ 915 function raiseError($message) { 916 debugLog("mimeDecode error: ". $message); 917 return false; 918 } 796 return $output; 797 } 798 799 /** 800 * Function that does the actual conversion to xml. Does a single 801 * mimepart at a time. 802 * 803 * @param object Input to convert to xml. This is a mimepart object. 804 * It may or may not contain subparts. 805 * @param integer Number of tabs to indent 806 * @return string XML version of input 807 * @access private 808 */ 809 function _getXML($input, $indent = 1) 810 { 811 $htab = "\t"; 812 $crlf = "\r\n"; 813 $output = ''; 814 $headers = @(array)$input->headers; 815 816 foreach ($headers as $hdr_name => $hdr_value) { 817 // Multiple headers with this name 818 if (is_array($headers[$hdr_name])) { 819 for ($i = 0; $i < count($hdr_value); $i++) { 820 $output .= Mail_mimeDecode::_getXML_helper($hdr_name, $hdr_value[$i], $indent); 821 } 822 823 // Only one header of this sort 824 } else { 825 $output .= Mail_mimeDecode::_getXML_helper($hdr_name, $hdr_value, $indent); 826 } 827 } 828 829 if (!empty($input->parts)) { 830 for ($i = 0; $i < count($input->parts); $i++) { 831 $output .= $crlf . str_repeat($htab, $indent) . '<mimepart>' . $crlf . 832 Mail_mimeDecode::_getXML($input->parts[$i], $indent+1) . 833 str_repeat($htab, $indent) . '</mimepart>' . $crlf; 834 } 835 } elseif (isset($input->body)) { 836 $output .= $crlf . str_repeat($htab, $indent) . '<body><![CDATA[' . 837 $input->body . ']]></body>' . $crlf; 838 } 839 840 return $output; 841 } 842 843 /** 844 * Helper function to _getXML(). Returns xml of a header. 845 * 846 * @param string Name of header 847 * @param string Value of header 848 * @param integer Number of tabs to indent 849 * @return string XML version of input 850 * @access private 851 */ 852 function _getXML_helper($hdr_name, $hdr_value, $indent) 853 { 854 $htab = "\t"; 855 $crlf = "\r\n"; 856 $return = ''; 857 858 $new_hdr_value = ($hdr_name != 'received') ? Mail_mimeDecode::_parseHeaderValue($hdr_value) : array('value' => $hdr_value); 859 $new_hdr_name = str_replace(' ', '-', ucwords(str_replace('-', ' ', $hdr_name))); 860 861 // Sort out any parameters 862 if (!empty($new_hdr_value['other'])) { 863 foreach ($new_hdr_value['other'] as $paramname => $paramvalue) { 864 $params[] = str_repeat($htab, $indent) . $htab . '<parameter>' . $crlf . 865 str_repeat($htab, $indent) . $htab . $htab . '<paramname>' . htmlspecialchars($paramname) . '</paramname>' . $crlf . 866 str_repeat($htab, $indent) . $htab . $htab . '<paramvalue>' . htmlspecialchars($paramvalue) . '</paramvalue>' . $crlf . 867 str_repeat($htab, $indent) . $htab . '</parameter>' . $crlf; 868 } 869 870 $params = implode('', $params); 871 } else { 872 $params = ''; 873 } 874 875 $return = str_repeat($htab, $indent) . '<header>' . $crlf . 876 str_repeat($htab, $indent) . $htab . '<headername>' . htmlspecialchars($new_hdr_name) . '</headername>' . $crlf . 877 str_repeat($htab, $indent) . $htab . '<headervalue>' . htmlspecialchars($new_hdr_value['value']) . '</headervalue>' . $crlf . 878 $params . 879 str_repeat($htab, $indent) . '</header>' . $crlf; 880 881 return $return; 882 } 883 884 /** 885 * Z-Push helper to decode text 886 * 887 * @param string current charset of input 888 * @param string input 889 * @return string XML version of input 890 * @access private 891 */ 892 function _fromCharset($charset, $input) { 893 if($charset == '' || (strtolower($charset) == $this->_charset)) 894 return $input; 895 896 return @iconv($charset, $this->_charset. "//TRANSLIT", $input); 897 } 898 899 /** 900 * Z-Push helper for error logging 901 * removing PEAR dependency 902 * 903 * @param string debug message 904 * @return boolean always false as there was an error 905 * @access private 906 */ 907 function raiseError($message) { 908 debugLog("mimeDecode error: ". $message); 909 return false; 910 } 919 911 } // End of class
Note: See TracChangeset
for help on using the changeset viewer.