mimeDecode.php 30 KB

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