class_mimeDecode.php
上传用户:gzy2002
上传日期:2010-02-11
资源大小:1785k
文件大小:21k
源码类别:

电子政务应用

开发平台:

Java

  1. <?php
  2. /**
  3. *  +----------------------------- IMPORTANT ------------------------------+
  4. *  | Usage of this class compared to native php extensions such as        |
  5. *  | mailparse or imap, is slow and may be feature deficient. If available|
  6. *  | you are STRONGLY recommended to use the php extensions.              |
  7. *  +----------------------------------------------------------------------+
  8. *
  9. * Mime Decoding class
  10. *
  11. * This class will parse a raw mime email and return
  12. * the structure. Returned structure is similar to
  13. * that returned by imap_fetchstructure().
  14. *
  15. * USAGE: (assume $input is your raw email)
  16. *
  17. * $decode = new Mail_mimeDecode($input);
  18. * $structure = $decode->decode();
  19. * print_r($structure);
  20. *
  21. * Or statically:
  22. *
  23. * $params['input'] = $input;
  24. * $structure = Mail_mimeDecode::decode($params);
  25. * print_r($structure);
  26. *
  27. * TODO:
  28. *  - Implement further content types, eg. multipart/parallel,
  29. *    perhaps even message/partial.
  30. *
  31. * @author  Richard Heyes
  32. * @version $Revision: 1.1 $
  33. * @package Mail
  34. */
  35. class Mail_mimeDecode
  36. {
  37.     /**
  38.      * The raw email to decode
  39.      * @var    string
  40.      */
  41.     var $_input;
  42.     /**
  43.      * The header part of the input
  44.      * @var    string
  45.      */
  46.     var $_header;
  47.     /**
  48.      * The body part of the input
  49.      * @var    string
  50.      */
  51.     var $_body;
  52.     /**
  53.      * If an error occurs, this is used to store the message
  54.      * @var    string
  55.      */
  56.     var $_error;
  57.     /**
  58.      * Flag to determine whether to include bodies in the
  59.      * returned object.
  60.      * @var    boolean
  61.      */
  62.     var $_include_bodies;
  63.     /**
  64.      * Flag to determine whether to decode bodies
  65.      * @var    boolean
  66.      */
  67.     var $_decode_bodies;
  68.     /**
  69.      * Flag to determine whether to decode headers
  70.      * @var    boolean
  71.      */
  72.     var $_decode_headers;
  73.     /**
  74.     * If invoked from a class, $this will be set. This has problematic
  75.     * connotations for calling decode() statically. Hence this variable
  76.     * is used to determine if we are indeed being called statically or
  77.     * via an object.
  78.     */
  79.     var $mailMimeDecode;
  80.     /**
  81.      * Constructor.
  82.      *
  83.      * Sets up the object, initialise the variables, and splits and
  84.      * stores the header and body of the input.
  85.      *
  86.      * @param string The input to decode
  87.      * @access public
  88.      */
  89.     function Mail_mimeDecode($input)
  90.     {
  91.         list($header, $body)   = $this->_splitBodyHeader($input);
  92.         $this->_input          = $input;
  93.         $this->_header         = $header;
  94.         $this->_body           = $body;
  95.         $this->_decode_bodies  = false;
  96.         $this->_include_bodies = true;
  97.         
  98.         $this->mailMimeDecode  = true;
  99.     }
  100.     /**
  101.      * Begins the decoding process. If called statically
  102.      * it will create an object and call the decode() method
  103.      * of it.
  104.      *
  105.      * @param array An array of various parameters that determine
  106.      *              various things:
  107.      *              include_bodies - Whether to include the body in the returned
  108.      *                               object.
  109.      *              decode_bodies  - Whether to decode the bodies
  110.      *                               of the parts. (Transfer encoding)
  111.      *              decode_headers - Whether to decode headers
  112.      *              input          - If called statically, this will be treated
  113.      *                               as the input
  114.      * @return object Decoded results
  115.      * @access public
  116.      */
  117.     function decode($params = null)
  118.     {
  119.         // Have we been called statically? If so, create an object and pass details to that.
  120.         if (!isset($this->mailMimeDecode) AND isset($params['input'])) {
  121.             $obj = new Mail_mimeDecode($params['input']);
  122.             $structure = $obj->decode($params);
  123.         // Called statically but no input
  124.         } elseif (!isset($this->mailMimeDecode)) {
  125. $this->_error = 'Called statically and no input given';
  126.             return false;
  127.         // Called via an object
  128.         } else {
  129.             $this->_include_bodies = isset($params['include_bodies'])  ? $params['include_bodies']  : false;
  130.             $this->_decode_bodies  = isset($params['decode_bodies'])   ? $params['decode_bodies']   : false;
  131.             $this->_decode_headers = isset($params['decode_headers'])  ? $params['decode_headers']  : false;
  132.             $structure = $this->_decode($this->_header, $this->_body);
  133.         }
  134.         return $structure;
  135.     }
  136.     /**
  137.      * Performs the decoding. Decodes the body string passed to it
  138.      * If it finds certain content-types it will call itself in a
  139.      * recursive fashion
  140.      *
  141.      * @param string Header section
  142.      * @param string Body section
  143.      * @return object Results of decoding process
  144.      * @access private
  145.      */
  146.     function _decode($headers, $body, $default_ctype = 'text/plain')
  147.     {
  148.         $return = new stdClass;
  149.         $headers = $this->_parseHeaders($headers);
  150.         foreach ($headers as $value) {
  151.             if (isset($return->headers[strtolower($value['name'])]) AND !is_array($return->headers[strtolower($value['name'])])) {
  152.                 $return->headers[strtolower($value['name'])]   = array($return->headers[strtolower($value['name'])]);
  153.                 $return->headers[strtolower($value['name'])][] = $value['value'];
  154.             } elseif (isset($return->headers[strtolower($value['name'])])) {
  155.                 $return->headers[strtolower($value['name'])][] = $value['value'];
  156.             } else {
  157.                 $return->headers[strtolower($value['name'])] = $value['value'];
  158.             }
  159.         }
  160.         reset($headers);
  161.         while (list($key, $value) = each($headers)) {
  162.             $headers[$key]['name'] = strtolower($headers[$key]['name']);
  163.             switch ($headers[$key]['name']) {
  164.                 case 'content-type':
  165.                     $content_type = $this->_parseHeaderValue($headers[$key]['value']);
  166.                     if (preg_match('/([0-9a-z+.-]+)/([0-9a-z+.-]+)/i', $content_type['value'], $regs)) {
  167.                         $return->ctype_primary   = $regs[1];
  168.                         $return->ctype_secondary = $regs[2];
  169.                     }
  170.                     if (isset($content_type['other'])) {
  171.                         while (list($p_name, $p_value) = each($content_type['other'])) {
  172.                             $return->ctype_parameters[$p_name] = $p_value;
  173.                         }
  174.                     }
  175.                     break;
  176.                 case 'content-disposition';
  177.                     $content_disposition = $this->_parseHeaderValue($headers[$key]['value']);
  178.                     $return->disposition   = $content_disposition['value'];
  179.                     if (isset($content_disposition['other'])) {
  180.                         while (list($p_name, $p_value) = each($content_disposition['other'])) {
  181.                             $return->d_parameters[$p_name] = $p_value;
  182.                         }
  183.                     }
  184.                     break;
  185.                 case 'content-transfer-encoding':
  186.                     $content_transfer_encoding = $this->_parseHeaderValue($headers[$key]['value']);
  187.                     break;
  188.             }
  189.         }
  190.         if (isset($content_type)) {
  191.             switch (strtolower($content_type['value'])) {
  192.                 case 'text/plain':
  193.                     $encoding = isset($content_transfer_encoding) ? $content_transfer_encoding['value'] : '7bit';
  194.                     $this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $encoding) : $body) : null;
  195.                     break;
  196.                 case 'text/html':
  197.                     $encoding = isset($content_transfer_encoding) ? $content_transfer_encoding['value'] : '7bit';
  198.                     $this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $encoding) : $body) : null;
  199.                     break;
  200.                 case 'multipart/parallel':
  201.                 case 'multipart/report': // RFC1892
  202.                 case 'multipart/signed': // PGP
  203.                 case 'multipart/digest':
  204.                 case 'multipart/alternative':
  205.                 case 'multipart/related':
  206.                 case 'multipart/mixed':
  207.                     if(!isset($content_type['other']['boundary'])){
  208.                         $this->_error = 'No boundary found for ' . $content_type['value'] . ' part';
  209.                         return false;
  210.                     }
  211.                     $default_ctype = (strtolower($content_type['value']) === 'multipart/digest') ? 'message/rfc822' : 'text/plain';
  212.                     $parts = $this->_boundarySplit($body, $content_type['other']['boundary']);
  213.                     for ($i = 0; $i < count($parts); $i++) {
  214.                         list($part_header, $part_body) = $this->_splitBodyHeader($parts[$i]);
  215.                         $part = $this->_decode($part_header, $part_body, $default_ctype);
  216.                         $return->parts[] = $part;
  217.                     }
  218.                     break;
  219.                 case 'message/rfc822':
  220.                     $obj = &new Mail_mimeDecode($body);
  221.                     $return->parts[] = $obj->decode(array('include_bodies' => $this->_include_bodies));
  222.                     unset($obj);
  223.                     break;
  224.                 default:
  225.                     if(!isset($content_transfer_encoding['value']))
  226.                         $content_transfer_encoding['value'] = '7bit';
  227.                     $this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $content_transfer_encoding['value']) : $body) : null;
  228.                     break;
  229.             }
  230.         } else {
  231.             $ctype = explode('/', $default_ctype);
  232.             $return->ctype_primary   = $ctype[0];
  233.             $return->ctype_secondary = $ctype[1];
  234.             $this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body) : $body) : null;
  235.         }
  236.         return $return;
  237.     }
  238.     /**
  239.      * Given the output of the above function, this will return an
  240.      * array of references to the parts, indexed by mime number.
  241.      *
  242.      * @param  object $structure   The structure to go through
  243.      * @param  string $mime_number Internal use only.
  244.      * @return array               Mime numbers
  245.      */
  246.     function &getMimeNumbers(&$structure, $no_refs = false, $mime_number = '', $prepend = '')
  247.     {
  248.         $return = array();
  249.         if (!empty($structure->parts)) {
  250.             if ($mime_number != '') {
  251.                 $structure->mime_id = $prepend . $mime_number;
  252.                 $return[$prepend . $mime_number] = &$structure;
  253.             }
  254.             for ($i = 0; $i < count($structure->parts); $i++) {
  255.             
  256.                 if (!empty($structure->headers['content-type']) AND substr(strtolower($structure->headers['content-type']), 0, 8) == 'message/') {
  257.                     $prepend      = $prepend . $mime_number . '.';
  258.                     $_mime_number = '';
  259.                 } else {
  260.                     $_mime_number = ($mime_number == '' ? $i + 1 : sprintf('%s.%s', $mime_number, $i + 1));
  261.                 }
  262.                 $arr = &Mail_mimeDecode::getMimeNumbers($structure->parts[$i], $no_refs, $_mime_number, $prepend);
  263.                 foreach ($arr as $key => $val) {
  264.                     $no_refs ? $return[$key] = '' : $return[$key] = &$arr[$key];
  265.                 }
  266.             }
  267.         } else {
  268.             if ($mime_number == '') {
  269.                 $mime_number = '1';
  270.             }
  271.             $structure->mime_id = $prepend . $mime_number;
  272.             $no_refs ? $return[$prepend . $mime_number] = '' : $return[$prepend . $mime_number] = &$structure;
  273.         }
  274.         
  275.         return $return;
  276.     }
  277.     /**
  278.      * Given a string containing a header and body
  279.      * section, this function will split them (at the first
  280.      * blank line) and return them.
  281.      *
  282.      * @param string Input to split apart
  283.      * @return array Contains header and body section
  284.      * @access private
  285.      */
  286.     function _splitBodyHeader($input)
  287.     {
  288.         if (preg_match("/^(.*?)r?nr?n(.*)/s", $input, $match)) {
  289.             return array($match[1], $match[2]);
  290.         }
  291.         $this->_error = 'Could not split header and body';
  292.         return false;
  293.     }
  294.     /**
  295.      * Parse headers given in $input and return
  296.      * as assoc array.
  297.      *
  298.      * @param string Headers to parse
  299.      * @return array Contains parsed headers
  300.      * @access private
  301.      */
  302.     function _parseHeaders($input)
  303.     {
  304.         if ($input !== '') {
  305.             // Unfold the input
  306.             $input   = preg_replace("/rn/", "n", $input);
  307.             $input   = preg_replace("/n(t| )+/", ' ', $input);
  308.             $headers = explode("n", trim($input));
  309.             foreach ($headers as $value) {
  310.                 $hdr_name = substr($value, 0, $pos = strpos($value, ':'));
  311.                 $hdr_value = substr($value, $pos+1);
  312.                 if($hdr_value[0] == ' ')
  313.                     $hdr_value = substr($hdr_value, 1);
  314.                 $return[] = array(
  315.                                   'name'  => $hdr_name,
  316.                                   'value' => $this->_decode_headers ? $this->_decodeHeader($hdr_value) : $hdr_value
  317.                                  );
  318.             }
  319.         } else {
  320.             $return = array();
  321.         }
  322.         return $return;
  323.     }
  324.     /**
  325.      * Function to parse a header value,
  326.      * extract first part, and any secondary
  327.      * parts (after ;) This function is not as
  328.      * robust as it could be. Eg. header comments
  329.      * in the wrong place will probably break it.
  330.      *
  331.      * @param string Header value to parse
  332.      * @return array Contains parsed result
  333.      * @access private
  334.      */
  335.     function _parseHeaderValue($input)
  336.     {
  337.         if (($pos = strpos($input, ';')) !== false) {
  338.             $return['value'] = trim(substr($input, 0, $pos));
  339.             $input = trim(substr($input, $pos+1));
  340.             if (strlen($input) > 0) {
  341.                 // This splits on a semi-colon, if there's no preceeding backslash
  342.                 // Can't handle if it's in double quotes however. (Of course anyone
  343.                 // sending that needs a good slap).
  344.                 $parameters = preg_split('/s*(?<!\\);s*/i', $input);
  345.                 for ($i = 0; $i < count($parameters); $i++) {
  346.                     $param_name  = substr($parameters[$i], 0, $pos = strpos($parameters[$i], '='));
  347.                     $param_value = substr($parameters[$i], $pos + 1);
  348.                     if ($param_value[0] == '"') {
  349.                         $param_value = substr($param_value, 1, -1);
  350.                     }
  351.                     $return['other'][$param_name] = $param_value;
  352.                     $return['other'][strtolower($param_name)] = $param_value;
  353.                 }
  354.             }
  355.         } else {
  356.             $return['value'] = trim($input);
  357.         }
  358.         return $return;
  359.     }
  360.     /**
  361.      * This function splits the input based
  362.      * on the given boundary
  363.      *
  364.      * @param string Input to parse
  365.      * @return array Contains array of resulting mime parts
  366.      * @access private
  367.      */
  368.     function _boundarySplit($input, $boundary)
  369.     {
  370.         $tmp = explode('--'.$boundary, $input);
  371.         for ($i=1; $i<count($tmp)-1; $i++) {
  372.             $parts[] = $tmp[$i];
  373.         }
  374.         return $parts;
  375.     }
  376.     /**
  377.      * Given a header, this function will decode it
  378.      * according to RFC2047. Probably not *exactly*
  379.      * conformant, but it does pass all the given
  380.      * examples (in RFC2047).
  381.      *
  382.      * @param string Input header value to decode
  383.      * @return string Decoded header value
  384.      * @access private
  385.      */
  386.     function _decodeHeader($input)
  387.     {
  388.         // Remove white space between encoded-words
  389.         $input = preg_replace('/(=?[^?]+?(Q|B)?[^?]*?=)( |' . "t|r?n" . ')+=?/', '1=?', $input);
  390.         // For each encoded-word...
  391.         while (preg_match('/(=?([^?]+)?(Q|B)?([^?]*)?=)/', $input, $matches)) {
  392.             $encoded  = $matches[1];
  393.             $charset  = $matches[2];
  394.             $encoding = $matches[3];
  395.             $text     = $matches[4];
  396.             switch ($encoding) {
  397.                 case 'B':
  398.                     $text = base64_decode($text);
  399.                     break;
  400.                 case 'Q':
  401.                     $text = str_replace('_', ' ', $text);
  402.                     preg_match_all('/=([a-f0-9]{2})/i', $text, $matches);
  403.                     foreach($matches[1] as $value)
  404.                         $text = str_replace('='.$value, chr(hexdec($value)), $text);
  405.                     break;
  406.             }
  407.             $input = str_replace($encoded, $text, $input);
  408.         }
  409.         return $input;
  410.     }
  411.     /**
  412.      * Given a body string and an encoding type,
  413.      * this function will decode and return it.
  414.      *
  415.      * @param  string Input body to decode
  416.      * @param  string Encoding type to use.
  417.      * @return string Decoded body
  418.      * @access private
  419.      */
  420.     function _decodeBody($input, $encoding = '7bit')
  421.     {
  422.         switch ($encoding) {
  423.             case '7bit':
  424. case '8bit':
  425.                 return $input;
  426.                 break;
  427.             case 'quoted-printable':
  428.                 return $this->_quotedPrintableDecode($input);
  429.                 break;
  430.             case 'base64':
  431.                 return base64_decode($input);
  432.                 break;
  433.             default:
  434.                 return $input;
  435.         }
  436.     }
  437.     /**
  438.      * Given a quoted-printable string, this
  439.      * function will decode and return it.
  440.      *
  441.      * @param  string Input body to decode
  442.      * @return string Decoded body
  443.      * @access private
  444.      */
  445.     function _quotedPrintableDecode($input)
  446.     {
  447.         // Remove soft line breaks
  448.         $input = preg_replace("/=r?n/", '', $input);
  449.         // Replace encoded characters
  450.         if (preg_match_all('/=[a-f0-9]{2}/i', $input, $matches)) {
  451.             $matches = array_unique($matches[0]);
  452.             foreach ($matches as $value) {
  453.                 $input = str_replace($value, chr(hexdec(substr($value,1))), $input);
  454.             }
  455.         }
  456.         return $input;
  457.     }
  458.     /**
  459.      * Checks the input for uuencoded files and returns
  460.      * an array of them. Can be called statically, eg:
  461.      *
  462.      * $files =& Mail_mimeDecode::uudecode($some_text);
  463.      *
  464.      * It will check for the begin 666 ... end syntax
  465.      * however and won't just blindly decode whatever you
  466.      * pass it.
  467.      *
  468.      * @param  string Input body to look for attahcments in
  469.      * @return array  Decoded bodies, filenames and permissions
  470.      * @access public
  471.      * @author Unknown
  472.      */
  473.     function &uudecode($input)
  474.     {
  475.         // Find all uuencoded sections
  476.         preg_match_all("/begin ([0-7]{3}) (.+)r?n(.+)r?nend/Us", $input, $matches);
  477.         for ($j = 0; $j < count($matches[3]); $j++) {
  478.             $str      = $matches[3][$j];
  479.             $filename = $matches[2][$j];
  480.             $fileperm = $matches[1][$j];
  481.             $file = '';
  482.             $str = preg_split("/r?n/", trim($str));
  483.             $strlen = count($str);
  484.             for ($i = 0; $i < $strlen; $i++) {
  485.                 $pos = 1;
  486.                 $d = 0;
  487.                 $len=(int)(((ord(substr($str[$i],0,1)) -32) - ' ') & 077);
  488.                 while (($d + 3 <= $len) AND ($pos + 4 <= strlen($str[$i]))) {
  489.                     $c0 = (ord(substr($str[$i],$pos,1)) ^ 0x20);
  490.                     $c1 = (ord(substr($str[$i],$pos+1,1)) ^ 0x20);
  491.                     $c2 = (ord(substr($str[$i],$pos+2,1)) ^ 0x20);
  492.                     $c3 = (ord(substr($str[$i],$pos+3,1)) ^ 0x20);
  493.                     $file .= chr(((($c0 - ' ') & 077) << 2) | ((($c1 - ' ') & 077) >> 4));
  494.                     $file .= chr(((($c1 - ' ') & 077) << 4) | ((($c2 - ' ') & 077) >> 2));
  495.                     $file .= chr(((($c2 - ' ') & 077) << 6) |  (($c3 - ' ') & 077));
  496.                     $pos += 4;
  497.                     $d += 3;
  498.                 }
  499.                 if (($d + 2 <= $len) && ($pos + 3 <= strlen($str[$i]))) {
  500.                     $c0 = (ord(substr($str[$i],$pos,1)) ^ 0x20);
  501.                     $c1 = (ord(substr($str[$i],$pos+1,1)) ^ 0x20);
  502.                     $c2 = (ord(substr($str[$i],$pos+2,1)) ^ 0x20);
  503.                     $file .= chr(((($c0 - ' ') & 077) << 2) | ((($c1 - ' ') & 077) >> 4));
  504.                     $file .= chr(((($c1 - ' ') & 077) << 4) | ((($c2 - ' ') & 077) >> 2));
  505.                     $pos += 3;
  506.                     $d += 2;
  507.                 }
  508.                 if (($d + 1 <= $len) && ($pos + 2 <= strlen($str[$i]))) {
  509.                     $c0 = (ord(substr($str[$i],$pos,1)) ^ 0x20);
  510.                     $c1 = (ord(substr($str[$i],$pos+1,1)) ^ 0x20);
  511.                     $file .= chr(((($c0 - ' ') & 077) << 2) | ((($c1 - ' ') & 077) >> 4));
  512.                 }
  513.             }
  514.             $files[] = array('filename' => $filename, 'fileperm' => $fileperm, 'filedata' => $file);
  515.         }
  516.         return $files;
  517.     }
  518. } // End of class
  519. ?>