PageRenderTime 27ms CodeModel.GetById 8ms RepoModel.GetById 0ms app.codeStats 1ms

/htdocs/includes/nusoap/lib/Mail/mimeDecode.php

https://bitbucket.org/speedealing/speedealing
PHP | 836 lines | 586 code | 53 blank | 197 comment | 43 complexity | a5443b4444f4551ebb892cbf826972c3 MD5 | raw file
Possible License(s): LGPL-3.0, LGPL-2.1, GPL-3.0, MIT
  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. * @package Mail
  87. */
  88. class Mail_mimeDecode extends PEAR
  89. {
  90. /**
  91. * The raw email to decode
  92. * @var string
  93. */
  94. var $_input;
  95. /**
  96. * The header part of the input
  97. * @var string
  98. */
  99. var $_header;
  100. /**
  101. * The body part of the input
  102. * @var string
  103. */
  104. var $_body;
  105. /**
  106. * If an error occurs, this is used to store the message
  107. * @var string
  108. */
  109. var $_error;
  110. /**
  111. * Flag to determine whether to include bodies in the
  112. * returned object.
  113. * @var boolean
  114. */
  115. var $_include_bodies;
  116. /**
  117. * Flag to determine whether to decode bodies
  118. * @var boolean
  119. */
  120. var $_decode_bodies;
  121. /**
  122. * Flag to determine whether to decode headers
  123. * @var boolean
  124. */
  125. var $_decode_headers;
  126. /**
  127. * Constructor.
  128. *
  129. * Sets up the object, initialise the variables, and splits and
  130. * stores the header and body of the input.
  131. *
  132. * @param string The input to decode
  133. * @access public
  134. */
  135. function Mail_mimeDecode($input)
  136. {
  137. list($header, $body) = $this->_splitBodyHeader($input);
  138. $this->_input = $input;
  139. $this->_header = $header;
  140. $this->_body = $body;
  141. $this->_decode_bodies = false;
  142. $this->_include_bodies = true;
  143. }
  144. /**
  145. * Begins the decoding process. If called statically
  146. * it will create an object and call the decode() method
  147. * of it.
  148. *
  149. * @param array An array of various parameters that determine
  150. * various things:
  151. * include_bodies - Whether to include the body in the returned
  152. * object.
  153. * decode_bodies - Whether to decode the bodies
  154. * of the parts. (Transfer encoding)
  155. * decode_headers - Whether to decode headers
  156. * input - If called statically, this will be treated
  157. * as the input
  158. * @return object Decoded results
  159. * @access public
  160. */
  161. function decode($params = null)
  162. {
  163. // determine if this method has been called statically
  164. $isStatic = !(isset($this) && get_class($this) == __CLASS__);
  165. // Have we been called statically?
  166. // If so, create an object and pass details to that.
  167. if ($isStatic AND isset($params['input'])) {
  168. $obj = new Mail_mimeDecode($params['input']);
  169. $structure = $obj->decode($params);
  170. // Called statically but no input
  171. } elseif ($isStatic) {
  172. return PEAR::raiseError('Called statically and no input given');
  173. // Called via an object
  174. } else {
  175. $this->_include_bodies = isset($params['include_bodies']) ?
  176. $params['include_bodies'] : false;
  177. $this->_decode_bodies = isset($params['decode_bodies']) ?
  178. $params['decode_bodies'] : false;
  179. $this->_decode_headers = isset($params['decode_headers']) ?
  180. $params['decode_headers'] : false;
  181. $structure = $this->_decode($this->_header, $this->_body);
  182. if ($structure === false) {
  183. $structure = $this->raiseError($this->_error);
  184. }
  185. }
  186. return $structure;
  187. }
  188. /**
  189. * Performs the decoding. Decodes the body string passed to it
  190. * If it finds certain content-types it will call itself in a
  191. * recursive fashion
  192. *
  193. * @param string Header section
  194. * @param string Body section
  195. * @return object Results of decoding process
  196. * @access private
  197. */
  198. function _decode($headers, $body, $default_ctype = 'text/plain')
  199. {
  200. $return = new stdClass;
  201. $return->headers = array();
  202. $headers = $this->_parseHeaders($headers);
  203. foreach ($headers as $value) {
  204. if (isset($return->headers[strtolower($value['name'])]) AND !is_array($return->headers[strtolower($value['name'])])) {
  205. $return->headers[strtolower($value['name'])] = array($return->headers[strtolower($value['name'])]);
  206. $return->headers[strtolower($value['name'])][] = $value['value'];
  207. } elseif (isset($return->headers[strtolower($value['name'])])) {
  208. $return->headers[strtolower($value['name'])][] = $value['value'];
  209. } else {
  210. $return->headers[strtolower($value['name'])] = $value['value'];
  211. }
  212. }
  213. reset($headers);
  214. while (list($key, $value) = each($headers)) {
  215. $headers[$key]['name'] = strtolower($headers[$key]['name']);
  216. switch ($headers[$key]['name']) {
  217. case 'content-type':
  218. $content_type = $this->_parseHeaderValue($headers[$key]['value']);
  219. if (preg_match('/([0-9a-z+.-]+)\/([0-9a-z+.-]+)/i', $content_type['value'], $regs)) {
  220. $return->ctype_primary = $regs[1];
  221. $return->ctype_secondary = $regs[2];
  222. }
  223. if (isset($content_type['other'])) {
  224. while (list($p_name, $p_value) = each($content_type['other'])) {
  225. $return->ctype_parameters[$p_name] = $p_value;
  226. }
  227. }
  228. break;
  229. case 'content-disposition':
  230. $content_disposition = $this->_parseHeaderValue($headers[$key]['value']);
  231. $return->disposition = $content_disposition['value'];
  232. if (isset($content_disposition['other'])) {
  233. while (list($p_name, $p_value) = each($content_disposition['other'])) {
  234. $return->d_parameters[$p_name] = $p_value;
  235. }
  236. }
  237. break;
  238. case 'content-transfer-encoding':
  239. $content_transfer_encoding = $this->_parseHeaderValue($headers[$key]['value']);
  240. break;
  241. }
  242. }
  243. if (isset($content_type)) {
  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. case 'text/html':
  250. $encoding = isset($content_transfer_encoding) ? $content_transfer_encoding['value'] : '7bit';
  251. $this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $encoding) : $body) : null;
  252. break;
  253. case 'multipart/parallel':
  254. case 'multipart/report': // RFC1892
  255. case 'multipart/signed': // PGP
  256. case 'multipart/digest':
  257. case 'multipart/alternative':
  258. case 'multipart/related':
  259. case 'multipart/mixed':
  260. if(!isset($content_type['other']['boundary'])){
  261. $this->_error = 'No boundary found for ' . $content_type['value'] . ' part';
  262. return false;
  263. }
  264. $default_ctype = (strtolower($content_type['value']) === 'multipart/digest') ? 'message/rfc822' : 'text/plain';
  265. $parts = $this->_boundarySplit($body, $content_type['other']['boundary']);
  266. for ($i = 0; $i < count($parts); $i++) {
  267. list($part_header, $part_body) = $this->_splitBodyHeader($parts[$i]);
  268. $part = $this->_decode($part_header, $part_body, $default_ctype);
  269. if($part === false)
  270. $part = $this->raiseError($this->_error);
  271. $return->parts[] = $part;
  272. }
  273. break;
  274. case 'message/rfc822':
  275. $obj = &new Mail_mimeDecode($body);
  276. $return->parts[] = $obj->decode(array('include_bodies' => $this->_include_bodies,
  277. 'decode_bodies' => $this->_decode_bodies,
  278. 'decode_headers' => $this->_decode_headers));
  279. unset($obj);
  280. break;
  281. default:
  282. if(!isset($content_transfer_encoding['value']))
  283. $content_transfer_encoding['value'] = '7bit';
  284. $this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $content_transfer_encoding['value']) : $body) : null;
  285. break;
  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. return $return;
  294. }
  295. /**
  296. * Given the output of the above function, this will return an
  297. * array of references to the parts, indexed by mime number.
  298. *
  299. * @param object $structure The structure to go through
  300. * @param string $mime_number Internal use only.
  301. * @return array Mime numbers
  302. */
  303. function &getMimeNumbers(&$structure, $no_refs = false, $mime_number = '', $prepend = '')
  304. {
  305. $return = array();
  306. if (!empty($structure->parts)) {
  307. if ($mime_number != '') {
  308. $structure->mime_id = $prepend . $mime_number;
  309. $return[$prepend . $mime_number] = &$structure;
  310. }
  311. for ($i = 0; $i < count($structure->parts); $i++) {
  312. if (!empty($structure->headers['content-type']) AND substr(strtolower($structure->headers['content-type']), 0, 8) == 'message/') {
  313. $prepend = $prepend . $mime_number . '.';
  314. $_mime_number = '';
  315. } else {
  316. $_mime_number = ($mime_number == '' ? $i + 1 : sprintf('%s.%s', $mime_number, $i + 1));
  317. }
  318. $arr = &Mail_mimeDecode::getMimeNumbers($structure->parts[$i], $no_refs, $_mime_number, $prepend);
  319. foreach ($arr as $key => $val) {
  320. $no_refs ? $return[$key] = '' : $return[$key] = &$arr[$key];
  321. }
  322. }
  323. } else {
  324. if ($mime_number == '') {
  325. $mime_number = '1';
  326. }
  327. $structure->mime_id = $prepend . $mime_number;
  328. $no_refs ? $return[$prepend . $mime_number] = '' : $return[$prepend . $mime_number] = &$structure;
  329. }
  330. return $return;
  331. }
  332. /**
  333. * Given a string containing a header and body
  334. * section, this function will split them (at the first
  335. * blank line) and return them.
  336. *
  337. * @param string Input to split apart
  338. * @return array Contains header and body section
  339. * @access private
  340. */
  341. function _splitBodyHeader($input)
  342. {
  343. if (preg_match("/^(.*?)\r?\n\r?\n(.*)/s", $input, $match)) {
  344. return array($match[1], $match[2]);
  345. }
  346. $this->_error = 'Could not split header and body';
  347. return false;
  348. }
  349. /**
  350. * Parse headers given in $input and return
  351. * as assoc array.
  352. *
  353. * @param string Headers to parse
  354. * @return array Contains parsed headers
  355. * @access private
  356. */
  357. function _parseHeaders($input)
  358. {
  359. if ($input !== '') {
  360. // Unfold the input
  361. $input = preg_replace("/\r?\n/", "\r\n", $input);
  362. $input = preg_replace("/\r\n(\t| )+/", ' ', $input);
  363. $headers = explode("\r\n", trim($input));
  364. foreach ($headers as $value) {
  365. $hdr_name = substr($value, 0, $pos = strpos($value, ':'));
  366. $hdr_value = substr($value, $pos+1);
  367. if($hdr_value[0] == ' ')
  368. $hdr_value = substr($hdr_value, 1);
  369. $return[] = array(
  370. 'name' => $hdr_name,
  371. 'value' => $this->_decode_headers ? $this->_decodeHeader($hdr_value) : $hdr_value
  372. );
  373. }
  374. } else {
  375. $return = array();
  376. }
  377. return $return;
  378. }
  379. /**
  380. * Function to parse a header value,
  381. * extract first part, and any secondary
  382. * parts (after ;) This function is not as
  383. * robust as it could be. Eg. header comments
  384. * in the wrong place will probably break it.
  385. *
  386. * @param string Header value to parse
  387. * @return array Contains parsed result
  388. * @access private
  389. */
  390. function _parseHeaderValue($input)
  391. {
  392. if (($pos = strpos($input, ';')) !== false) {
  393. $return['value'] = trim(substr($input, 0, $pos));
  394. $input = trim(substr($input, $pos+1));
  395. if (strlen($input) > 0) {
  396. // This splits on a semi-colon, if there's no preceeding backslash
  397. // Now works with quoted values; had to glue the \; breaks in PHP
  398. // the regex is already bordering on incomprehensible
  399. $splitRegex = '/([^;\'"]*[\'"]([^\'"]*([^\'"]*)*)[\'"][^;\'"]*|([^;]+))(;|$)/';
  400. preg_match_all($splitRegex, $input, $matches);
  401. $parameters = array();
  402. for ($i=0; $i<count($matches[0]); $i++) {
  403. $param = $matches[0][$i];
  404. while (substr($param, -2) == '\;') {
  405. $param .= $matches[0][++$i];
  406. }
  407. $parameters[] = $param;
  408. }
  409. for ($i = 0; $i < count($parameters); $i++) {
  410. $param_name = trim(substr($parameters[$i], 0, $pos = strpos($parameters[$i], '=')), "'\";\t\\ ");
  411. $param_value = trim(str_replace('\;', ';', substr($parameters[$i], $pos + 1)), "'\";\t\\ ");
  412. if ($param_value[0] == '"') {
  413. $param_value = substr($param_value, 1, -1);
  414. }
  415. $return['other'][$param_name] = $param_value;
  416. $return['other'][strtolower($param_name)] = $param_value;
  417. }
  418. }
  419. } else {
  420. $return['value'] = trim($input);
  421. }
  422. return $return;
  423. }
  424. /**
  425. * This function splits the input based
  426. * on the given boundary
  427. *
  428. * @param string Input to parse
  429. * @return array Contains array of resulting mime parts
  430. * @access private
  431. */
  432. function _boundarySplit($input, $boundary)
  433. {
  434. $parts = array();
  435. $bs_possible = substr($boundary, 2, -2);
  436. $bs_check = '\"' . $bs_possible . '\"';
  437. if ($boundary == $bs_check) {
  438. $boundary = $bs_possible;
  439. }
  440. $tmp = explode('--' . $boundary, $input);
  441. for ($i = 1; $i < count($tmp) - 1; $i++) {
  442. $parts[] = $tmp[$i];
  443. }
  444. return $parts;
  445. }
  446. /**
  447. * Given a header, this function will decode it
  448. * according to RFC2047. Probably not *exactly*
  449. * conformant, but it does pass all the given
  450. * examples (in RFC2047).
  451. *
  452. * @param string Input header value to decode
  453. * @return string Decoded header value
  454. * @access private
  455. */
  456. function _decodeHeader($input)
  457. {
  458. // Remove white space between encoded-words
  459. $input = preg_replace('/(=\?[^?]+\?(q|b)\?[^?]*\?=)(\s)+=\?/i', '\1=?', $input);
  460. // For each encoded-word...
  461. while (preg_match('/(=\?([^?]+)\?(q|b)\?([^?]*)\?=)/i', $input, $matches)) {
  462. $encoded = $matches[1];
  463. $charset = $matches[2];
  464. $encoding = $matches[3];
  465. $text = $matches[4];
  466. switch (strtolower($encoding)) {
  467. case 'b':
  468. $text = base64_decode($text);
  469. break;
  470. case 'q':
  471. $text = str_replace('_', ' ', $text);
  472. preg_match_all('/=([a-f0-9]{2})/i', $text, $matches);
  473. foreach($matches[1] as $value)
  474. $text = str_replace('='.$value, chr(hexdec($value)), $text);
  475. break;
  476. }
  477. $input = str_replace($encoded, $text, $input);
  478. }
  479. return $input;
  480. }
  481. /**
  482. * Given a body string and an encoding type,
  483. * this function will decode and return it.
  484. *
  485. * @param string Input body to decode
  486. * @param string Encoding type to use.
  487. * @return string Decoded body
  488. * @access private
  489. */
  490. function _decodeBody($input, $encoding = '7bit')
  491. {
  492. switch (strtolower($encoding)) {
  493. case '7bit':
  494. return $input;
  495. break;
  496. case 'quoted-printable':
  497. return $this->_quotedPrintableDecode($input);
  498. break;
  499. case 'base64':
  500. return base64_decode($input);
  501. break;
  502. default:
  503. return $input;
  504. }
  505. }
  506. /**
  507. * Given a quoted-printable string, this
  508. * function will decode and return it.
  509. *
  510. * @param string Input body to decode
  511. * @return string Decoded body
  512. * @access private
  513. */
  514. function _quotedPrintableDecode($input)
  515. {
  516. // Remove soft line breaks
  517. $input = preg_replace("/=\r?\n/", '', $input);
  518. // Replace encoded characters
  519. $input = preg_replace('/=([a-f0-9]{2})/ie', "chr(hexdec('\\1'))", $input);
  520. return $input;
  521. }
  522. /**
  523. * Checks the input for uuencoded files and returns
  524. * an array of them. Can be called statically, eg:
  525. *
  526. * $files =& Mail_mimeDecode::uudecode($some_text);
  527. *
  528. * It will check for the begin 666 ... end syntax
  529. * however and won't just blindly decode whatever you
  530. * pass it.
  531. *
  532. * @param string Input body to look for attahcments in
  533. * @return array Decoded bodies, filenames and permissions
  534. * @access public
  535. * @author Unknown
  536. */
  537. function &uudecode($input)
  538. {
  539. // Find all uuencoded sections
  540. preg_match_all("/begin ([0-7]{3}) (.+)\r?\n(.+)\r?\nend/Us", $input, $matches);
  541. for ($j = 0; $j < count($matches[3]); $j++) {
  542. $str = $matches[3][$j];
  543. $filename = $matches[2][$j];
  544. $fileperm = $matches[1][$j];
  545. $file = '';
  546. $str = preg_split("/\r?\n/", trim($str));
  547. $strlen = count($str);
  548. for ($i = 0; $i < $strlen; $i++) {
  549. $pos = 1;
  550. $d = 0;
  551. $len=(int)(((ord(substr($str[$i],0,1)) -32) - ' ') & 077);
  552. while (($d + 3 <= $len) AND ($pos + 4 <= 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. $c3 = (ord(substr($str[$i],$pos+3,1)) ^ 0x20);
  557. $file .= chr(((($c0 - ' ') & 077) << 2) | ((($c1 - ' ') & 077) >> 4));
  558. $file .= chr(((($c1 - ' ') & 077) << 4) | ((($c2 - ' ') & 077) >> 2));
  559. $file .= chr(((($c2 - ' ') & 077) << 6) | (($c3 - ' ') & 077));
  560. $pos += 4;
  561. $d += 3;
  562. }
  563. if (($d + 2 <= $len) && ($pos + 3 <= strlen($str[$i]))) {
  564. $c0 = (ord(substr($str[$i],$pos,1)) ^ 0x20);
  565. $c1 = (ord(substr($str[$i],$pos+1,1)) ^ 0x20);
  566. $c2 = (ord(substr($str[$i],$pos+2,1)) ^ 0x20);
  567. $file .= chr(((($c0 - ' ') & 077) << 2) | ((($c1 - ' ') & 077) >> 4));
  568. $file .= chr(((($c1 - ' ') & 077) << 4) | ((($c2 - ' ') & 077) >> 2));
  569. $pos += 3;
  570. $d += 2;
  571. }
  572. if (($d + 1 <= $len) && ($pos + 2 <= strlen($str[$i]))) {
  573. $c0 = (ord(substr($str[$i],$pos,1)) ^ 0x20);
  574. $c1 = (ord(substr($str[$i],$pos+1,1)) ^ 0x20);
  575. $file .= chr(((($c0 - ' ') & 077) << 2) | ((($c1 - ' ') & 077) >> 4));
  576. }
  577. }
  578. $files[] = array('filename' => $filename, 'fileperm' => $fileperm, 'filedata' => $file);
  579. }
  580. return $files;
  581. }
  582. /**
  583. * getSendArray() returns the arguments required for Mail::send()
  584. * used to build the arguments for a mail::send() call
  585. *
  586. * Usage:
  587. * $mailtext = Full email (for example generated by a template)
  588. * $decoder = new Mail_mimeDecode($mailtext);
  589. * $parts = $decoder->getSendArray();
  590. * if (!PEAR::isError($parts) {
  591. * list($recipents,$headers,$body) = $parts;
  592. * $mail = Mail::factory('smtp');
  593. * $mail->send($recipents,$headers,$body);
  594. * } else {
  595. * echo $parts->message;
  596. * }
  597. * @return mixed array of recipeint, headers,body or Pear_Error
  598. * @access public
  599. * @author Alan Knowles <alan@akbkhome.com>
  600. */
  601. function getSendArray()
  602. {
  603. // prevent warning if this is not set
  604. $this->_decode_headers = FALSE;
  605. $headerlist =$this->_parseHeaders($this->_header);
  606. $to = "";
  607. if (!$headerlist) {
  608. return $this->raiseError("Message did not contain headers");
  609. }
  610. foreach($headerlist as $item) {
  611. $header[$item['name']] = $item['value'];
  612. switch (strtolower($item['name'])) {
  613. case "to":
  614. case "cc":
  615. case "bcc":
  616. $to = ",".$item['value'];
  617. default:
  618. break;
  619. }
  620. }
  621. if ($to == "") {
  622. return $this->raiseError("Message did not contain any recipents");
  623. }
  624. $to = substr($to,1);
  625. return array($to,$header,$this->_body);
  626. }
  627. /**
  628. * Returns a xml copy of the output of
  629. * Mail_mimeDecode::decode. Pass the output in as the
  630. * argument. This function can be called statically. Eg:
  631. *
  632. * $output = $obj->decode();
  633. * $xml = Mail_mimeDecode::getXML($output);
  634. *
  635. * The DTD used for this should have been in the package. Or
  636. * alternatively you can get it from cvs, or here:
  637. * http://www.phpguru.org/xmail/xmail.dtd.
  638. *
  639. * @param object Input to convert to xml. This should be the
  640. * output of the Mail_mimeDecode::decode function
  641. * @return string XML version of input
  642. * @access public
  643. */
  644. function getXML($input)
  645. {
  646. $crlf = "\r\n";
  647. $output = '<?xml version=\'1.0\'?>' . $crlf .
  648. '<!DOCTYPE email SYSTEM "http://www.phpguru.org/xmail/xmail.dtd">' . $crlf .
  649. '<email>' . $crlf .
  650. Mail_mimeDecode::_getXML($input) .
  651. '</email>';
  652. return $output;
  653. }
  654. /**
  655. * Function that does the actual conversion to xml. Does a single
  656. * mimepart at a time.
  657. *
  658. * @param object Input to convert to xml. This is a mimepart object.
  659. * It may or may not contain subparts.
  660. * @param integer Number of tabs to indent
  661. * @return string XML version of input
  662. * @access private
  663. */
  664. function _getXML($input, $indent = 1)
  665. {
  666. $htab = "\t";
  667. $crlf = "\r\n";
  668. $output = '';
  669. $headers = @(array)$input->headers;
  670. foreach ($headers as $hdr_name => $hdr_value) {
  671. // Multiple headers with this name
  672. if (is_array($headers[$hdr_name])) {
  673. for ($i = 0; $i < count($hdr_value); $i++) {
  674. $output .= Mail_mimeDecode::_getXML_helper($hdr_name, $hdr_value[$i], $indent);
  675. }
  676. // Only one header of this sort
  677. } else {
  678. $output .= Mail_mimeDecode::_getXML_helper($hdr_name, $hdr_value, $indent);
  679. }
  680. }
  681. if (!empty($input->parts)) {
  682. for ($i = 0; $i < count($input->parts); $i++) {
  683. $output .= $crlf . str_repeat($htab, $indent) . '<mimepart>' . $crlf .
  684. Mail_mimeDecode::_getXML($input->parts[$i], $indent+1) .
  685. str_repeat($htab, $indent) . '</mimepart>' . $crlf;
  686. }
  687. } elseif (isset($input->body)) {
  688. $output .= $crlf . str_repeat($htab, $indent) . '<body><![CDATA[' .
  689. $input->body . ']]></body>' . $crlf;
  690. }
  691. return $output;
  692. }
  693. /**
  694. * Helper function to _getXML(). Returns xml of a header.
  695. *
  696. * @param string Name of header
  697. * @param string Value of header
  698. * @param integer Number of tabs to indent
  699. * @return string XML version of input
  700. * @access private
  701. */
  702. function _getXML_helper($hdr_name, $hdr_value, $indent)
  703. {
  704. $htab = "\t";
  705. $crlf = "\r\n";
  706. $return = '';
  707. $new_hdr_value = ($hdr_name != 'received') ? Mail_mimeDecode::_parseHeaderValue($hdr_value) : array('value' => $hdr_value);
  708. $new_hdr_name = str_replace(' ', '-', ucwords(str_replace('-', ' ', $hdr_name)));
  709. // Sort out any parameters
  710. if (!empty($new_hdr_value['other'])) {
  711. foreach ($new_hdr_value['other'] as $paramname => $paramvalue) {
  712. $params[] = str_repeat($htab, $indent) . $htab . '<parameter>' . $crlf .
  713. str_repeat($htab, $indent) . $htab . $htab . '<paramname>' . htmlspecialchars($paramname) . '</paramname>' . $crlf .
  714. str_repeat($htab, $indent) . $htab . $htab . '<paramvalue>' . htmlspecialchars($paramvalue) . '</paramvalue>' . $crlf .
  715. str_repeat($htab, $indent) . $htab . '</parameter>' . $crlf;
  716. }
  717. $params = implode('', $params);
  718. } else {
  719. $params = '';
  720. }
  721. $return = str_repeat($htab, $indent) . '<header>' . $crlf .
  722. str_repeat($htab, $indent) . $htab . '<headername>' . htmlspecialchars($new_hdr_name) . '</headername>' . $crlf .
  723. str_repeat($htab, $indent) . $htab . '<headervalue>' . htmlspecialchars($new_hdr_value['value']) . '</headervalue>' . $crlf .
  724. $params .
  725. str_repeat($htab, $indent) . '</header>' . $crlf;
  726. return $return;
  727. }
  728. } // End of class
  729. ?>