Automatically exported from code.google.com/p/planningalerts
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

838 lines
30 KiB

  1. <?php
  2. /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
  3. // +-----------------------------------------------------------------------+
  4. // | Copyright (c) 2002-2003 Richard Heyes |
  5. // | Copyright (c) 2003-2005 The PHP Group |
  6. // | All rights reserved. |
  7. // | |
  8. // | Redistribution and use in source and binary forms, with or without |
  9. // | modification, are permitted provided that the following conditions |
  10. // | are met: |
  11. // | |
  12. // | o Redistributions of source code must retain the above copyright |
  13. // | notice, this list of conditions and the following disclaimer. |
  14. // | o Redistributions in binary form must reproduce the above copyright |
  15. // | notice, this list of conditions and the following disclaimer in the |
  16. // | documentation and/or other materials provided with the distribution.|
  17. // | o The names of the authors may not be used to endorse or promote |
  18. // | products derived from this software without specific prior written |
  19. // | permission. |
  20. // | |
  21. // | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
  22. // | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
  23. // | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
  24. // | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
  25. // | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
  26. // | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
  27. // | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
  28. // | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
  29. // | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
  30. // | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
  31. // | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
  32. // | |
  33. // +-----------------------------------------------------------------------+
  34. // | Author: Richard Heyes <richard@phpguru.org> |
  35. // +-----------------------------------------------------------------------+
  36. require_once 'PEAR.php';
  37. /**
  38. * +----------------------------- IMPORTANT ------------------------------+
  39. * | Usage of this class compared to native php extensions such as |
  40. * | mailparse or imap, is slow and may be feature deficient. If available|
  41. * | you are STRONGLY recommended to use the php extensions. |
  42. * +----------------------------------------------------------------------+
  43. *
  44. * Mime Decoding class
  45. *
  46. * This class will parse a raw mime email and return
  47. * the structure. Returned structure is similar to
  48. * that returned by imap_fetchstructure().
  49. *
  50. * USAGE: (assume $input is your raw email)
  51. *
  52. * $decode = new Mail_mimeDecode($input, "\r\n");
  53. * $structure = $decode->decode();
  54. * print_r($structure);
  55. *
  56. * Or statically:
  57. *
  58. * $params['input'] = $input;
  59. * $structure = Mail_mimeDecode::decode($params);
  60. * print_r($structure);
  61. *
  62. * TODO:
  63. * o Implement multipart/appledouble
  64. * o UTF8: ???
  65. > 4. We have also found a solution for decoding the UTF-8
  66. > headers. Therefore I made the following function:
  67. >
  68. > function decode_utf8($txt) {
  69. > $trans=array("Å&#8216;"=>"õ","ű"=>"û","Å�"=>"Ã&#8226;","Å°"
  70. =>"Ã&#8250;");
  71. > $txt=strtr($txt,$trans);
  72. > return(utf8_decode($txt));
  73. > }
  74. >
  75. > And I have inserted the following line to the class:
  76. >
  77. > if (strtolower($charset)=="utf-8") $text=decode_utf8($text);
  78. >
  79. > ... before the following one in the "_decodeHeader" function:
  80. >
  81. > $input = str_replace($encoded, $text, $input);
  82. >
  83. > This way from now on it can easily decode the UTF-8 headers too.
  84. *
  85. * @author Richard Heyes <richard@phpguru.org>
  86. * @version $Revision: 1.46 $
  87. * @package Mail
  88. */
  89. class Mail_mimeDecode extends PEAR
  90. {
  91. /**
  92. * The raw email to decode
  93. * @var string
  94. */
  95. var $_input;
  96. /**
  97. * The header part of the input
  98. * @var string
  99. */
  100. var $_header;
  101. /**
  102. * The body part of the input
  103. * @var string
  104. */
  105. var $_body;
  106. /**
  107. * If an error occurs, this is used to store the message
  108. * @var string
  109. */
  110. var $_error;
  111. /**
  112. * Flag to determine whether to include bodies in the
  113. * returned object.
  114. * @var boolean
  115. */
  116. var $_include_bodies;
  117. /**
  118. * Flag to determine whether to decode bodies
  119. * @var boolean
  120. */
  121. var $_decode_bodies;
  122. /**
  123. * Flag to determine whether to decode headers
  124. * @var boolean
  125. */
  126. var $_decode_headers;
  127. /**
  128. * Constructor.
  129. *
  130. * Sets up the object, initialise the variables, and splits and
  131. * stores the header and body of the input.
  132. *
  133. * @param string The input to decode
  134. * @access public
  135. */
  136. function Mail_mimeDecode($input)
  137. {
  138. list($header, $body) = $this->_splitBodyHeader($input);
  139. $this->_input = $input;
  140. $this->_header = $header;
  141. $this->_body = $body;
  142. $this->_decode_bodies = false;
  143. $this->_include_bodies = true;
  144. }
  145. /**
  146. * Begins the decoding process. If called statically
  147. * it will create an object and call the decode() method
  148. * of it.
  149. *
  150. * @param array An array of various parameters that determine
  151. * various things:
  152. * include_bodies - Whether to include the body in the returned
  153. * object.
  154. * decode_bodies - Whether to decode the bodies
  155. * of the parts. (Transfer encoding)
  156. * decode_headers - Whether to decode headers
  157. * input - If called statically, this will be treated
  158. * as the input
  159. * @return object Decoded results
  160. * @access public
  161. */
  162. function decode($params = null)
  163. {
  164. // determine if this method has been called statically
  165. $isStatic = !(isset($this) && get_class($this) == __CLASS__);
  166. // Have we been called statically?
  167. // If so, create an object and pass details to that.
  168. if ($isStatic AND isset($params['input'])) {
  169. $obj = new Mail_mimeDecode($params['input']);
  170. $structure = $obj->decode($params);
  171. // Called statically but no input
  172. } elseif ($isStatic) {
  173. return PEAR::raiseError('Called statically and no input given');
  174. // Called via an object
  175. } else {
  176. $this->_include_bodies = isset($params['include_bodies']) ?
  177. $params['include_bodies'] : false;
  178. $this->_decode_bodies = isset($params['decode_bodies']) ?
  179. $params['decode_bodies'] : false;
  180. $this->_decode_headers = isset($params['decode_headers']) ?
  181. $params['decode_headers'] : false;
  182. $structure = $this->_decode($this->_header, $this->_body);
  183. if ($structure === false) {
  184. $structure = $this->raiseError($this->_error);
  185. }
  186. }
  187. return $structure;
  188. }
  189. /**
  190. * Performs the decoding. Decodes the body string passed to it
  191. * If it finds certain content-types it will call itself in a
  192. * recursive fashion
  193. *
  194. * @param string Header section
  195. * @param string Body section
  196. * @return object Results of decoding process
  197. * @access private
  198. */
  199. function _decode($headers, $body, $default_ctype = 'text/plain')
  200. {
  201. $return = new stdClass;
  202. $return->headers = array();
  203. $headers = $this->_parseHeaders($headers);
  204. foreach ($headers as $value) {
  205. if (isset($return->headers[strtolower($value['name'])]) AND !is_array($return->headers[strtolower($value['name'])])) {
  206. $return->headers[strtolower($value['name'])] = array($return->headers[strtolower($value['name'])]);
  207. $return->headers[strtolower($value['name'])][] = $value['value'];
  208. } elseif (isset($return->headers[strtolower($value['name'])])) {
  209. $return->headers[strtolower($value['name'])][] = $value['value'];
  210. } else {
  211. $return->headers[strtolower($value['name'])] = $value['value'];
  212. }
  213. }
  214. reset($headers);
  215. while (list($key, $value) = each($headers)) {
  216. $headers[$key]['name'] = strtolower($headers[$key]['name']);
  217. switch ($headers[$key]['name']) {
  218. case 'content-type':
  219. $content_type = $this->_parseHeaderValue($headers[$key]['value']);
  220. if (preg_match('/([0-9a-z+.-]+)\/([0-9a-z+.-]+)/i', $content_type['value'], $regs)) {
  221. $return->ctype_primary = $regs[1];
  222. $return->ctype_secondary = $regs[2];
  223. }
  224. if (isset($content_type['other'])) {
  225. while (list($p_name, $p_value) = each($content_type['other'])) {
  226. $return->ctype_parameters[$p_name] = $p_value;
  227. }
  228. }
  229. break;
  230. case 'content-disposition':
  231. $content_disposition = $this->_parseHeaderValue($headers[$key]['value']);
  232. $return->disposition = $content_disposition['value'];
  233. if (isset($content_disposition['other'])) {
  234. while (list($p_name, $p_value) = each($content_disposition['other'])) {
  235. $return->d_parameters[$p_name] = $p_value;
  236. }
  237. }
  238. break;
  239. case 'content-transfer-encoding':
  240. $content_transfer_encoding = $this->_parseHeaderValue($headers[$key]['value']);
  241. break;
  242. }
  243. }
  244. if (isset($content_type)) {
  245. switch (strtolower($content_type['value'])) {
  246. case 'text/plain':
  247. $encoding = isset($content_transfer_encoding) ? $content_transfer_encoding['value'] : '7bit';
  248. $this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $encoding) : $body) : null;
  249. break;
  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. case 'multipart/parallel':
  255. case 'multipart/report': // RFC1892
  256. case 'multipart/signed': // PGP
  257. case 'multipart/digest':
  258. case 'multipart/alternative':
  259. case 'multipart/related':
  260. case 'multipart/mixed':
  261. if(!isset($content_type['other']['boundary'])){
  262. $this->_error = 'No boundary found for ' . $content_type['value'] . ' part';
  263. return false;
  264. }
  265. $default_ctype = (strtolower($content_type['value']) === 'multipart/digest') ? 'message/rfc822' : 'text/plain';
  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. case 'message/rfc822':
  276. $obj = &new Mail_mimeDecode($body);
  277. $return->parts[] = $obj->decode(array('include_bodies' => $this->_include_bodies,
  278. 'decode_bodies' => $this->_decode_bodies,
  279. 'decode_headers' => $this->_decode_headers));
  280. unset($obj);
  281. break;
  282. default:
  283. if(!isset($content_transfer_encoding['value']))
  284. $content_transfer_encoding['value'] = '7bit';
  285. $this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $content_transfer_encoding['value']) : $body) : null;
  286. break;
  287. }
  288. } else {
  289. $ctype = explode('/', $default_ctype);
  290. $return->ctype_primary = $ctype[0];
  291. $return->ctype_secondary = $ctype[1];
  292. $this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body) : $body) : null;
  293. }
  294. return $return;
  295. }
  296. /**
  297. * Given the output of the above function, this will return an
  298. * array of references to the parts, indexed by mime number.
  299. *
  300. * @param object $structure The structure to go through
  301. * @param string $mime_number Internal use only.
  302. * @return array Mime numbers
  303. */
  304. function &getMimeNumbers(&$structure, $no_refs = false, $mime_number = '', $prepend = '')
  305. {
  306. $return = array();
  307. if (!empty($structure->parts)) {
  308. if ($mime_number != '') {
  309. $structure->mime_id = $prepend . $mime_number;
  310. $return[$prepend . $mime_number] = &$structure;
  311. }
  312. for ($i = 0; $i < count($structure->parts); $i++) {
  313. if (!empty($structure->headers['content-type']) AND substr(strtolower($structure->headers['content-type']), 0, 8) == 'message/') {
  314. $prepend = $prepend . $mime_number . '.';
  315. $_mime_number = '';
  316. } else {
  317. $_mime_number = ($mime_number == '' ? $i + 1 : sprintf('%s.%s', $mime_number, $i + 1));
  318. }
  319. $arr = &Mail_mimeDecode::getMimeNumbers($structure->parts[$i], $no_refs, $_mime_number, $prepend);
  320. foreach ($arr as $key => $val) {
  321. $no_refs ? $return[$key] = '' : $return[$key] = &$arr[$key];
  322. }
  323. }
  324. } else {
  325. if ($mime_number == '') {
  326. $mime_number = '1';
  327. }
  328. $structure->mime_id = $prepend . $mime_number;
  329. $no_refs ? $return[$prepend . $mime_number] = '' : $return[$prepend . $mime_number] = &$structure;
  330. }
  331. return $return;
  332. }
  333. /**
  334. * Given a string containing a header and body
  335. * section, this function will split them (at the first
  336. * blank line) and return them.
  337. *
  338. * @param string Input to split apart
  339. * @return array Contains header and body section
  340. * @access private
  341. */
  342. function _splitBodyHeader($input)
  343. {
  344. if (preg_match("/^(.*?)\r?\n\r?\n(.*)/s", $input, $match)) {
  345. return array($match[1], $match[2]);
  346. }
  347. $this->_error = 'Could not split header and body';
  348. return false;
  349. }
  350. /**
  351. * Parse headers given in $input and return
  352. * as assoc array.
  353. *
  354. * @param string Headers to parse
  355. * @return array Contains parsed headers
  356. * @access private
  357. */
  358. function _parseHeaders($input)
  359. {
  360. if ($input !== '') {
  361. // Unfold the input
  362. $input = preg_replace("/\r?\n/", "\r\n", $input);
  363. $input = preg_replace("/\r\n(\t| )+/", ' ', $input);
  364. $headers = explode("\r\n", trim($input));
  365. foreach ($headers as $value) {
  366. $hdr_name = substr($value, 0, $pos = strpos($value, ':'));
  367. $hdr_value = substr($value, $pos+1);
  368. if($hdr_value[0] == ' ')
  369. $hdr_value = substr($hdr_value, 1);
  370. $return[] = array(
  371. 'name' => $hdr_name,
  372. 'value' => $this->_decode_headers ? $this->_decodeHeader($hdr_value) : $hdr_value
  373. );
  374. }
  375. } else {
  376. $return = array();
  377. }
  378. return $return;
  379. }
  380. /**
  381. * Function to parse a header value,
  382. * extract first part, and any secondary
  383. * parts (after ;) This function is not as
  384. * robust as it could be. Eg. header comments
  385. * in the wrong place will probably break it.
  386. *
  387. * @param string Header value to parse
  388. * @return array Contains parsed result
  389. * @access private
  390. */
  391. function _parseHeaderValue($input)
  392. {
  393. if (($pos = strpos($input, ';')) !== false) {
  394. $return['value'] = trim(substr($input, 0, $pos));
  395. $input = trim(substr($input, $pos+1));
  396. if (strlen($input) > 0) {
  397. // This splits on a semi-colon, if there's no preceeding backslash
  398. // Now works with quoted values; had to glue the \; breaks in PHP
  399. // the regex is already bordering on incomprehensible
  400. $splitRegex = '/([^;\'"]*[\'"]([^\'"]*([^\'"]*)*)[\'"][^;\'"]*|([^;]+))(;|$)/';
  401. preg_match_all($splitRegex, $input, $matches);
  402. $parameters = array();
  403. for ($i=0; $i<count($matches[0]); $i++) {
  404. $param = $matches[0][$i];
  405. while (substr($param, -2) == '\;') {
  406. $param .= $matches[0][++$i];
  407. }
  408. $parameters[] = $param;
  409. }
  410. for ($i = 0; $i < count($parameters); $i++) {
  411. $param_name = trim(substr($parameters[$i], 0, $pos = strpos($parameters[$i], '=')), "'\";\t\\ ");
  412. $param_value = trim(str_replace('\;', ';', substr($parameters[$i], $pos + 1)), "'\";\t\\ ");
  413. if ($param_value[0] == '"') {
  414. $param_value = substr($param_value, 1, -1);
  415. }
  416. $return['other'][$param_name] = $param_value;
  417. $return['other'][strtolower($param_name)] = $param_value;
  418. }
  419. }
  420. } else {
  421. $return['value'] = trim($input);
  422. }
  423. return $return;
  424. }
  425. /**
  426. * This function splits the input based
  427. * on the given boundary
  428. *
  429. * @param string Input to parse
  430. * @return array Contains array of resulting mime parts
  431. * @access private
  432. */
  433. function _boundarySplit($input, $boundary)
  434. {
  435. $parts = array();
  436. $bs_possible = substr($boundary, 2, -2);
  437. $bs_check = '\"' . $bs_possible . '\"';
  438. if ($boundary == $bs_check) {
  439. $boundary = $bs_possible;
  440. }
  441. $tmp = explode('--' . $boundary, $input);
  442. for ($i = 1; $i < count($tmp) - 1; $i++) {
  443. $parts[] = $tmp[$i];
  444. }
  445. return $parts;
  446. }
  447. /**
  448. * Given a header, this function will decode it
  449. * according to RFC2047. Probably not *exactly*
  450. * conformant, but it does pass all the given
  451. * examples (in RFC2047).
  452. *
  453. * @param string Input header value to decode
  454. * @return string Decoded header value
  455. * @access private
  456. */
  457. function _decodeHeader($input)
  458. {
  459. // Remove white space between encoded-words
  460. $input = preg_replace('/(=\?[^?]+\?(q|b)\?[^?]*\?=)(\s)+=\?/i', '\1=?', $input);
  461. // For each encoded-word...
  462. while (preg_match('/(=\?([^?]+)\?(q|b)\?([^?]*)\?=)/i', $input, $matches)) {
  463. $encoded = $matches[1];
  464. $charset = $matches[2];
  465. $encoding = $matches[3];
  466. $text = $matches[4];
  467. switch (strtolower($encoding)) {
  468. case 'b':
  469. $text = base64_decode($text);
  470. break;
  471. case 'q':
  472. $text = str_replace('_', ' ', $text);
  473. preg_match_all('/=([a-f0-9]{2})/i', $text, $matches);
  474. foreach($matches[1] as $value)
  475. $text = str_replace('='.$value, chr(hexdec($value)), $text);
  476. break;
  477. }
  478. $input = str_replace($encoded, $text, $input);
  479. }
  480. return $input;
  481. }
  482. /**
  483. * Given a body string and an encoding type,
  484. * this function will decode and return it.
  485. *
  486. * @param string Input body to decode
  487. * @param string Encoding type to use.
  488. * @return string Decoded body
  489. * @access private
  490. */
  491. function _decodeBody($input, $encoding = '7bit')
  492. {
  493. switch (strtolower($encoding)) {
  494. case '7bit':
  495. return $input;
  496. break;
  497. case 'quoted-printable':
  498. return $this->_quotedPrintableDecode($input);
  499. break;
  500. case 'base64':
  501. return base64_decode($input);
  502. break;
  503. default:
  504. return $input;
  505. }
  506. }
  507. /**
  508. * Given a quoted-printable string, this
  509. * function will decode and return it.
  510. *
  511. * @param string Input body to decode
  512. * @return string Decoded body
  513. * @access private
  514. */
  515. function _quotedPrintableDecode($input)
  516. {
  517. // Remove soft line breaks
  518. $input = preg_replace("/=\r?\n/", '', $input);
  519. // Replace encoded characters
  520. $input = preg_replace('/=([a-f0-9]{2})/ie', "chr(hexdec('\\1'))", $input);
  521. return $input;
  522. }
  523. /**
  524. * Checks the input for uuencoded files and returns
  525. * an array of them. Can be called statically, eg:
  526. *
  527. * $files =& Mail_mimeDecode::uudecode($some_text);
  528. *
  529. * It will check for the begin 666 ... end syntax
  530. * however and won't just blindly decode whatever you
  531. * pass it.
  532. *
  533. * @param string Input body to look for attahcments in
  534. * @return array Decoded bodies, filenames and permissions
  535. * @access public
  536. * @author Unknown
  537. */
  538. function &uudecode($input)
  539. {
  540. // Find all uuencoded sections
  541. preg_match_all("/begin ([0-7]{3}) (.+)\r?\n(.+)\r?\nend/Us", $input, $matches);
  542. for ($j = 0; $j < count($matches[3]); $j++) {
  543. $str = $matches[3][$j];
  544. $filename = $matches[2][$j];
  545. $fileperm = $matches[1][$j];
  546. $file = '';
  547. $str = preg_split("/\r?\n/", trim($str));
  548. $strlen = count($str);
  549. for ($i = 0; $i < $strlen; $i++) {
  550. $pos = 1;
  551. $d = 0;
  552. $len=(int)(((ord(substr($str[$i],0,1)) -32) - ' ') & 077);
  553. while (($d + 3 <= $len) AND ($pos + 4 <= strlen($str[$i]))) {
  554. $c0 = (ord(substr($str[$i],$pos,1)) ^ 0x20);
  555. $c1 = (ord(substr($str[$i],$pos+1,1)) ^ 0x20);
  556. $c2 = (ord(substr($str[$i],$pos+2,1)) ^ 0x20);
  557. $c3 = (ord(substr($str[$i],$pos+3,1)) ^ 0x20);
  558. $file .= chr(((($c0 - ' ') & 077) << 2) | ((($c1 - ' ') & 077) >> 4));
  559. $file .= chr(((($c1 - ' ') & 077) << 4) | ((($c2 - ' ') & 077) >> 2));
  560. $file .= chr(((($c2 - ' ') & 077) << 6) | (($c3 - ' ') & 077));
  561. $pos += 4;
  562. $d += 3;
  563. }
  564. if (($d + 2 <= $len) && ($pos + 3 <= strlen($str[$i]))) {
  565. $c0 = (ord(substr($str[$i],$pos,1)) ^ 0x20);
  566. $c1 = (ord(substr($str[$i],$pos+1,1)) ^ 0x20);
  567. $c2 = (ord(substr($str[$i],$pos+2,1)) ^ 0x20);
  568. $file .= chr(((($c0 - ' ') & 077) << 2) | ((($c1 - ' ') & 077) >> 4));
  569. $file .= chr(((($c1 - ' ') & 077) << 4) | ((($c2 - ' ') & 077) >> 2));
  570. $pos += 3;
  571. $d += 2;
  572. }
  573. if (($d + 1 <= $len) && ($pos + 2 <= strlen($str[$i]))) {
  574. $c0 = (ord(substr($str[$i],$pos,1)) ^ 0x20);
  575. $c1 = (ord(substr($str[$i],$pos+1,1)) ^ 0x20);
  576. $file .= chr(((($c0 - ' ') & 077) << 2) | ((($c1 - ' ') & 077) >> 4));
  577. }
  578. }
  579. $files[] = array('filename' => $filename, 'fileperm' => $fileperm, 'filedata' => $file);
  580. }
  581. return $files;
  582. }
  583. /**
  584. * getSendArray() returns the arguments required for Mail::send()
  585. * used to build the arguments for a mail::send() call
  586. *
  587. * Usage:
  588. * $mailtext = Full email (for example generated by a template)
  589. * $decoder = new Mail_mimeDecode($mailtext);
  590. * $parts = $decoder->getSendArray();
  591. * if (!PEAR::isError($parts) {
  592. * list($recipents,$headers,$body) = $parts;
  593. * $mail = Mail::factory('smtp');
  594. * $mail->send($recipents,$headers,$body);
  595. * } else {
  596. * echo $parts->message;
  597. * }
  598. * @return mixed array of recipeint, headers,body or Pear_Error
  599. * @access public
  600. * @author Alan Knowles <alan@akbkhome.com>
  601. */
  602. function getSendArray()
  603. {
  604. // prevent warning if this is not set
  605. $this->_decode_headers = FALSE;
  606. $headerlist =$this->_parseHeaders($this->_header);
  607. $to = "";
  608. if (!$headerlist) {
  609. return $this->raiseError("Message did not contain headers");
  610. }
  611. foreach($headerlist as $item) {
  612. $header[$item['name']] = $item['value'];
  613. switch (strtolower($item['name'])) {
  614. case "to":
  615. case "cc":
  616. case "bcc":
  617. $to = ",".$item['value'];
  618. default:
  619. break;
  620. }
  621. }
  622. if ($to == "") {
  623. return $this->raiseError("Message did not contain any recipents");
  624. }
  625. $to = substr($to,1);
  626. return array($to,$header,$this->_body);
  627. }
  628. /**
  629. * Returns a xml copy of the output of
  630. * Mail_mimeDecode::decode. Pass the output in as the
  631. * argument. This function can be called statically. Eg:
  632. *
  633. * $output = $obj->decode();
  634. * $xml = Mail_mimeDecode::getXML($output);
  635. *
  636. * The DTD used for this should have been in the package. Or
  637. * alternatively you can get it from cvs, or here:
  638. * http://www.phpguru.org/xmail/xmail.dtd.
  639. *
  640. * @param object Input to convert to xml. This should be the
  641. * output of the Mail_mimeDecode::decode function
  642. * @return string XML version of input
  643. * @access public
  644. */
  645. function getXML($input)
  646. {
  647. $crlf = "\r\n";
  648. $output = '<?xml version=\'1.0\'?>' . $crlf .
  649. '<!DOCTYPE email SYSTEM "http://www.phpguru.org/xmail/xmail.dtd">' . $crlf .
  650. '<email>' . $crlf .
  651. Mail_mimeDecode::_getXML($input) .
  652. '</email>';
  653. return $output;
  654. }
  655. /**
  656. * Function that does the actual conversion to xml. Does a single
  657. * mimepart at a time.
  658. *
  659. * @param object Input to convert to xml. This is a mimepart object.
  660. * It may or may not contain subparts.
  661. * @param integer Number of tabs to indent
  662. * @return string XML version of input
  663. * @access private
  664. */
  665. function _getXML($input, $indent = 1)
  666. {
  667. $htab = "\t";
  668. $crlf = "\r\n";
  669. $output = '';
  670. $headers = @(array)$input->headers;
  671. foreach ($headers as $hdr_name => $hdr_value) {
  672. // Multiple headers with this name
  673. if (is_array($headers[$hdr_name])) {
  674. for ($i = 0; $i < count($hdr_value); $i++) {
  675. $output .= Mail_mimeDecode::_getXML_helper($hdr_name, $hdr_value[$i], $indent);
  676. }
  677. // Only one header of this sort
  678. } else {
  679. $output .= Mail_mimeDecode::_getXML_helper($hdr_name, $hdr_value, $indent);
  680. }
  681. }
  682. if (!empty($input->parts)) {
  683. for ($i = 0; $i < count($input->parts); $i++) {
  684. $output .= $crlf . str_repeat($htab, $indent) . '<mimepart>' . $crlf .
  685. Mail_mimeDecode::_getXML($input->parts[$i], $indent+1) .
  686. str_repeat($htab, $indent) . '</mimepart>' . $crlf;
  687. }
  688. } elseif (isset($input->body)) {
  689. $output .= $crlf . str_repeat($htab, $indent) . '<body><![CDATA[' .
  690. $input->body . ']]></body>' . $crlf;
  691. }
  692. return $output;
  693. }
  694. /**
  695. * Helper function to _getXML(). Returns xml of a header.
  696. *
  697. * @param string Name of header
  698. * @param string Value of header
  699. * @param integer Number of tabs to indent
  700. * @return string XML version of input
  701. * @access private
  702. */
  703. function _getXML_helper($hdr_name, $hdr_value, $indent)
  704. {
  705. $htab = "\t";
  706. $crlf = "\r\n";
  707. $return = '';
  708. $new_hdr_value = ($hdr_name != 'received') ? Mail_mimeDecode::_parseHeaderValue($hdr_value) : array('value' => $hdr_value);
  709. $new_hdr_name = str_replace(' ', '-', ucwords(str_replace('-', ' ', $hdr_name)));
  710. // Sort out any parameters
  711. if (!empty($new_hdr_value['other'])) {
  712. foreach ($new_hdr_value['other'] as $paramname => $paramvalue) {
  713. $params[] = str_repeat($htab, $indent) . $htab . '<parameter>' . $crlf .
  714. str_repeat($htab, $indent) . $htab . $htab . '<paramname>' . htmlspecialchars($paramname) . '</paramname>' . $crlf .
  715. str_repeat($htab, $indent) . $htab . $htab . '<paramvalue>' . htmlspecialchars($paramvalue) . '</paramvalue>' . $crlf .
  716. str_repeat($htab, $indent) . $htab . '</parameter>' . $crlf;
  717. }
  718. $params = implode('', $params);
  719. } else {
  720. $params = '';
  721. }
  722. $return = str_repeat($htab, $indent) . '<header>' . $crlf .
  723. str_repeat($htab, $indent) . $htab . '<headername>' . htmlspecialchars($new_hdr_name) . '</headername>' . $crlf .
  724. str_repeat($htab, $indent) . $htab . '<headervalue>' . htmlspecialchars($new_hdr_value['value']) . '</headervalue>' . $crlf .
  725. $params .
  726. str_repeat($htab, $indent) . '</header>' . $crlf;
  727. return $return;
  728. }
  729. } // End of class
  730. ?>