home *** CD-ROM | disk | FTP | other *** search
/ PC World 2003 March / PCWorld_2003-03_cd.bin / Software / Vyzkuste / phptriad / phptriad2-2-1.exe / php / pear / Mail / mimeDecode.php < prev    next >
Encoding:
PHP Script  |  2001-11-13  |  24.8 KB  |  693 lines

  1. <?php
  2. //
  3. // +----------------------------------------------------------------------+
  4. // | PHP version 4.0                                                      |
  5. // +----------------------------------------------------------------------+
  6. // | Copyright (c) 1997-2001 The PHP Group                                |
  7. // +----------------------------------------------------------------------+
  8. // | This source file is subject to version 2.02 of the PHP license,      |
  9. // | that is bundled with this package in the file LICENSE, and is        |
  10. // | available at through the world-wide-web at                           |
  11. // | http://www.php.net/license/2_02.txt.                                 |
  12. // | If you did not receive a copy of the PHP license and are unable to   |
  13. // | obtain it through the world-wide-web, please send a note to          |
  14. // | license@php.net so we can mail you a copy immediately.               |
  15. // +----------------------------------------------------------------------+
  16. // | Authors: Richard Heyes <richard@phpguru.org>                         |
  17. // +----------------------------------------------------------------------+
  18.  
  19.     require_once('PEAR.php');
  20.  
  21. /**
  22. *  +----------------------------- IMPORTANT ------------------------------+
  23. *  | Usage of this class compared to native php extensions such as        |
  24. *  | mailparse or imap, is slow and may be feature deficient. If available|
  25. *  | you are STRONGLY recommended to use the php extensions.              |
  26. *  +----------------------------------------------------------------------+
  27. *
  28. * Mime Decoding class
  29. *
  30. * This class will parse a raw mime email and return
  31. * the structure. Returned structure is similar to 
  32. * that returned by imap_fetchstructure().
  33. *
  34. * USAGE: (assume $input is your raw email)
  35. *
  36. * $decode = new Mail_mimeDecode($input, "\r\n");
  37. * $structure = $decode->decode();
  38. * print_r($structure);
  39. *
  40. * Or statically:
  41. *
  42. * $params['input'] = $input;
  43. * $structure = Mail_mimeDecode::decode($params);
  44. * print_r($structure);
  45. *
  46. * TODO:
  47. *  - Implement further content types, eg. multipart/parallel,
  48. *    perhaps even message/partial.
  49. *
  50. * @author  Richard Heyes <richard@phpguru.org>
  51. * @version $Revision: 1.6.2.2 $
  52. * @package Mail
  53. */
  54.  
  55. class Mail_mimeDecode extends PEAR{
  56.  
  57.     /**
  58.      * The raw email to decode
  59.      * @var    string
  60.      */
  61.     var $_input;
  62.  
  63.     /**
  64.      * The header part of the input
  65.      * @var    string
  66.      */
  67.     var $_header;
  68.  
  69.     /**
  70.      * The body part of the input
  71.      * @var    string
  72.      */
  73.     var $_body;
  74.  
  75.     /**
  76.      * If an error occurs, this is used to store the message
  77.      * @var    string
  78.      */
  79.     var $_error;
  80.  
  81.     /**
  82.      * Flag to determine whether to include bodies in the
  83.      * returned object.
  84.      * @var    boolean
  85.      */
  86.     var $_include_bodies;
  87.  
  88.     /**
  89.      * Flag to determine whether to decode bodies
  90.      * @var    boolean
  91.      */
  92.     var $_decode_bodies;
  93.  
  94.     /**
  95.      * Flag to determine whether to decode headers
  96.      * @var    boolean
  97.      */
  98.     var $_decode_headers;
  99.  
  100.     /**
  101.      * Variable to hold the line end type.
  102.      * @var    string
  103.      */
  104.     var $_crlf;
  105.  
  106.     /**
  107.      * Constructor.
  108.      * 
  109.      * Sets up the object, initialise the variables, and splits and
  110.      * stores the header and body of the input.
  111.      *
  112.      * @param string The input to decode
  113.      * @param string CRLF type to use (CRLF/LF/CR)
  114.      * @access public
  115.      */
  116.     function Mail_mimeDecode($input, $crlf = "\r\n")
  117.     {
  118.  
  119.         $this->_crlf = $crlf;
  120.         list($header, $body) = $this->_splitBodyHeader($input);
  121.  
  122.         $this->_input          = $input;
  123.         $this->_header         = $header;
  124.         $this->_body           = $body;
  125.         $this->_decode_bodies  = false;
  126.         $this->_include_bodies = true;
  127.     }
  128.  
  129.     /**
  130.      * Begins the decoding process. If called statically
  131.      * it will create an object and call the decode() method
  132.      * of it.
  133.      * 
  134.      * @param array An array of various parameters that determine
  135.      *              various things:
  136.      *              include_bodies - Whether to include the body in the returned
  137.      *                               object.
  138.      *              decode_bodies  - Whether to decode the bodies
  139.      *                               of the parts. (Transfer encoding)
  140.      *              decode_headers - Whether to decode headers
  141.      *              input          - If called statically, this will be treated
  142.      *                               as the input
  143.      *              crlf           - If called statically, this will be used as
  144.      *                               the crlf value.
  145.      * @return object Decoded results
  146.      * @access public
  147.      */
  148.     function decode($params = null)
  149.     {
  150.  
  151.         // Have we been called statically? If so, create an object and pass details to that.
  152.         if (!isset($this) AND isset($params['input'])) {
  153.  
  154.             if (isset($params['crlf']))
  155.                 $obj = new Mail_mimeDecode($params['input'], $params['crlf']);
  156.             else
  157.                 $obj = new Mail_mimeDecode($params['input']);
  158.             $structure = $obj->decode($params);
  159.  
  160.         // Called statically but no input
  161.         } elseif (!isset($this)) {
  162.             return $this->raiseError('Called statically and no input given');
  163.  
  164.         // Called via an object
  165.         } else {
  166.             $this->_include_bodies = isset($params['include_bodies'])  ? $params['include_bodies']  : false;
  167.             $this->_decode_bodies  = isset($params['decode_bodies'])   ? $params['decode_bodies']   : false;
  168.             $this->_decode_headers = isset($params['decode_headers'])  ? $params['decode_headers']  : false;
  169.             
  170.             $structure = $this->_decode($this->_header, $this->_body);
  171.             if($structure === false)
  172.                 $structure = $this->raiseError($this->_error);
  173.         }
  174.  
  175.         return $structure;
  176.     }
  177.  
  178.     /**
  179.      * Performs the decoding. Decodes the body string passed to it
  180.      * If it finds certain content-types it will call itself in a
  181.      * recursive fashion
  182.      * 
  183.      * @param string Header section
  184.      * @param string Body section
  185.      * @return object Results of decoding process
  186.      * @access private
  187.      */
  188.     function _decode($headers, $body, $default_ctype = 'text/plain')
  189.     {
  190.         $return = new stdClass;
  191.         $headers = $this->_parseHeaders($headers);
  192.  
  193.         foreach ($headers as $value) {
  194.             if (isset($return->headers[strtolower($value['name'])]) AND !is_array($return->headers[strtolower($value['name'])])) {
  195.                 $return->headers[strtolower($value['name'])]   = array($return->headers[strtolower($value['name'])]);
  196.                 $return->headers[strtolower($value['name'])][] = $value['value'];
  197.  
  198.             } elseif (isset($return->headers[strtolower($value['name'])])) {
  199.                 $return->headers[strtolower($value['name'])][] = $value['value'];
  200.  
  201.             } else {
  202.                 $return->headers[strtolower($value['name'])] = $value['value'];
  203.             }
  204.         }
  205.  
  206.         reset($headers);
  207.         while (list($key, $value) = each($headers)) {
  208.             $headers[$key]['name'] = strtolower($headers[$key]['name']);
  209.             switch ($headers[$key]['name']) {
  210.  
  211.                 case 'content-type':
  212.                     $content_type = $this->_parseHeaderValue($headers[$key]['value']);
  213.     
  214.                     if (preg_match('/([0-9a-z+.-]+)\/([0-9a-z+.-]+)/i', $content_type['value'], $regs)) {
  215.                         $return->ctype_primary   = $regs[1];
  216.                         $return->ctype_secondary = $regs[2];
  217.                     }
  218.     
  219.                     if (isset($content_type['other'])) {
  220.                         while (list($p_name, $p_value) = each($content_type['other'])) {
  221.                             $return->ctype_parameters[$p_name] = $p_value;
  222.                         }
  223.                     }
  224.                     break;
  225.  
  226.                 case 'content-disposition';
  227.                     $content_disposition = $this->_parseHeaderValue($headers[$key]['value']);
  228.                     $return->disposition   = $content_disposition['value'];
  229.                     if (isset($content_disposition['other'])) {
  230.                         while (list($p_name, $p_value) = each($content_disposition['other'])) {
  231.                             $return->d_parameters[$p_name] = $p_value;
  232.                         }
  233.                     }
  234.                     break;
  235.  
  236.                 case 'content-transfer-encoding':
  237.                     $content_transfer_encoding = $this->_parseHeaderValue($headers[$key]['value']);
  238.                     break;
  239.             }
  240.         }
  241.  
  242.         if (isset($content_type)) {
  243.  
  244.             switch (strtolower($content_type['value'])) {
  245.                 case 'text/plain':
  246.                     $encoding = isset($content_transfer_encoding) ? $content_transfer_encoding['value'] : '7bit';
  247.                     $this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $encoding) : $body) : null;
  248.                     break;
  249.     
  250.                 case 'text/html':
  251.                     $encoding = isset($content_transfer_encoding) ? $content_transfer_encoding['value'] : '7bit';
  252.                     $this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $encoding) : $body) : null;
  253.                     break;
  254.  
  255.                 case 'multipart/digest':
  256.                 case 'multipart/alternative':
  257.                 case 'multipart/related':
  258.                 case 'multipart/mixed':
  259.                     if(!isset($content_type['other']['boundary'])){
  260.                         $this->_error = 'No boundary found for ' . $content_type['value'] . ' part';
  261.                         return false;
  262.                     }
  263.  
  264.                     $default_ctype = (strtolower($content_type['value']) === 'multipart/digest') ? 'message/rfc822' : 'text/plain';
  265.  
  266.                     $parts = $this->_boundarySplit($body, $content_type['other']['boundary']);
  267.                     for ($i = 0; $i < count($parts); $i++) {
  268.                         list($part_header, $part_body) = $this->_splitBodyHeader($parts[$i]);
  269.                         $part = $this->_decode($part_header, $part_body, $default_ctype);
  270.                         if($part === false)
  271.                             $part = $this->raiseError($this->_error);
  272.                         $return->parts[] = $part;
  273.                     }
  274.                     break;
  275.  
  276.                 case 'message/rfc822':
  277.                     $obj = new Mail_mimeDecode($body, $this->_crlf);
  278.                     $return->parts[] = $obj->decode(array('include_bodies' => $this->_include_bodies));
  279.                     unset($obj);
  280.                     break;
  281.  
  282.                 default:
  283.                     $this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $content_transfer_encoding['value']) : $body) : null;
  284.                     break;
  285.             }
  286.  
  287.         } else {
  288.             $ctype = explode('/', $default_ctype);
  289.             $return->ctype_primary   = $ctype[0];
  290.             $return->ctype_secondary = $ctype[1];
  291.             $this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body) : $body) : null;
  292.         }
  293.         
  294.         return $return;
  295.     }
  296.  
  297.     /**
  298.      * Given a string containing a header and body
  299.      * section, this function will split them (at the first
  300.      * blank line) and return them.
  301.      * 
  302.      * @param string Input to split apart
  303.      * @return array Contains header and body section
  304.      * @access private
  305.      */
  306.     function _splitBodyHeader($input)
  307.     {
  308.  
  309.         $pos = strpos($input, $this->_crlf . $this->_crlf);
  310.         if ($pos === false) {
  311.             $this->_error = 'Could not split header and body';
  312.             return false;
  313.         }
  314.  
  315.         $header = substr($input, 0, $pos);
  316.         $body   = substr($input, $pos+(2*strlen($this->_crlf)));
  317.  
  318.         return array($header, $body);
  319.     }
  320.  
  321.     /**
  322.      * Parse headers given in $input and return
  323.      * as assoc array.
  324.      * 
  325.      * @param string Headers to parse
  326.      * @return array Contains parsed headers
  327.      * @access private
  328.      */
  329.     function _parseHeaders($input)
  330.     {
  331.  
  332.         if ($input !== '') {
  333.             // Unfold the input
  334.             $input   = preg_replace('/' . $this->_crlf . "(\t| )/", ' ', $input);
  335.             $headers = explode($this->_crlf, trim($input));
  336.     
  337.             foreach ($headers as $value) {
  338.                 $hdr_name = substr($value, 0, $pos = strpos($value, ':'));
  339.                 $hdr_value = substr($value, $pos+1);
  340.                 if($hdr_value[0] == ' ')
  341.                     $hdr_value = substr($hdr_value, 1);
  342.  
  343.                 $return[] = array(
  344.                                   'name'  => $hdr_name,
  345.                                   'value' => $this->_decode_headers ? $this->_decodeHeader($hdr_value) : $hdr_value
  346.                                  );
  347.             }
  348.         } else {
  349.             $return = array();
  350.         }
  351.  
  352.         return $return;
  353.     }
  354.  
  355.     /**
  356.      * Function to parse a header value,
  357.      * extract first part, and any secondary
  358.      * parts (after ;) This function is not as
  359.      * robust as it could be. Eg. header comments
  360.      * in the wrong place will probably break it.
  361.      * 
  362.      * @param string Header value to parse
  363.      * @return array Contains parsed result
  364.      * @access private
  365.      */
  366.     function _parseHeaderValue($input)
  367.     {
  368.  
  369.         if (($pos = strpos($input, ';')) !== false) {
  370.  
  371.             $return['value'] = trim(substr($input, 0, $pos));
  372.             $input = trim(substr($input, $pos+1));
  373.  
  374.             if (strlen($input) > 0) {
  375.                 preg_match_all('/(([[:alnum:]]+)="?([^"]*)"?\s?;?)+/i', $input, $matches);
  376.  
  377.                 for ($i = 0; $i < count($matches[2]); $i++) {
  378.                     $return['other'][strtolower($matches[2][$i])] = $matches[3][$i];
  379.                 }
  380.             }
  381.         } else {
  382.             $return['value'] = trim($input);
  383.         }
  384.         
  385.         return $return;
  386.     }
  387.  
  388.     /**
  389.      * This function splits the input based
  390.      * on the given boundary
  391.      * 
  392.      * @param string Input to parse
  393.      * @return array Contains array of resulting mime parts
  394.      * @access private
  395.      */
  396.     function _boundarySplit($input, $boundary)
  397.     {
  398.         $tmp = explode('--'.$boundary, $input);
  399.  
  400.         for ($i=1; $i<count($tmp)-1; $i++) {
  401.             $parts[] = $tmp[$i];
  402.         }
  403.  
  404.         return $parts;
  405.     }
  406.  
  407.     /**
  408.      * Given a header, this function will decode it
  409.      * according to RFC2047. Probably not *exactly*
  410.      * conformant, but it does pass all the given
  411.      * examples (in RFC2047).
  412.      *
  413.      * @param string Input header value to decode
  414.      * @return string Decoded header value
  415.      * @access private
  416.      */
  417.     function _decodeHeader($input)
  418.     {
  419.         // Remove white space between encoded-words
  420.         $input = preg_replace('/(=\?[^?]+\?(Q|B)\?[^?]*\?=)( |' . "\t|" . $this->_crlf . ')+=\?/', '\1=?', $input);
  421.  
  422.         // For each encoded-word...
  423.         while (preg_match('/(=\?([^?]+)\?(Q|B)\?([^?]*)\?=)/', $input, $matches)) {
  424.  
  425.             $encoded  = $matches[1];
  426.             $charset  = $matches[2];
  427.             $encoding = $matches[3];
  428.             $text     = $matches[4];
  429.  
  430.             switch ($encoding) {
  431.                 case 'B':
  432.                     $text = base64_decode($text);
  433.                     break;
  434.  
  435.                 case 'Q':
  436.                     $text = str_replace('_', ' ', $text);
  437.                     preg_match_all('/=([A-F0-9]{2})/', $text, $matches);
  438.                     foreach($matches[1] as $value)
  439.                         $text = str_replace('='.$value, chr(hexdec($value)), $text);
  440.                     break;
  441.             }
  442.  
  443.             $input = str_replace($encoded, $text, $input);
  444.         }
  445.         
  446.         return $input;
  447.     }
  448.  
  449.     /**
  450.      * Given a body string and an encoding type, 
  451.      * this function will decode and return it.
  452.      *
  453.      * @param  string Input body to decode
  454.      * @param  string Encoding type to use.
  455.      * @return string Decoded body
  456.      * @access private
  457.      */
  458.     function _decodeBody($input, $encoding = '7bit')
  459.     {
  460.         switch ($encoding) {
  461.             case '7bit':
  462.                 return $input;
  463.                 break;
  464.  
  465.             case 'quoted-printable':
  466.                 return $this->_quotedPrintableDecode($input);
  467.                 break;
  468.  
  469.             case 'base64':
  470.                 return base64_decode($input);
  471.                 break;
  472.  
  473.             default:
  474.                 return $input;
  475.         }
  476.     }
  477.  
  478.     /**
  479.      * Given a quoted-printable string, this
  480.      * function will decode and return it.
  481.      *
  482.      * @param  string Input body to decode
  483.      * @return string Decoded body
  484.      * @access private
  485.      */
  486.     function _quotedPrintableDecode($input)
  487.     {
  488.         // Remove soft line breaks
  489.         $input = preg_replace("/=\r?\n/", '', $input);
  490.  
  491.         // Replace encoded characters
  492.         if (preg_match_all('/=[A-Z0-9]{2}/', $input, $matches)) {
  493.             $matches = array_unique($matches[0]);
  494.             foreach ($matches as $value) {
  495.                 $input = str_replace($value, chr(hexdec(substr($value,1))), $input);
  496.             }
  497.         }
  498.  
  499.         return $input;
  500.     }
  501.  
  502.     /**
  503.      * Checks the input for uuencoded files and returns
  504.      * an array of them. Can be called statically, eg:
  505.      *
  506.      * $files =& Mail_mimeDecode::uudecode($some_text);
  507.      *
  508.      * It will check for the begin 666 ... end syntax
  509.      * however and won't just blindly decode whatever you
  510.      * pass it.
  511.      *
  512.      * @param  string Input body to look for attahcments in
  513.      * @return array  Decoded bodies, filenames and permissions
  514.      * @access public
  515.      * @author Unknown
  516.      */
  517.     function &uudecode($input)
  518.     {
  519.         // Find all uuencoded sections
  520.         preg_match_all("/begin ([0-7]{3}) (.+)\r?\n(.+)\r?\nend/Us", $input, $matches);
  521.  
  522.         for ($j = 0; $j < count($matches[3]); $j++) {
  523.  
  524.             $str      = $matches[3][$j];
  525.             $filename = $matches[2][$j];
  526.             $fileperm = $matches[1][$j];
  527.  
  528.             $file = '';
  529.             $str = preg_split("/\r?\n/", trim($str));
  530.             $strlen = count($str);
  531.     
  532.             for ($i = 0; $i < $strlen; $i++) {
  533.                 $pos = 1;
  534.                 $d = 0;
  535.                 $len=(int)(((ord(substr($str[$i],0,1)) -32) - ' ') & 077);
  536.     
  537.                 while (($d + 3 <= $len) AND ($pos + 4 <= strlen($str[$i]))) {
  538.                     $c0 = (ord(substr($str[$i],$pos,1)) ^ 0x20);
  539.                     $c1 = (ord(substr($str[$i],$pos+1,1)) ^ 0x20);
  540.                     $c2 = (ord(substr($str[$i],$pos+2,1)) ^ 0x20);
  541.                     $c3 = (ord(substr($str[$i],$pos+3,1)) ^ 0x20);
  542.                     $file .= chr(((($c0 - ' ') & 077) << 2) | ((($c1 - ' ') & 077) >> 4));
  543.         
  544.                     $file .= chr(((($c1 - ' ') & 077) << 4) | ((($c2 - ' ') & 077) >> 2));
  545.         
  546.                     $file .= chr(((($c2 - ' ') & 077) << 6) |  (($c3 - ' ') & 077));
  547.         
  548.                     $pos += 4;
  549.                     $d += 3;
  550.                 }
  551.         
  552.                 if (($d + 2 <= $len) && ($pos + 3 <= strlen($str[$i]))) {
  553.                     $c0 = (ord(substr($str[$i],$pos,1)) ^ 0x20);
  554.                     $c1 = (ord(substr($str[$i],$pos+1,1)) ^ 0x20);
  555.                     $c2 = (ord(substr($str[$i],$pos+2,1)) ^ 0x20);
  556.                     $file .= chr(((($c0 - ' ') & 077) << 2) | ((($c1 - ' ') & 077) >> 4));
  557.         
  558.                     $file .= chr(((($c1 - ' ') & 077) << 4) | ((($c2 - ' ') & 077) >> 2));
  559.         
  560.                     $pos += 3;
  561.                     $d += 2;
  562.                 }
  563.         
  564.                 if (($d + 1 <= $len) && ($pos + 2 <= strlen($str[$i]))) {
  565.                     $c0 = (ord(substr($str[$i],$pos,1)) ^ 0x20);
  566.                     $c1 = (ord(substr($str[$i],$pos+1,1)) ^ 0x20);
  567.                     $file .= chr(((($c0 - ' ') & 077) << 2) | ((($c1 - ' ') & 077) >> 4));
  568.         
  569.                 }
  570.             }
  571.             $files[] = array('filename' => $filename, 'fileperm' => $fileperm, 'filedata' => $file);
  572.         }
  573.  
  574.         return $files;
  575.     }
  576.  
  577.     /**
  578.      * Returns a xml copy of the output of
  579.      * Mail_mimeDecode::decode. Pass the output in as the
  580.      * argument. This function can be called statically. Eg:
  581.      *
  582.      * $output = $obj->decode();
  583.      * $xml    = Mail_mimeDecode::getXML($output);
  584.      *
  585.      * The DTD used for this should have been in the package. Or
  586.      * alternatively you can get it from cvs, or here:
  587.      * http://www.phpguru.org/xmail/xmail.dtd.
  588.      *
  589.      * @param  object Input to convert to xml. This should be the
  590.      *                output of the Mail_mimeDecode::decode function
  591.      * @return string XML version of input
  592.      * @access public
  593.      */
  594.     function getXML($input)
  595.     {
  596.         $crlf    =  "\r\n";
  597.         $output  = '<?xml version=\'1.0\'?>' . $crlf .
  598.                    '<!DOCTYPE email SYSTEM "http://www.phpguru.org/xmail/xmail.dtd">' . $crlf .
  599.                    '<email>' . $crlf .
  600.                    Mail_mimeDecode::_getXML($input) .
  601.                    '</email>';
  602.  
  603.         return $output;
  604.     }
  605.  
  606.     /**
  607.      * Function that does the actual conversion to xml. Does a single
  608.      * mimepart at a time.
  609.      *
  610.      * @param  object  Input to convert to xml. This is a mimepart object.
  611.      *                 It may or may not contain subparts.
  612.      * @param  integer Number of tabs to indent
  613.      * @return string  XML version of input
  614.      * @access private
  615.      */
  616.     function _getXML($input, $indent = 1)
  617.     {
  618.         $htab    =  "\t";
  619.         $crlf    =  "\r\n";
  620.         $output  =  '';
  621.         $headers =& $input->headers;
  622.  
  623.         foreach ($headers as $hdr_name => $hdr_value) {
  624.  
  625.             // Multiple headers with this name
  626.             if (is_array($headers[$hdr_name])) {
  627.                 for ($i = 0; $i < count($hdr_value); $i++) {
  628.                     $output .= Mail_mimeDecode::_getXML_helper($hdr_name, $hdr_value[$i], $indent);
  629.                 }
  630.  
  631.             // Only one header of this sort
  632.             } else {
  633.                 $output .= Mail_mimeDecode::_getXML_helper($hdr_name, $hdr_value, $indent);
  634.             }
  635.         }
  636.  
  637.         if (!empty($input->parts)) {
  638.             for ($i = 0; $i < count($input->parts); $i++) {
  639.                 $output .= $crlf . str_repeat($htab, $indent) . '<mimepart>' . $crlf .
  640.                            Mail_mimeDecode::_getXML($input->parts[$i], $indent+1) .
  641.                            str_repeat($htab, $indent) . '</mimepart>' . $crlf;
  642.             }
  643.         } else {
  644.             $output .= $crlf . str_repeat($htab, $indent) . '<body><![CDATA[' .
  645.                        $input->body . ']]></body>' . $crlf;
  646.         }
  647.  
  648.         return $output;
  649.     }
  650.  
  651.     /**
  652.      * Helper function to _getXML(). Returns xml of a header.
  653.      *
  654.      * @param  string  Name of header
  655.      * @param  string  Value of header
  656.      * @param  integer Number of tabs to indent
  657.      * @return string  XML version of input
  658.      * @access private
  659.      */
  660.     function _getXML_helper($hdr_name, $hdr_value, $indent)
  661.     {
  662.         $htab   = "\t";
  663.         $crlf   = "\r\n";
  664.         $return = '';
  665.  
  666.         $new_hdr_value = ($hdr_name != 'received') ? Mail_mimeDecode::_parseHeaderValue($hdr_value) : array('value' => $hdr_value);
  667.         $new_hdr_name  = str_replace(' ', '-', ucwords(str_replace('-', ' ', $hdr_name)));
  668.  
  669.         // Sort out any parameters
  670.         if (!empty($new_hdr_value['other'])) {
  671.             foreach ($new_hdr_value['other'] as $paramname => $paramvalue) {
  672.                 $params[] = str_repeat($htab, $indent) . $htab . '<parameter>' . $crlf .
  673.                             str_repeat($htab, $indent) . $htab . $htab . '<paramname>' . htmlspecialchars($paramname) . '</paramname>' . $crlf .
  674.                             str_repeat($htab, $indent) . $htab . $htab . '<paramvalue>' . htmlspecialchars($paramvalue) . '</paramvalue>' . $crlf .
  675.                             str_repeat($htab, $indent) . $htab . '</parameter>' . $crlf;
  676.             }
  677.             
  678.             $params = implode('', $params);
  679.         } else {
  680.             $params = '';
  681.         }
  682.  
  683.         $return = str_repeat($htab, $indent) . '<header>' . $crlf .
  684.                   str_repeat($htab, $indent) . $htab . '<headername>' . htmlspecialchars($new_hdr_name) . '</headername>' . $crlf .
  685.                   str_repeat($htab, $indent) . $htab . '<headervalue>' . htmlspecialchars($new_hdr_value['value']) . '</headervalue>' . $crlf .
  686.                   $params .
  687.                   str_repeat($htab, $indent) . '</header>' . $crlf;
  688.  
  689.         return $return;
  690.     }    
  691.  
  692. } // End of class
  693. ?>