Fork of FusionPBX but with LDAP kinda working
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.

2438 lines
76 KiB

2 years ago
  1. <?php
  2. /*
  3. * mime_parser.php
  4. *
  5. * @(#) $Id: mime_parser.php,v 1.68 2010/04/08 22:58:13 mlemos Exp $
  6. *
  7. */
  8. define('MIME_PARSER_START', 1);
  9. define('MIME_PARSER_HEADER', 2);
  10. define('MIME_PARSER_HEADER_VALUE', 3);
  11. define('MIME_PARSER_BODY', 4);
  12. define('MIME_PARSER_BODY_START', 5);
  13. define('MIME_PARSER_BODY_DATA', 6);
  14. define('MIME_PARSER_BODY_DONE', 7);
  15. define('MIME_PARSER_END', 8);
  16. define('MIME_MESSAGE_START', 1);
  17. define('MIME_MESSAGE_GET_HEADER_NAME', 2);
  18. define('MIME_MESSAGE_GET_HEADER_VALUE', 3);
  19. define('MIME_MESSAGE_GET_BODY', 4);
  20. define('MIME_MESSAGE_GET_BODY_PART', 5);
  21. define('MIME_ADDRESS_START', 1);
  22. define('MIME_ADDRESS_FIRST', 2);
  23. /*
  24. {metadocument}<?xml version="1.0" encoding="ISO-8859-1" ?>
  25. <class>
  26. <package>net.manuellemos.mimeparser</package>
  27. <version>@(#) $Id: mime_parser.php,v 1.68 2010/04/08 22:58:13 mlemos Exp $</version>
  28. <copyright>Copyright (C) Manuel Lemos 2006 - 2008</copyright>
  29. <title>MIME parser</title>
  30. <author>Manuel Lemos</author>
  31. <authoraddress>mlemos-at-acm.org</authoraddress>
  32. <documentation>
  33. <idiom>en</idiom>
  34. <purpose>Parse MIME encapsulated e-mail message data compliant with
  35. the RFC 2822 or aggregated in mbox format.</purpose>
  36. <usage>Use the function <functionlink>Decode</functionlink> function
  37. to retrieve the structure of the messages to be parsed. Adjust its
  38. parameters to tell how to return the decoded body data.
  39. Use the <tt>SaveBody</tt> parameter to make the body parts be saved
  40. to files when the message is larger than the available memory. Use
  41. the <tt>SkipBody</tt> parameter to just retrieve the message
  42. structure without returning the body data.<paragraphbreak />
  43. If the message data is an archive that may contain multiple messages
  44. aggregated in the mbox format, set the variable
  45. <variablelink>mbox</variablelink> to <booleanvalue>1</booleanvalue>.</usage>
  46. </documentation>
  47. {/metadocument}
  48. */
  49. class mime_parser_class
  50. {
  51. /*
  52. {metadocument}
  53. <variable>
  54. <name>error</name>
  55. <type>STRING</type>
  56. <value></value>
  57. <documentation>
  58. <purpose>Store the message that is returned when an error
  59. occurs.</purpose>
  60. <usage>Check this variable to understand what happened when a call to
  61. any of the class functions has failed.<paragraphbreak />
  62. This class uses cumulative error handling. This means that if one
  63. class functions that may fail is called and this variable was
  64. already set to an error message due to a failure in a previous call
  65. to the same or other function, the function will also fail and does
  66. not do anything.<paragraphbreak />
  67. This allows programs using this class to safely call several
  68. functions that may fail and only check the failure condition after
  69. the last function call.<paragraphbreak />
  70. Just set this variable to an empty string to clear the error
  71. condition.</usage>
  72. </documentation>
  73. </variable>
  74. {/metadocument}
  75. */
  76. var $error='';
  77. /*
  78. {metadocument}
  79. <variable>
  80. <name>error_position</name>
  81. <type>INTEGER</type>
  82. <value>-1</value>
  83. <documentation>
  84. <purpose>Point to the position of the message data or file that
  85. refers to the last error that occurred.</purpose>
  86. <usage>Check this variable to determine the relevant position of the
  87. message when a parsing error occurs.</usage>
  88. </documentation>
  89. </variable>
  90. {/metadocument}
  91. */
  92. var $error_position = -1;
  93. /*
  94. {metadocument}
  95. <variable>
  96. <name>mbox</name>
  97. <type>BOOLEAN</type>
  98. <value>0</value>
  99. <documentation>
  100. <purpose>Specify whether the message data to parse is a single RFC
  101. 2822 message or it is an archive that contain multiple messages in
  102. the mbox format.</purpose>
  103. <usage>Set this variable to <booleanvalue>1</booleanvalue> if it is
  104. it is intended to parse an mbox message archive.<br />
  105. mbox archives may contain multiple messages. Each message starts
  106. with the header <tt>From</tt>. Since all valid RFC 2822 headers
  107. must with a colon, the class will fail to parse a mbox archive if
  108. this variable is set to <booleanvalue>0</booleanvalue>.</usage>
  109. </documentation>
  110. </variable>
  111. {/metadocument}
  112. */
  113. var $mbox = 0;
  114. /*
  115. {metadocument}
  116. <variable>
  117. <name>decode_headers</name>
  118. <type>BOOLEAN</type>
  119. <value>1</value>
  120. <documentation>
  121. <purpose>Specify whether the message headers should be decoded.</purpose>
  122. <usage>Set this variable to <booleanvalue>1</booleanvalue> if it is
  123. necessary to decode message headers that may have non-ASCII
  124. characters and use other character set encodings.</usage>
  125. </documentation>
  126. </variable>
  127. {/metadocument}
  128. */
  129. var $decode_headers = 1;
  130. /*
  131. {metadocument}
  132. <variable>
  133. <name>decode_bodies</name>
  134. <type>BOOLEAN</type>
  135. <value>1</value>
  136. <documentation>
  137. <purpose>Specify whether the message body parts should be decoded.</purpose>
  138. <usage>Set this variable to <booleanvalue>1</booleanvalue> if it is
  139. necessary to parse the message bodies and extract its part
  140. structure.</usage>
  141. </documentation>
  142. </variable>
  143. {/metadocument}
  144. */
  145. var $decode_bodies = 1;
  146. /*
  147. {metadocument}
  148. <variable>
  149. <name>extract_addresses</name>
  150. <type>BOOLEAN</type>
  151. <value>1</value>
  152. <documentation>
  153. <purpose>Specify whether the message headers that usually contain
  154. e-mail addresses should be parsed and the addresses should be
  155. extracted by the <functionlink>Decode</functionlink> function.</purpose>
  156. <usage>Set this variable to <booleanvalue>1</booleanvalue> if it is
  157. necessary to extract the e-mail addresses contained in certain
  158. message headers.<paragraphbreak />
  159. The headers to be parsed are defined by the
  160. <variablelink>address_headers</variablelink> variable.<paragraphbreak />
  161. The parsed addresses are returned by the
  162. <tt>ExtractedAddresses</tt> entry of the <argumentlink>
  163. <function>Decode</function>
  164. <argument>decoded</argument>
  165. </argumentlink> argument of the
  166. <functionlink>Decode</functionlink> function.</usage>
  167. </documentation>
  168. </variable>
  169. {/metadocument}
  170. */
  171. var $extract_addresses = 1;
  172. /*
  173. {metadocument}
  174. <variable>
  175. <name>address_headers</name>
  176. <type>HASH</type>
  177. <value></value>
  178. <documentation>
  179. <purpose>Specify which headers contain addresses that should be
  180. parsed and extracted.</purpose>
  181. <usage>Change this variable if you need to extract e-mail addresses
  182. from a different list of message headers.<paragraphbreak />
  183. It must be set to an associative array with keys set to the names
  184. of the headers to be parsed including the colon. The array values
  185. must be set to a boolean flag to tell whether the headers with the
  186. respective name should be parsed. The header names must be in lower
  187. case.<paragraphbreak />
  188. By default the class addresses from the headers:
  189. <stringvalue>from:</stringvalue>, <stringvalue>to:</stringvalue>,
  190. <stringvalue>cc:</stringvalue>, <stringvalue>bcc:</stringvalue>,
  191. <stringvalue>return-path:</stringvalue>,
  192. <stringvalue>reply-to:</stringvalue> and
  193. <stringvalue>disposition-notification-to:</stringvalue>.</usage>
  194. </documentation>
  195. </variable>
  196. {/metadocument}
  197. */
  198. var $address_headers = array(
  199. 'from:' => 1,
  200. 'to:' => 1,
  201. 'cc:' => 1,
  202. 'bcc:' => 1,
  203. 'return-path:'=>1,
  204. 'reply-to:'=>1,
  205. 'disposition-notification-to:'=>1
  206. );
  207. /*
  208. {metadocument}
  209. <variable>
  210. <name>message_buffer_length</name>
  211. <type>INTEGER</type>
  212. <value>8000</value>
  213. <documentation>
  214. <purpose>Maximum length of the chunks of message data that the class
  215. parse at one time.</purpose>
  216. <usage>Adjust this value according to the available memory.</usage>
  217. </documentation>
  218. </variable>
  219. {/metadocument}
  220. */
  221. var $message_buffer_length = 8000;
  222. /*
  223. {metadocument}
  224. <variable>
  225. <name>ignore_syntax_errors</name>
  226. <type>BOOLEAN</type>
  227. <value>1</value>
  228. <documentation>
  229. <purpose>Specify whether the class should ignore syntax errors in
  230. malformed messages.</purpose>
  231. <usage>Set this variable to <booleanvalue>0</booleanvalue> if it is
  232. necessary to verify whether message data may be corrupted due to
  233. to eventual bugs in the program that generated the
  234. message.<paragraphbreak />
  235. Currently the class only ignores some types of syntax errors.
  236. Other syntax errors may still cause the
  237. <functionlink>Decode</functionlink> to fail.</usage>
  238. </documentation>
  239. </variable>
  240. {/metadocument}
  241. */
  242. var $ignore_syntax_errors=1;
  243. /*
  244. {metadocument}
  245. <variable>
  246. <name>warnings</name>
  247. <type>HASH</type>
  248. <value></value>
  249. <documentation>
  250. <purpose>Return a list of positions of the original message that
  251. contain syntax errors.</purpose>
  252. <usage>Check this variable to retrieve eventual message syntax
  253. errors that were ignored when the
  254. <variablelink>ignore_syntax_errors</variablelink> is set to
  255. <booleanvalue>1</booleanvalue>.<paragraphbreak />
  256. The indexes of this array are the positions of the errors. The
  257. array values are the corresponding syntax error messages.</usage>
  258. </documentation>
  259. </variable>
  260. {/metadocument}
  261. */
  262. var $warnings=array();
  263. /*
  264. {metadocument}
  265. <variable>
  266. <name>track_lines</name>
  267. <type>BOOLEAN</type>
  268. <value>0</value>
  269. <documentation>
  270. <purpose>Tell the class to keep track the position of each message
  271. line.</purpose>
  272. <usage>Set this variable to <integervalue>1</integervalue> if you
  273. need to determine the line and column number associated to a given
  274. position of the parsed message.</usage>
  275. </documentation>
  276. </variable>
  277. {/metadocument}
  278. */
  279. var $track_lines = 0;
  280. /* Private variables */
  281. var $state = MIME_PARSER_START;
  282. var $buffer = '';
  283. var $buffer_position = 0;
  284. var $offset = 0;
  285. var $parts = array();
  286. var $part_position = 0;
  287. var $headers = array();
  288. var $body_parser;
  289. var $body_parser_state = MIME_PARSER_BODY_DONE;
  290. var $body_buffer = '';
  291. var $body_buffer_position = 0;
  292. var $body_offset = 0;
  293. var $current_header = '';
  294. var $file;
  295. var $body_file;
  296. var $position = 0;
  297. var $body_part_number = 1;
  298. var $next_token = '';
  299. var $lines = array();
  300. var $line_offset = 0;
  301. var $last_line = 1;
  302. var $last_carriage_return = 0;
  303. /* Private functions */
  304. Function SetError($error)
  305. {
  306. $this->error = $error;
  307. return(0);
  308. }
  309. Function SetErrorWithContact($error)
  310. {
  311. return($this->SetError($error.'. Please contact the author Manuel Lemos <mlemos@acm.org> and send a copy of this message to let him add support for this kind of messages'));
  312. }
  313. Function SetPositionedError($error, $position)
  314. {
  315. $this->error_position = $position;
  316. return($this->SetError($error));
  317. }
  318. Function SetPositionedWarning($error, $position)
  319. {
  320. if(!$this->ignore_syntax_errors)
  321. return($this->SetPositionedError($error, $position));
  322. $this->warnings[$position]=$error;
  323. return(1);
  324. }
  325. Function SetPHPError($error, &$php_error_message)
  326. {
  327. if(IsSet($php_error_message)
  328. && strlen($php_error_message))
  329. $error .= ': '.$php_error_message;
  330. return($this->SetError($error));
  331. }
  332. Function ResetParserState()
  333. {
  334. $this->error='';
  335. $this->error_position = -1;
  336. $this->state = MIME_PARSER_START;
  337. $this->buffer = '';
  338. $this->buffer_position = 0;
  339. $this->offset = 0;
  340. $this->parts = array();
  341. $this->part_position = 0;
  342. $this->headers = array();
  343. $this->body_parser_state = MIME_PARSER_BODY_DONE;
  344. $this->body_buffer = '';
  345. $this->body_buffer_position = 0;
  346. $this->body_offset = 0;
  347. $this->current_header = '';
  348. $this->position = 0;
  349. $this->body_part_number = 1;
  350. $this->next_token = '';
  351. $this->lines = ($this->track_lines ? array(0 => 0) : array());
  352. $this->line_offset = 0;
  353. $this->last_line = 0;
  354. $this->last_carriage_return = 0;
  355. }
  356. Function Tokenize($string,$separator="")
  357. {
  358. if(!strcmp($separator,""))
  359. {
  360. $separator=$string;
  361. $string=$this->next_token;
  362. }
  363. for($character=0;$character<strlen($separator);$character++)
  364. {
  365. if(GetType($position=strpos($string,$separator[$character]))=='integer')
  366. $found=(IsSet($found) ? min($found,$position) : $position);
  367. }
  368. if(IsSet($found))
  369. {
  370. $this->next_token=substr($string,$found+1);
  371. return(substr($string,0,$found));
  372. }
  373. else
  374. {
  375. $this->next_token='';
  376. return($string);
  377. }
  378. }
  379. Function ParseStructuredHeader($value, &$type, &$parameters, &$character_sets, &$languages)
  380. {
  381. $type = strtolower(trim($this->Tokenize($value, ';')));
  382. $p = trim($this->Tokenize(''));
  383. $parameters = $character_sets = $languages = array();
  384. while(strlen($p))
  385. {
  386. $parameter = trim(strtolower($this->Tokenize($p, '=')));
  387. $remaining = trim($this->Tokenize(''));
  388. if(strlen($remaining)
  389. && !strcmp($remaining[0], '"')
  390. && (GetType($quote = strpos($remaining, '"', 1)) == 'integer'))
  391. {
  392. $value = substr($remaining, 1, $quote - 1);
  393. $p = trim(substr($remaining, $quote + 1));
  394. if(strlen($p) > 0
  395. && !strcmp($p[0], ';'))
  396. $p = substr($p, 1);
  397. }
  398. else
  399. {
  400. $value = trim($this->Tokenize($remaining, ';'));
  401. $p = trim($this->Tokenize(''));
  402. }
  403. if(($l=strlen($parameter))
  404. && !strcmp($parameter[$l - 1],'*'))
  405. {
  406. $parameter=$this->Tokenize($parameter, '*');
  407. if(IsSet($parameters[$parameter])
  408. && IsSet($character_sets[$parameter]))
  409. $value = $parameters[$parameter] . UrlDecode($value);
  410. else
  411. {
  412. $character_sets[$parameter] = strtolower($this->Tokenize($value, '\''));
  413. $languages[$parameter] = $this->Tokenize('\'');
  414. $value = UrlDecode($this->Tokenize(''));
  415. }
  416. }
  417. $parameters[$parameter] = $value;
  418. }
  419. }
  420. Function FindStringLineBreak($string, $position, &$break, &$line_break)
  421. {
  422. if(GetType($line_break=strpos($string, $break="\n", $position))=='integer')
  423. {
  424. if(GetType($new_line_break=strpos($string, "\n", $position))=='integer')
  425. {
  426. if($new_line_break < $line_break)
  427. {
  428. $break = "\n";
  429. $line_break = $new_line_break;
  430. return(1);
  431. }
  432. }
  433. if($line_break>$position
  434. && $string[$line_break-1]=="\r")
  435. {
  436. $line_break--;
  437. $break="\r\n";
  438. }
  439. return(1);
  440. }
  441. return(GetType($line_break=strpos($string, $break="\r", $position))=='integer');
  442. }
  443. Function FindLineBreak($position, &$break, &$line_break)
  444. {
  445. if(GetType($line_break=strpos($this->buffer, $break="\r", $position))=='integer')
  446. {
  447. if(GetType($new_line_break=strpos($this->buffer, "\n", $position))=='integer')
  448. {
  449. if($new_line_break < $line_break)
  450. {
  451. $break = "\n";
  452. $line_break = $new_line_break;
  453. return(1);
  454. }
  455. }
  456. if(($n = $line_break + 1) < strlen($this->buffer)
  457. && $this->buffer[$n]=="\n")
  458. $break="\r\n";
  459. return(1);
  460. }
  461. return(GetType($line_break=strpos($this->buffer, $break="\n", $position))=='integer');
  462. }
  463. Function FindBodyLineBreak($position, &$break, &$line_break)
  464. {
  465. if(GetType($line_break=strpos($this->body_buffer, $break="\r", $position))=='integer')
  466. {
  467. if(GetType($new_line_break=strpos($this->body_buffer, "\n", $position))=='integer')
  468. {
  469. if($new_line_break < $line_break)
  470. {
  471. $break = "\n";
  472. $line_break = $new_line_break;
  473. return(1);
  474. }
  475. }
  476. if(($n = $line_break + 1) < strlen($this->body_buffer)
  477. && $this->body_buffer[$n]=="\n")
  478. $break="\r\n";
  479. return(1);
  480. }
  481. return(GetType($line_break=strpos($this->body_buffer, $break="\n", $position))=='integer');
  482. }
  483. Function ParseHeaderString($body, &$position, &$headers)
  484. {
  485. $l = strlen($body);
  486. $headers = array();
  487. for(;$position < $l;)
  488. {
  489. if($this->FindStringLineBreak($body, $position, $break, $line_break))
  490. {
  491. $line = substr($body, $position, $line_break - $position);
  492. $position = $line_break + strlen($break);
  493. }
  494. else
  495. {
  496. $line = substr($body, $position);
  497. $position = $l;
  498. }
  499. if(strlen($line)==0)
  500. break;
  501. $h = strtolower(strtok($line,':'));
  502. $headers[$h] = trim(strtok(''));
  503. }
  504. }
  505. Function ParsePart($end, &$part, &$need_more_data)
  506. {
  507. $need_more_data = 0;
  508. switch($this->state)
  509. {
  510. case MIME_PARSER_START:
  511. $part=array(
  512. 'Type'=>'MessageStart',
  513. 'Position'=>$this->offset + $this->buffer_position
  514. );
  515. $this->state = MIME_PARSER_HEADER;
  516. break;
  517. case MIME_PARSER_HEADER:
  518. if($this->FindLineBreak($this->buffer_position, $break, $line_break))
  519. {
  520. $next = $line_break + strlen($break);
  521. if(!strcmp($break,"\r")
  522. && strlen($this->buffer) == $next
  523. && !$end)
  524. {
  525. $need_more_data = 1;
  526. break;
  527. }
  528. if($line_break==$this->buffer_position)
  529. {
  530. $part=array(
  531. 'Type'=>'BodyStart',
  532. 'Position'=>$this->offset + $this->buffer_position
  533. );
  534. $this->buffer_position = $next;
  535. $this->state = MIME_PARSER_BODY;
  536. break;
  537. }
  538. }
  539. if(GetType($colon=strpos($this->buffer, ':', $this->buffer_position))=='integer')
  540. {
  541. if(GetType($space=strpos(substr($this->buffer, $this->buffer_position, $colon - $this->buffer_position), ' '))=='integer')
  542. {
  543. if((!$this->mbox
  544. || strcmp(substr($this->buffer, $this->buffer_position, $space), 'From'))
  545. && !$this->SetPositionedWarning('invalid header name line', $this->buffer_position))
  546. return(0);
  547. $next = $this->buffer_position + $space + 1;
  548. }
  549. else
  550. $next = $colon+1;
  551. }
  552. else
  553. {
  554. $need_more_data = 1;
  555. break;
  556. }
  557. $part=array(
  558. 'Type'=>'HeaderName',
  559. 'Name'=>substr($this->buffer, $this->buffer_position, $next - $this->buffer_position),
  560. 'Position'=>$this->offset + $this->buffer_position
  561. );
  562. $this->buffer_position = $next;
  563. $this->state = MIME_PARSER_HEADER_VALUE;
  564. break;
  565. case MIME_PARSER_HEADER_VALUE:
  566. $position = $this->buffer_position;
  567. $value = '';
  568. for(;;)
  569. {
  570. if($this->FindLineBreak($position, $break, $line_break))
  571. {
  572. $next = $line_break + strlen($break);
  573. $line = substr($this->buffer, $position, $line_break - $position);
  574. if(strlen($this->buffer) == $next)
  575. {
  576. if(!$end)
  577. {
  578. $need_more_data = 1;
  579. break 2;
  580. }
  581. $value .= $line;
  582. $part=array(
  583. 'Type'=>'HeaderValue',
  584. 'Value'=>$value,
  585. 'Position'=>$this->offset + $this->buffer_position
  586. );
  587. $this->buffer_position = $next;
  588. $this->state = MIME_PARSER_END;
  589. break ;
  590. }
  591. else
  592. {
  593. $character = $this->buffer[$next];
  594. if(!strcmp($character, ' ')
  595. || !strcmp($character, "\t"))
  596. {
  597. $value .= $line;
  598. $position = $next + 1;
  599. }
  600. else
  601. {
  602. $value .= $line;
  603. $part=array(
  604. 'Type'=>'HeaderValue',
  605. 'Value'=>$value,
  606. 'Position'=>$this->offset + $this->buffer_position
  607. );
  608. $this->buffer_position = $next;
  609. $this->state = MIME_PARSER_HEADER;
  610. break 2;
  611. }
  612. }
  613. }
  614. else
  615. {
  616. if(!$end)
  617. {
  618. $need_more_data = 1;
  619. break;
  620. }
  621. else
  622. {
  623. $value .= substr($this->buffer, $position);
  624. $part=array(
  625. 'Type'=>'HeaderValue',
  626. 'Value'=>$value,
  627. 'Position'=>$this->offset + $this->buffer_position
  628. );
  629. $this->buffer_position = strlen($this->buffer);
  630. $this->state = MIME_PARSER_END;
  631. break;
  632. }
  633. }
  634. }
  635. break;
  636. case MIME_PARSER_BODY:
  637. if($this->mbox)
  638. {
  639. $add = 0;
  640. $append='';
  641. if($this->FindLineBreak($this->buffer_position, $break, $line_break))
  642. {
  643. $next = $line_break + strlen($break);
  644. $following = $next + strlen($break);
  645. if($following >= strlen($this->buffer)
  646. || GetType($line=strpos($this->buffer, $break, $following))!='integer')
  647. {
  648. if(!$end)
  649. {
  650. $need_more_data = 1;
  651. break;
  652. }
  653. }
  654. $start = substr($this->buffer, $next, strlen($break.'From '));
  655. if(!strcmp($break.'From ', $start))
  656. {
  657. if($line_break == $this->buffer_position)
  658. {
  659. $part=array(
  660. 'Type'=>'MessageEnd',
  661. 'Position'=>$this->offset + $this->buffer_position
  662. );
  663. $this->buffer_position = $following;
  664. $this->state = MIME_PARSER_START;
  665. break;
  666. }
  667. else
  668. $add = strlen($break);
  669. $next = $line_break;
  670. }
  671. elseif(($indent = strspn($this->buffer, '>', $next)) > 0)
  672. {
  673. $start = substr($this->buffer, $next + $indent, strlen('From '));
  674. if(!strcmp('From ', $start))
  675. {
  676. $part=array(
  677. 'Type'=>'BodyData',
  678. 'Data'=>substr($this->buffer, $this->buffer_position, $next - $this->buffer_position),
  679. 'Position'=>$this->offset + $this->buffer_position
  680. );
  681. $this->buffer_position = $next + 1;
  682. break;
  683. }
  684. }
  685. }
  686. else
  687. {
  688. if(!$end)
  689. {
  690. $need_more_data = 1;
  691. break;
  692. }
  693. $next = strlen($this->buffer);
  694. $append="\r\n";
  695. }
  696. if($next > $this->buffer_position)
  697. {
  698. $part=array(
  699. 'Type'=>'BodyData',
  700. 'Data'=>substr($this->buffer, $this->buffer_position, $next + $add - $this->buffer_position).$append,
  701. 'Position'=>$this->offset + $this->buffer_position
  702. );
  703. }
  704. elseif($end)
  705. {
  706. $part=array(
  707. 'Type'=>'MessageEnd',
  708. 'Position'=>$this->offset + $this->buffer_position
  709. );
  710. $this->state = MIME_PARSER_END;
  711. }
  712. $this->buffer_position = $next;
  713. }
  714. else
  715. {
  716. if(strlen($this->buffer)-$this->buffer_position)
  717. {
  718. $data=substr($this->buffer, $this->buffer_position, strlen($this->buffer) - $this->buffer_position);
  719. $end_line = (!strcmp(substr($data,-1),"\n") || !strcmp(substr($data,-1),"\r"));
  720. if($end
  721. && !$end_line)
  722. {
  723. $data.="\n";
  724. $end_line = 1;
  725. }
  726. $offset = $this->offset + $this->buffer_position;
  727. $this->buffer_position = strlen($this->buffer);
  728. $need_more_data = !$end;
  729. if(!$end_line)
  730. {
  731. if(GetType($line_break=strrpos($data, "\n"))=='integer'
  732. || GetType($line_break=strrpos($data, "\r"))=='integer')
  733. {
  734. $line_break++;
  735. $this->buffer_position -= strlen($data) - $line_break;
  736. $data = substr($data, 0, $line_break);
  737. }
  738. }
  739. $part=array(
  740. 'Type'=>'BodyData',
  741. 'Data'=>$data,
  742. 'Position'=>$offset
  743. );
  744. }
  745. else
  746. {
  747. if($end)
  748. {
  749. $part=array(
  750. 'Type'=>'MessageEnd',
  751. 'Position'=>$this->offset + $this->buffer_position
  752. );
  753. $this->state = MIME_PARSER_END;
  754. }
  755. else
  756. $need_more_data = 1;
  757. }
  758. }
  759. break;
  760. default:
  761. return($this->SetPositionedError($this->state.' is not a valid parser state', $this->buffer_position));
  762. }
  763. return(1);
  764. }
  765. Function QueueBodyParts()
  766. {
  767. for(;;)
  768. {
  769. if(!$this->body_parser->GetPart($part,$end))
  770. return($this->SetError($this->body_parser->error));
  771. if($end)
  772. return(1);
  773. if(!IsSet($part['Part']))
  774. $part['Part']=$this->headers['Boundary'];
  775. $this->parts[]=$part;
  776. }
  777. }
  778. Function ParseParameters($value, &$first, &$parameters, $return)
  779. {
  780. $first = strtolower(trim(strtok($value, ';')));
  781. $values = trim(strtok(''));
  782. $parameters = array();
  783. $return_value = '';
  784. while(strlen($values))
  785. {
  786. $parameter = trim(strtolower(strtok($values, '=')));
  787. $value = trim(strtok(';'));
  788. $l = strlen($value);
  789. if($l > 1
  790. && !strcmp($value[0], '"')
  791. && !strcmp($value[$l - 1], '"'))
  792. $value = substr($value, 1, $l - 2);
  793. $parameters[$parameter] = $value;
  794. if(!strcmp($parameter, $return))
  795. $return_value = $value;
  796. $values = trim(strtok(''));
  797. }
  798. return($return_value);
  799. }
  800. Function DecodePart($part)
  801. {
  802. switch($part['Type'])
  803. {
  804. case 'MessageStart':
  805. $this->headers=array();
  806. break;
  807. case 'HeaderName':
  808. if($this->decode_bodies)
  809. $this->current_header = strtolower($part['Name']);
  810. break;
  811. case 'HeaderValue':
  812. if($this->decode_headers)
  813. {
  814. $value = $part['Value'];
  815. $error = '';
  816. for($decoded_header = array(), $position = 0; $position<strlen($value); )
  817. {
  818. if(GetType($encoded=strpos($value,'=?', $position))!='integer')
  819. {
  820. if($position<strlen($value))
  821. {
  822. if(count($decoded_header))
  823. $decoded_header[count($decoded_header)-1]['Value'].=substr($value, $position);
  824. else
  825. {
  826. $decoded_header[]=array(
  827. 'Value'=>substr($value, $position),
  828. 'Encoding'=>'ASCII'
  829. );
  830. }
  831. }
  832. break;
  833. }
  834. $set = $encoded + 2;
  835. if(GetType($method=strpos($value,'?', $set))!='integer')
  836. {
  837. $error = 'invalid header encoding syntax '.$part['Value'];
  838. $error_position = $part['Position'] + $set;
  839. break;
  840. }
  841. $encoding=strtoupper(substr($value, $set, $method - $set));
  842. $method += 1;
  843. if(GetType($data=strpos($value,'?', $method))!='integer')
  844. {
  845. $error = 'invalid header encoding syntax '.$part['Value'];
  846. $error_position = $part['Position'] + $set;
  847. break;
  848. }
  849. $start = $data + 1;
  850. if(GetType($end=strpos($value,'?=', $start))!='integer')
  851. {
  852. $error = 'invalid header encoding syntax '.$part['Value'];
  853. $error_position = $part['Position'] + $start;
  854. break;
  855. }
  856. if($encoded > $position)
  857. {
  858. if(count($decoded_header))
  859. $decoded_header[count($decoded_header)-1]['Value'].=substr($value, $position, $encoded - $position);
  860. else
  861. {
  862. $decoded_header[]=array(
  863. 'Value'=>substr($value, $position, $encoded - $position),
  864. 'Encoding'=>'ASCII'
  865. );
  866. }
  867. }
  868. switch(strtolower(substr($value, $method, $data - $method)))
  869. {
  870. case 'q':
  871. if($end>$start)
  872. {
  873. for($decoded = '', $position = $start; $position < $end ; )
  874. {
  875. switch($value[$position])
  876. {
  877. case '=':
  878. $h = HexDec($hex = strtolower(substr($value, $position+1, 2)));
  879. if($end - $position < 3
  880. || strcmp(sprintf('%02x', $h), $hex))
  881. {
  882. $warning = 'the header specified an invalid encoded character';
  883. $warning_position = $part['Position'] + $position + 1;
  884. if($this->ignore_syntax_errors)
  885. {
  886. $this->SetPositionedWarning($warning, $warning_position);
  887. $decoded .= '=';
  888. $position ++;
  889. }
  890. else
  891. {
  892. $error = $warning;
  893. $error_position = $warning_position;
  894. break 4;
  895. }
  896. }
  897. else
  898. {
  899. $decoded .= Chr($h);
  900. $position += 3;
  901. }
  902. break;
  903. case '_':
  904. $decoded .= ' ';
  905. $position++;
  906. break;
  907. default:
  908. $decoded .= $value[$position];
  909. $position++;
  910. break;
  911. }
  912. }
  913. if(count($decoded_header)
  914. && (!strcmp($decoded_header[$last = count($decoded_header)-1]['Encoding'], 'ASCII'))
  915. || !strcmp($decoded_header[$last]['Encoding'], $encoding))
  916. {
  917. $decoded_header[$last]['Value'].= $decoded;
  918. $decoded_header[$last]['Encoding']= $encoding;
  919. }
  920. else
  921. {
  922. $decoded_header[]=array(
  923. 'Value'=>$decoded,
  924. 'Encoding'=>$encoding
  925. );
  926. }
  927. }
  928. break;
  929. case 'b':
  930. $decoded=base64_decode(substr($value, $start, $end - $start));
  931. if($end <= $start
  932. || GetType($decoded) != 'string'
  933. || strlen($decoded) == 0)
  934. {
  935. $warning = 'the header specified an invalid base64 encoded text';
  936. $warning_position = $part['Position'] + $start;
  937. if($this->ignore_syntax_errors)
  938. $this->SetPositionedWarning($warning, $warning_position);
  939. else
  940. {
  941. $error = $warning;
  942. $error_position = $warning_position;
  943. break 2;
  944. }
  945. }
  946. if(count($decoded_header)
  947. && (!strcmp($decoded_header[$last = count($decoded_header)-1]['Encoding'], 'ASCII'))
  948. || !strcmp($decoded_header[$last]['Encoding'], $encoding))
  949. {
  950. $decoded_header[$last]['Value'].= $decoded;
  951. $decoded_header[$last]['Encoding']= $encoding;
  952. }
  953. else
  954. {
  955. $decoded_header[]=array(
  956. 'Value'=>$decoded,
  957. 'Encoding'=>$encoding
  958. );
  959. }
  960. break;
  961. default:
  962. $error = 'the header specified an unsupported encoding method';
  963. $error_position = $part['Position'] + $method;
  964. break 2;
  965. }
  966. $position = $end + 2;
  967. }
  968. if(strlen($error)==0
  969. && count($decoded_header))
  970. $part['Decoded']=$decoded_header;
  971. }
  972. if($this->decode_bodies
  973. || $this->decode_headers)
  974. {
  975. switch($this->current_header)
  976. {
  977. case 'content-type:':
  978. $boundary = $this->ParseParameters($part['Value'], $type, $parameters, 'boundary');
  979. $this->headers['Type'] = $type;
  980. if($this->decode_headers)
  981. {
  982. $part['MainValue'] = $type;
  983. $part['Parameters'] = $parameters;
  984. }
  985. if(!strcmp(strtok($type, '/'), 'multipart'))
  986. {
  987. $this->headers['Multipart'] = 1;
  988. if(strlen($boundary))
  989. $this->headers['Boundary'] = $boundary;
  990. else
  991. return($this->SetPositionedError('multipart content-type header does not specify the boundary parameter', $part['Position']));
  992. }
  993. break;
  994. case 'content-transfer-encoding:':
  995. switch($this->headers['Encoding']=strtolower(trim($part['Value'])))
  996. {
  997. case 'quoted-printable':
  998. $this->headers['QuotedPrintable'] = 1;
  999. break;
  1000. case '7 bit':
  1001. case '8 bit':
  1002. if(!$this->SetPositionedWarning('"'.$this->headers['Encoding'].'" is an incorrect content transfer encoding type', $part['Position']))
  1003. return(0);
  1004. case '7bit':
  1005. case '8bit':
  1006. case 'binary':
  1007. break;
  1008. case 'base64':
  1009. $this->headers['Base64']=1;
  1010. break;
  1011. default:
  1012. if(!$this->SetPositionedWarning('decoding '.$this->headers['Encoding'].' encoded bodies is not yet supported', $part['Position']))
  1013. return(0);
  1014. }
  1015. break;
  1016. }
  1017. }
  1018. break;
  1019. case 'BodyStart':
  1020. if($this->decode_bodies
  1021. && IsSet($this->headers['Multipart']))
  1022. {
  1023. $this->body_parser_state = MIME_PARSER_BODY_START;
  1024. $this->body_buffer = '';
  1025. $this->body_buffer_position = 0;
  1026. }
  1027. break;
  1028. case 'MessageEnd':
  1029. if($this->decode_bodies
  1030. && IsSet($this->headers['Multipart'])
  1031. && $this->body_parser_state != MIME_PARSER_BODY_DONE)
  1032. {
  1033. if($this->body_parser_state != MIME_PARSER_BODY_DATA)
  1034. return($this->SetPositionedError('incomplete message body part', $part['Position']));
  1035. if(!$this->SetPositionedWarning('truncated message body part', $part['Position']))
  1036. return(0);
  1037. }
  1038. break;
  1039. case 'BodyData':
  1040. if($this->decode_bodies)
  1041. {
  1042. if(strlen($this->body_buffer)==0)
  1043. {
  1044. $this->body_buffer = $part['Data'];
  1045. $this->body_offset = $part['Position'];
  1046. }
  1047. else
  1048. $this->body_buffer .= $part['Data'];
  1049. if(IsSet($this->headers['Multipart']))
  1050. {
  1051. $boundary = '--'.$this->headers['Boundary'];
  1052. switch($this->body_parser_state)
  1053. {
  1054. case MIME_PARSER_BODY_START:
  1055. for($position = $this->body_buffer_position; ;)
  1056. {
  1057. if(!$this->FindBodyLineBreak($position, $break, $line_break))
  1058. return(1);
  1059. $next = $line_break + strlen($break);
  1060. if(!strcmp(rtrim(substr($this->body_buffer, $position, $line_break - $position)), $boundary))
  1061. {
  1062. $part=array(
  1063. 'Type'=>'StartPart',
  1064. 'Part'=>$this->headers['Boundary'],
  1065. 'Position'=>$this->body_offset + $next
  1066. );
  1067. $this->parts[]=$part;
  1068. UnSet($this->body_parser);
  1069. $this->body_parser = new mime_parser_class;
  1070. $this->body_parser->decode_bodies = 1;
  1071. $this->body_parser->decode_headers = $this->decode_headers;
  1072. $this->body_parser->mbox = 0;
  1073. $this->body_parser_state = MIME_PARSER_BODY_DATA;
  1074. $this->body_buffer = substr($this->body_buffer, $next);
  1075. $this->body_offset += $next;
  1076. $this->body_buffer_position = 0;
  1077. break;
  1078. }
  1079. else
  1080. $position = $next;
  1081. }
  1082. case MIME_PARSER_BODY_DATA:
  1083. for($position = $this->body_buffer_position; ;)
  1084. {
  1085. if(!$this->FindBodyLineBreak($position, $break, $line_break))
  1086. {
  1087. if($position > 0)
  1088. {
  1089. if(!$this->body_parser->Parse(substr($this->body_buffer, 0, $position), 0))
  1090. return($this->SetError($this->body_parser->error));
  1091. if(!$this->QueueBodyParts())
  1092. return(0);
  1093. }
  1094. $this->body_buffer = substr($this->body_buffer, $position);
  1095. $this->body_buffer_position = 0;
  1096. $this->body_offset += $position;
  1097. return(1);
  1098. }
  1099. $next = $line_break + strlen($break);
  1100. $line = rtrim(substr($this->body_buffer, $position, $line_break - $position));
  1101. if(!strcmp($line, $boundary.'--'))
  1102. {
  1103. if(!$this->body_parser->Parse(substr($this->body_buffer, 0, $position), 1))
  1104. return($this->SetError($this->body_parser->error));
  1105. if(!$this->QueueBodyParts())
  1106. return(0);
  1107. $part=array(
  1108. 'Type'=>'EndPart',
  1109. 'Part'=>$this->headers['Boundary'],
  1110. 'Position'=>$this->body_offset + $position
  1111. );
  1112. $this->body_buffer = substr($this->body_buffer, $next);
  1113. $this->body_buffer_position = 0;
  1114. $this->body_offset += $next;
  1115. $this->body_parser_state = MIME_PARSER_BODY_DONE;
  1116. break 2;
  1117. }
  1118. elseif(!strcmp($line, $boundary))
  1119. {
  1120. if(!$this->body_parser->Parse(substr($this->body_buffer, 0, $position), 1))
  1121. return($this->SetError($this->body_parser->error));
  1122. if(!$this->QueueBodyParts())
  1123. return(0);
  1124. $part=array(
  1125. 'Type'=>'EndPart',
  1126. 'Part'=>$this->headers['Boundary'],
  1127. 'Position'=>$this->body_offset + $position
  1128. );
  1129. $this->parts[] = $part;
  1130. $part=array(
  1131. 'Type'=>'StartPart',
  1132. 'Part'=>$this->headers['Boundary'],
  1133. 'Position'=>$this->body_offset + $next
  1134. );
  1135. $this->parts[] = $part;
  1136. UnSet($this->body_parser);
  1137. $this->body_parser = new mime_parser_class;
  1138. $this->body_parser->decode_bodies = 1;
  1139. $this->body_parser->decode_headers = $this->decode_headers;
  1140. $this->body_parser->mbox = 0;
  1141. $this->body_buffer = substr($this->body_buffer, $next);
  1142. $this->body_buffer_position = 0;
  1143. $this->body_offset += $next;
  1144. $position=0;
  1145. continue;
  1146. }
  1147. $position = $next;
  1148. }
  1149. break;
  1150. case MIME_PARSER_BODY_DONE:
  1151. return(1);
  1152. default:
  1153. return($this->SetPositionedError($this->state.' is not a valid body parser state', $this->body_buffer_position));
  1154. }
  1155. }
  1156. elseif(IsSet($this->headers['QuotedPrintable']))
  1157. {
  1158. for($end = strlen($this->body_buffer), $decoded = '', $position = $this->body_buffer_position; $position < $end; )
  1159. {
  1160. if(GetType($equal = strpos($this->body_buffer, '=', $position))!='integer')
  1161. {
  1162. $decoded .= substr($this->body_buffer, $position);
  1163. $position = $end;
  1164. break;
  1165. }
  1166. $next = $equal + 1;
  1167. switch($end - $equal)
  1168. {
  1169. case 1:
  1170. $decoded .= substr($this->body_buffer, $position, $equal - $position);
  1171. $position = $equal;
  1172. break 2;
  1173. case 2:
  1174. $decoded .= substr($this->body_buffer, $position, $equal - $position);
  1175. if(!strcmp($this->body_buffer[$next],"\n"))
  1176. $position = $end;
  1177. else
  1178. $position = $equal;
  1179. break 2;
  1180. }
  1181. if(!strcmp(substr($this->body_buffer, $next, 2), $break="\r\n")
  1182. || !strcmp($this->body_buffer[$next], $break="\n")
  1183. || !strcmp($this->body_buffer[$next], $break="\r"))
  1184. {
  1185. $decoded .= substr($this->body_buffer, $position, $equal - $position);
  1186. $position = $next + strlen($break);
  1187. continue;
  1188. }
  1189. $decoded .= substr($this->body_buffer, $position, $equal - $position);
  1190. $h = HexDec($hex=strtolower(substr($this->body_buffer, $next, 2)));
  1191. if(strcmp(sprintf('%02x', $h), $hex))
  1192. {
  1193. if(!$this->SetPositionedWarning('the body specified an invalid quoted-printable encoded character', $this->body_offset + $next))
  1194. return(0);
  1195. $decoded.='=';
  1196. $position=$next;
  1197. }
  1198. else
  1199. {
  1200. $decoded .= Chr($h);
  1201. $position = $equal + 3;
  1202. }
  1203. }
  1204. if(strlen($decoded)==0)
  1205. {
  1206. $this->body_buffer_position = $position;
  1207. return(1);
  1208. }
  1209. $part['Data'] = $decoded;
  1210. $this->body_buffer = substr($this->body_buffer, $position);
  1211. $this->body_buffer_position = 0;
  1212. $this->body_offset += $position;
  1213. }
  1214. elseif(IsSet($this->headers['Base64']))
  1215. {
  1216. $part['Data'] = base64_decode($this->body_buffer_position ? substr($this->body_buffer,$this->body_buffer_position) : $this->body_buffer);
  1217. $this->body_offset += strlen($this->body_buffer) - $this->body_buffer_position;
  1218. $this->body_buffer_position = 0;
  1219. $this->body_buffer = '';
  1220. }
  1221. else
  1222. {
  1223. $part['Data'] = substr($this->body_buffer, $this->body_buffer_position);
  1224. $this->body_buffer_position = 0;
  1225. $this->body_buffer = '';
  1226. }
  1227. }
  1228. break;
  1229. }
  1230. $this->parts[]=$part;
  1231. return(1);
  1232. }
  1233. Function DecodeStream($parameters, &$end_of_message, &$decoded)
  1234. {
  1235. $end_of_message = 1;
  1236. $state = MIME_MESSAGE_START;
  1237. for(;;)
  1238. {
  1239. if(!$this->GetPart($part, $end))
  1240. return(0);
  1241. if($end)
  1242. {
  1243. if(IsSet($parameters['File']))
  1244. {
  1245. $end_of_data = feof($this->file);
  1246. if($end_of_data)
  1247. break;
  1248. $data = @fread($this->file, $this->message_buffer_length);
  1249. if(GetType($data)!='string')
  1250. return($this->SetPHPError('could not read the message file', $php_errormsg));
  1251. $end_of_data = feof($this->file);
  1252. }
  1253. else
  1254. {
  1255. $end_of_data=($this->position>=strlen($parameters['Data']));
  1256. if($end_of_data)
  1257. break;
  1258. $data = substr($parameters['Data'], $this->position, $this->message_buffer_length);
  1259. $this->position += strlen($data);
  1260. $end_of_data = ($this->position >= strlen($parameters['Data']));
  1261. }
  1262. if(!$this->Parse($data, $end_of_data))
  1263. return(0);
  1264. continue;
  1265. }
  1266. $type = $part['Type'];
  1267. switch($state)
  1268. {
  1269. case MIME_MESSAGE_START:
  1270. switch($type)
  1271. {
  1272. case 'MessageStart':
  1273. $decoded=array(
  1274. 'Headers'=>array(),
  1275. 'Parts'=>array()
  1276. );
  1277. $end_of_message = 0;
  1278. $state = MIME_MESSAGE_GET_HEADER_NAME;
  1279. continue 3;
  1280. case 'MessageEnd':
  1281. return($this->SetPositionedWarning('incorrectly ended body part', $part['Position']));
  1282. }
  1283. break;
  1284. case MIME_MESSAGE_GET_HEADER_NAME:
  1285. switch($type)
  1286. {
  1287. case 'HeaderName':
  1288. $header = strtolower($part['Name']);
  1289. $state = MIME_MESSAGE_GET_HEADER_VALUE;
  1290. continue 3;
  1291. case 'BodyStart':
  1292. $state = MIME_MESSAGE_GET_BODY;
  1293. $part_number = 0;
  1294. continue 3;
  1295. }
  1296. break;
  1297. case MIME_MESSAGE_GET_HEADER_VALUE:
  1298. switch($type)
  1299. {
  1300. case 'HeaderValue':
  1301. $value = trim($part['Value']);
  1302. if(!IsSet($decoded['Headers'][$header]))
  1303. {
  1304. $h = 0;
  1305. $decoded['Headers'][$header]=$value;
  1306. if($this->extract_addresses
  1307. && IsSet($this->address_headers[$header]))
  1308. $decoded['HeaderPositions'][$header] = $part['Position'];
  1309. }
  1310. elseif(GetType($decoded['Headers'][$header])=='string')
  1311. {
  1312. $h = 1;
  1313. $decoded['Headers'][$header]=array($decoded['Headers'][$header], $value);
  1314. }
  1315. else
  1316. {
  1317. $h = count($decoded['Headers'][$header]);
  1318. $decoded['Headers'][$header][]=$value;
  1319. }
  1320. if(IsSet($part['Decoded'])
  1321. && (count($part['Decoded'])>1
  1322. || strcmp($part['Decoded'][0]['Encoding'],'ASCII')
  1323. || strcmp($value, trim($part['Decoded'][0]['Value']))))
  1324. {
  1325. $p=$part['Decoded'];
  1326. $p[0]['Value']=ltrim($p[0]['Value']);
  1327. $last=count($p)-1;
  1328. $p[$last]['Value']=rtrim($p[$last]['Value']);
  1329. $decoded['DecodedHeaders'][$header][$h]=$p;
  1330. }
  1331. switch($header)
  1332. {
  1333. case 'content-disposition:':
  1334. $filename='filename';
  1335. break;
  1336. case 'content-type:':
  1337. if(!IsSet($decoded['FileName']))
  1338. {
  1339. $filename='name';
  1340. break;
  1341. }
  1342. default:
  1343. $filename='';
  1344. break;
  1345. }
  1346. if(strlen($filename))
  1347. {
  1348. if(IsSet($decoded['DecodedHeaders'][$header][$h])
  1349. && count($decoded['DecodedHeaders'][$header][$h]) == 1)
  1350. {
  1351. $value = $decoded['DecodedHeaders'][$header][$h][0]['Value'];
  1352. $encoding = $decoded['DecodedHeaders'][$header][$h][0]['Encoding'];
  1353. }
  1354. else
  1355. $encoding = '';
  1356. $this->ParseStructuredHeader($value, $type, $header_parameters, $character_sets, $languages);
  1357. if(IsSet($header_parameters[$filename]))
  1358. {
  1359. $decoded['FileName']=$header_parameters[$filename];
  1360. if(IsSet($character_sets[$filename])
  1361. && strlen($character_sets[$filename]))
  1362. $decoded['FileNameCharacterSet']=$character_sets[$filename];
  1363. if(IsSet($character_sets['language'])
  1364. && strlen($character_sets['language']))
  1365. $decoded['FileNameCharacterSet']=$character_sets[$filename];
  1366. if(!IsSet($decoded['FileNameCharacterSet'])
  1367. && strlen($encoding))
  1368. $decoded['FileNameCharacterSet'] = $encoding;
  1369. if(!strcmp($header, 'content-disposition:'))
  1370. $decoded['FileDisposition']=$type;
  1371. }
  1372. }
  1373. $state = MIME_MESSAGE_GET_HEADER_NAME;
  1374. continue 3;
  1375. }
  1376. break;
  1377. case MIME_MESSAGE_GET_BODY:
  1378. switch($type)
  1379. {
  1380. case 'BodyData':
  1381. if(IsSet($parameters['SaveBody']))
  1382. {
  1383. if(!IsSet($decoded['BodyFile']))
  1384. {
  1385. $directory_separator=(defined('DIRECTORY_SEPARATOR') ? DIRECTORY_SEPARATOR : '/');
  1386. $path = (strlen($parameters['SaveBody']) ? ($parameters['SaveBody'].(strcmp($parameters['SaveBody'][strlen($parameters['SaveBody'])-1], $directory_separator) ? $directory_separator : '')) : '').strval($this->body_part_number);
  1387. if(!($this->body_file = fopen($path, 'wb')))
  1388. return($this->SetPHPError('could not create file '.$path.' to save the message body part', $php_errormsg));
  1389. $decoded['BodyFile'] = $path;
  1390. $decoded['BodyPart'] = $this->body_part_number;
  1391. $decoded['BodyLength'] = 0;
  1392. $this->body_part_number++;
  1393. }
  1394. if(strlen($part['Data'])
  1395. && !fwrite($this->body_file, $part['Data']))
  1396. {
  1397. $this->SetPHPError('could not save the message body part to file '.$decoded['BodyFile'], $php_errormsg);
  1398. fclose($this->body_file);
  1399. @unlink($decoded['BodyFile']);
  1400. return(0);
  1401. }
  1402. }
  1403. elseif(IsSet($parameters['SkipBody'])
  1404. && $parameters['SkipBody'])
  1405. {
  1406. if(!IsSet($decoded['BodyPart']))
  1407. {
  1408. $decoded['BodyPart'] = $this->body_part_number;
  1409. $decoded['BodyLength'] = 0;
  1410. $this->body_part_number++;
  1411. }
  1412. }
  1413. else
  1414. {
  1415. if(IsSet($decoded['Body']))
  1416. $decoded['Body'].=$part['Data'];
  1417. else
  1418. {
  1419. $decoded['Body']=$part['Data'];
  1420. $decoded['BodyPart'] = $this->body_part_number;
  1421. $decoded['BodyLength'] = 0;
  1422. $this->body_part_number++;
  1423. }
  1424. }
  1425. $decoded['BodyLength'] += strlen($part['Data']);
  1426. continue 3;
  1427. case 'StartPart':
  1428. if(!$this->DecodeStream($parameters, $end_of_part, $decoded_part))
  1429. return(0);
  1430. $decoded['Parts'][$part_number]=$decoded_part;
  1431. $part_number++;
  1432. $state = MIME_MESSAGE_GET_BODY_PART;
  1433. continue 3;
  1434. case 'MessageEnd':
  1435. if(IsSet($decoded['BodyFile']))
  1436. fclose($this->body_file);
  1437. return(1);
  1438. }
  1439. break;
  1440. case MIME_MESSAGE_GET_BODY_PART:
  1441. switch($type)
  1442. {
  1443. case 'EndPart':
  1444. $state = MIME_MESSAGE_GET_BODY;
  1445. continue 3;
  1446. }
  1447. break;
  1448. }
  1449. return($this->SetError('unexpected decoded message part type '.$type.' in state '.$state));
  1450. }
  1451. return(1);
  1452. }
  1453. /* Public functions */
  1454. Function Parse($data, $end)
  1455. {
  1456. if(strlen($this->error))
  1457. return(0);
  1458. if($this->state==MIME_PARSER_END)
  1459. return($this->SetError('the parser already reached the end'));
  1460. $length = strlen($data);
  1461. if($this->track_lines
  1462. && $length)
  1463. {
  1464. $line = $this->last_line;
  1465. $position = 0;
  1466. if($this->last_carriage_return)
  1467. {
  1468. if($data[0] == "\n")
  1469. ++$position;
  1470. $this->lines[++$line] = $this->line_offset + $position;
  1471. $this->last_carriage_return = 0;
  1472. }
  1473. while($position < $length)
  1474. {
  1475. $position += strcspn($data, "\r\n", $position) ;
  1476. if($position >= $length)
  1477. break;
  1478. if($data[$position] == "\r")
  1479. {
  1480. ++$position;
  1481. if($position >= $length)
  1482. {
  1483. $this->last_carriage_return = 1;
  1484. break;
  1485. }
  1486. if($data[$position] == "\n")
  1487. ++$position;
  1488. $this->lines[++$line] = $this->line_offset + $position;
  1489. }
  1490. else
  1491. {
  1492. ++$position;
  1493. $this->lines[++$line] = $this->line_offset + $position;
  1494. }
  1495. }
  1496. $this->last_line = $line;
  1497. $this->line_offset += $length;
  1498. }
  1499. $this->buffer .= $data;
  1500. do
  1501. {
  1502. Unset($part);
  1503. if(!$this->ParsePart($end, $part, $need_more_data))
  1504. return(0);
  1505. if(IsSet($part)
  1506. && !$this->DecodePart($part))
  1507. return(0);
  1508. }
  1509. while(!$need_more_data
  1510. && $this->state!=MIME_PARSER_END);
  1511. if($end
  1512. && $this->state!=MIME_PARSER_END)
  1513. return($this->SetError('reached a premature end of data'));
  1514. if($this->buffer_position>0)
  1515. {
  1516. $this->offset += $this->buffer_position;
  1517. $this->buffer = substr($this->buffer, $this->buffer_position);
  1518. $this->buffer_position = 0;
  1519. }
  1520. return(1);
  1521. }
  1522. Function ParseFile($file)
  1523. {
  1524. if(strlen($this->error))
  1525. return(0);
  1526. if(!($stream = @fopen($file, 'r')))
  1527. return($this->SetPHPError('Could not open the file '.$file, $php_errormsg));
  1528. for($end = 0;!$end;)
  1529. {
  1530. if(!($data = @fread($stream, $this->message_buffer_length)))
  1531. {
  1532. $this->SetPHPError('Could not read the file '.$file, $php_errormsg);
  1533. fclose($stream);
  1534. return(0);
  1535. }
  1536. $end=feof($stream);
  1537. if(!$this->Parse($data, $end))
  1538. {
  1539. fclose($stream);
  1540. return(0);
  1541. }
  1542. }
  1543. fclose($stream);
  1544. return(1);
  1545. }
  1546. Function GetPart(&$part, &$end)
  1547. {
  1548. $end = ($this->part_position >= count($this->parts));
  1549. if($end)
  1550. {
  1551. if($this->part_position)
  1552. {
  1553. $this->part_position = 0;
  1554. $this->parts = array();
  1555. }
  1556. }
  1557. else
  1558. {
  1559. $part = $this->parts[$this->part_position];
  1560. $this->part_position ++;
  1561. }
  1562. return(1);
  1563. }
  1564. /*
  1565. {metadocument}
  1566. <function>
  1567. <name>Decode</name>
  1568. <type>BOOLEAN</type>
  1569. <documentation>
  1570. <purpose>Parse and decode message data and retrieve its structure.</purpose>
  1571. <usage>Pass an array to the <argumentlink>
  1572. <function>Decode</function>
  1573. <argument>parameters</argument>
  1574. </argumentlink>
  1575. parameter to define whether the message data should be read and
  1576. parsed from a file or a data string, as well additional parsing
  1577. options. The <argumentlink>
  1578. <function>Decode</function>
  1579. <argument>decoded</argument>
  1580. </argumentlink> returns the
  1581. data structure of the parsed messages.</usage>
  1582. <returnvalue>This function returns <booleanvalue>1</booleanvalue> if
  1583. the specified message data is parsed successfully. Otherwise,
  1584. check the variables <variablelink>error</variablelink> and
  1585. <variablelink>error_position</variablelink> to determine what
  1586. error occurred and the relevant message position.</returnvalue>
  1587. </documentation>
  1588. <argument>
  1589. <name>parameters</name>
  1590. <type>HASH</type>
  1591. <documentation>
  1592. <purpose>Associative array to specify parameters for the message
  1593. data parsing and decoding operation. Here follows the list of
  1594. supported parameters that should be used as indexes of the
  1595. array:<paragraphbreak />
  1596. <tt>File</tt><paragraphbreak />
  1597. Name of the file from which the message data will be read. It
  1598. may be the name of a file stream or a remote URL, as long as
  1599. your PHP installation is configured to allow accessing remote
  1600. files with the <tt>fopen()</tt> function.<paragraphbreak />
  1601. <tt>Data</tt><paragraphbreak />
  1602. String that specifies the message data. This should be used
  1603. as alternative data source for passing data available in memory,
  1604. like for instance messages stored in a database that was queried
  1605. dynamically and the message data was fetched into a string
  1606. variable.<paragraphbreak />
  1607. <tt>SaveBody</tt><paragraphbreak />
  1608. If this parameter is specified, the message body parts are saved
  1609. to files. The path of the directory where the files are saved is
  1610. defined by this parameter value. The information about the
  1611. message body part structure is returned by the <argumentlink>
  1612. <function>Decode</function>
  1613. <argument>decoded</argument>
  1614. </argumentlink> argument, but it just returns the body data part
  1615. file name instead of the actual body data. It is recommended for
  1616. retrieving messages larger than the available memory. The names
  1617. of the body part files are numbers starting from
  1618. <stringvalue>1</stringvalue>.<paragraphbreak />
  1619. <tt>SkipBody</tt><paragraphbreak />
  1620. If this parameter is set to <booleanvalue>1</booleanvalue>, the
  1621. message body parts are skipped. This means the information about
  1622. the message body part structure is returned by the <argumentlink>
  1623. <function>Decode</function>
  1624. <argument>decoded</argument>
  1625. </argumentlink> but it does not return any body data. It is
  1626. recommended just for parsing messages without the need to
  1627. retrieve the message body part data.</purpose>
  1628. </documentation>
  1629. </argument>
  1630. <argument>
  1631. <name>decoded</name>
  1632. <type>ARRAY</type>
  1633. <out />
  1634. <documentation>
  1635. <purpose>Retrieve the structure of the parsed message headers and
  1636. body data.<paragraphbreak />
  1637. The argument is used to return by reference an array of message
  1638. structure definitions. Each array entry refers to the structure
  1639. of each message that is found and parsed successfully.<paragraphbreak />
  1640. Each message entry consists of an associative array with several
  1641. entries that describe the message structure. Here follows the
  1642. list of message structure entries names and the meaning of the
  1643. respective values:<paragraphbreak />
  1644. <tt>Headers</tt><paragraphbreak />
  1645. Associative array that returns the list of all the message
  1646. headers. The array entries are the header names mapped to
  1647. lower case, including the end colon. The array values are the
  1648. respective header raw values without any start or trailing white
  1649. spaces. Long header values split between multiple message lines
  1650. are gathered in single string without line breaks. If an header
  1651. with the same name appears more than once in the message, the
  1652. respective value is an array with the values of all of the
  1653. header occurrences.<paragraphbreak />
  1654. <tt>DecodedHeaders</tt><paragraphbreak />
  1655. Associative array that returns the list of all the encoded
  1656. message headers when the
  1657. <variablelink>decode_headers</variablelink> variable is set. The
  1658. array entries are the header names mapped to lower case,
  1659. including the end colon. The array values are also arrays that
  1660. list only the occurrences of the header that originally were
  1661. encoded. Each entry of the decoded header array contains more
  1662. associative arrays that describe each part of the decoded
  1663. header. Each of those associative arrays have an entry named
  1664. <tt>Value</tt> that contains the decoded header part value, and
  1665. another entry named <tt>Encoding</tt> that specifies the
  1666. character set encoding of the value in upper case.<paragraphbreak />
  1667. <tt>ExtractedAddresses</tt><paragraphbreak />
  1668. If the <variablelink>extract_addresses</variablelink> variable
  1669. is set to <booleanvalue>1</booleanvalue>, this entry is set to an
  1670. associative array with the addresses found in the headers
  1671. specified by the <variablelink>address_headers</variablelink>
  1672. variable.<paragraphbreak />
  1673. The parsed addresses found on each header are returned as an
  1674. array with the format of the <link>
  1675. <data>addresses</data>
  1676. <url>rfc822_addresses_class.html#argument_ParseAddressList_addresses</url>
  1677. </link> argument of the <link>
  1678. <data>ParseAddressList</data>
  1679. <url>rfc822_addresses_class.html#function_ParseAddressList</url>
  1680. </link> function of the <link>
  1681. <data>RFC 822 addresses</data>
  1682. <url>rfc822_addresses_class.html</url>
  1683. </link> class.<paragraphbreak />
  1684. <tt>Parts</tt><paragraphbreak />
  1685. If this message content type is multipart, this entry is an
  1686. array that describes each of the parts contained in the message
  1687. body. Each message part is described by an associative array
  1688. with the same structure of a complete message
  1689. definition.<paragraphbreak />
  1690. <tt>Body</tt><paragraphbreak />
  1691. String with the decoded data contained in the message body. If
  1692. the <tt>SaveBody</tt> or <tt>SkipBody</tt> parameters are
  1693. defined, the <tt>Body</tt> entry is not set.<paragraphbreak />
  1694. <tt>BodyFile</tt><paragraphbreak />
  1695. Name of the file to which the message body data was saved when
  1696. the <tt>SaveBody</tt> parameter is defined.<paragraphbreak />
  1697. <tt>BodyLength</tt><paragraphbreak />
  1698. Length of the current decoded body part.<paragraphbreak />
  1699. <tt>BodyPart</tt><paragraphbreak />
  1700. Number of the current message body part.<paragraphbreak />
  1701. <tt>FileName</tt><paragraphbreak />
  1702. Name of the file for body parts composed from
  1703. files.<paragraphbreak />
  1704. <tt>FileNameCharacterSet</tt><paragraphbreak />
  1705. Character set encoding for file parts with names that may
  1706. include non-ASCII characters.<paragraphbreak />
  1707. <tt>FileNameLanguage</tt><paragraphbreak />
  1708. Language of file parts with names that may include non-ASCII
  1709. characters.<paragraphbreak />
  1710. <tt>FileDisposition</tt><paragraphbreak />
  1711. Disposition of parts that files. It may be either
  1712. <tt><stringvalue>inline</stringvalue></tt> for file parts to be
  1713. displayed with the message, or
  1714. <tt><stringvalue>attachment</stringvalue></tt> otherwise.</purpose>
  1715. </documentation>
  1716. </argument>
  1717. <do>
  1718. {/metadocument}
  1719. */
  1720. Function Decode($parameters, &$decoded)
  1721. {
  1722. if(IsSet($parameters['File']))
  1723. {
  1724. if(!($this->file = @fopen($parameters['File'], 'r')))
  1725. return($this->SetPHPError('could not open the message file to decode '.$parameters['File'], $php_errormsg));
  1726. }
  1727. elseif(IsSet($parameters['Data']))
  1728. $this->position = 0;
  1729. else
  1730. return($this->SetError('it was not specified a valid message to decode'));
  1731. $this->warnings = $decoded = array();
  1732. $this->ResetParserState();
  1733. $addresses = new rfc822_addresses_class;
  1734. $addresses->ignore_syntax_errors = $this->ignore_syntax_errors;
  1735. for($message = 0; ($success = $this->DecodeStream($parameters, $end_of_message, $decoded_message)) && !$end_of_message; $message++)
  1736. {
  1737. if($this->extract_addresses)
  1738. {
  1739. $headers = $decoded_message['Headers'];
  1740. $positions = (IsSet($decoded_message['HeaderPositions']) ? $decoded_message['HeaderPositions'] : array());
  1741. $th = count($headers);
  1742. for(Reset($headers), $h = 0; $h<$th; Next($headers), ++$h)
  1743. {
  1744. $header = Key($headers);
  1745. if(IsSet($this->address_headers[$header])
  1746. && $this->address_headers[$header])
  1747. {
  1748. $values = (GetType($headers[$header]) == 'array' ? $headers[$header] : array($headers[$header]));
  1749. $p = (GetType($positions[$header]) == 'array' ? $positions[$header] : array($positions[$header]));
  1750. $tv = count($values);
  1751. for($v = 0; $v<$tv; ++$v)
  1752. {
  1753. if($addresses->ParseAddressList($values[$v], $a))
  1754. {
  1755. if($v==0)
  1756. $decoded_message['ExtractedAddresses'][$header] = $a;
  1757. else
  1758. {
  1759. $tl = count($a);
  1760. for($l = 0; $l<$tl; ++$l)
  1761. $decoded_message['ExtractedAddresses'][$header][] = $a[$l];
  1762. }
  1763. $tw = count($addresses->warnings);
  1764. for($w = 0, Reset($addresses->warnings); $w < $tw; Next($addresses->warnings), $w++)
  1765. {
  1766. $warning = Key($addresses->warnings);
  1767. if(!$this->SetPositionedWarning('Address extraction warning from header '.$header.' '.$addresses->warnings[$warning], $warning + $p[$v]))
  1768. return(0);
  1769. }
  1770. }
  1771. elseif(!$this->SetPositionedWarning('Address extraction error from header '.$header.' '.$addresses->error, $addresses->error_position + $p[$v]))
  1772. return(0);
  1773. }
  1774. }
  1775. }
  1776. UnSet($decoded_message['HeaderPositions']);
  1777. }
  1778. $decoded[$message]=$decoded_message;
  1779. }
  1780. if(IsSet($parameters['File']))
  1781. fclose($this->file);
  1782. return($success);
  1783. }
  1784. /*
  1785. {metadocument}
  1786. </do>
  1787. </function>
  1788. {/metadocument}
  1789. */
  1790. Function CopyAddresses($message, &$results, $header)
  1791. {
  1792. if(!IsSet($message['Headers'][$header]))
  1793. return;
  1794. if(!IsSet($message['ExtractedAddresses'][$header]))
  1795. {
  1796. $parser = new rfc822_addresses_class;
  1797. $parser->ignore_syntax_errors = $this->ignore_syntax_errors;
  1798. $values = (GetType($message['Headers'][$header]) == 'array' ? $message['Headers'][$header] : array($message['Headers'][$header]));
  1799. $tv = count($values);
  1800. $addresses = array();
  1801. for($v = 0; $v<$tv; ++$v)
  1802. {
  1803. if($parser->ParseAddressList($values[$v], $a))
  1804. {
  1805. if($v==0)
  1806. $addresses = $a;
  1807. else
  1808. {
  1809. $tl = count($a);
  1810. for($l = 0; $l<$tl; ++$l)
  1811. $addresses[] = $a[$l];
  1812. }
  1813. }
  1814. }
  1815. }
  1816. else
  1817. $addresses = $message['ExtractedAddresses'][$header];
  1818. if(count($addresses))
  1819. $results[ucfirst(substr($header, 0, strlen($header) -1))] = $addresses;
  1820. }
  1821. Function ReadMessageBody($message, &$body, $prefix)
  1822. {
  1823. if(IsSet($message[$prefix]))
  1824. $body = $message[$prefix];
  1825. elseif(IsSet($message[$prefix.'File']))
  1826. {
  1827. $path = $message[$prefix.'File'];
  1828. if(!($file = @fopen($path, 'rb')))
  1829. return($this->SetPHPError('could not open the message body file '.$path, $php_errormsg));
  1830. for($body = '', $end = 0;!$end;)
  1831. {
  1832. if(!($data = @fread($file, $this->message_buffer_length)))
  1833. {
  1834. $this->SetPHPError('Could not open the message body file '.$path, $php_errormsg);
  1835. fclose($stream);
  1836. return(0);
  1837. }
  1838. $end=feof($file);
  1839. $body.=$data;
  1840. }
  1841. fclose($file);
  1842. }
  1843. else
  1844. $body = '';
  1845. return(1);
  1846. }
  1847. /*
  1848. {metadocument}
  1849. <function>
  1850. <name>Analyze</name>
  1851. <type>BOOLEAN</type>
  1852. <documentation>
  1853. <purpose>Analyze a parsed message to describe its contents.</purpose>
  1854. <usage>Pass an array to the <argumentlink>
  1855. <function>Analyze</function>
  1856. <argument>message</argument>
  1857. </argumentlink>
  1858. parameter with the decoded message array structure returned by the
  1859. <functionlink>Decode</functionlink> function. The <argumentlink>
  1860. <function>Analyze</function>
  1861. <argument>results</argument>
  1862. </argumentlink> returns details about the type of message that was
  1863. analyzed and its contents.</usage>
  1864. <returnvalue>This function returns <booleanvalue>1</booleanvalue> if
  1865. the specified message is analyzed successfully. Otherwise,
  1866. check the variables <variablelink>error</variablelink> and
  1867. <variablelink>error_position</variablelink> to determine what
  1868. error occurred.</returnvalue>
  1869. </documentation>
  1870. <argument>
  1871. <name>message</name>
  1872. <type>HASH</type>
  1873. <documentation>
  1874. <purpose>Pass an associative array with the definition of an
  1875. individual message returned by the <argumentlink>
  1876. <function>Decode</function>
  1877. <argument>decoded</argument>
  1878. </argumentlink> argument of the
  1879. <functionlink>Decode</functionlink> function..</purpose>
  1880. </documentation>
  1881. </argument>
  1882. <argument>
  1883. <name>results</name>
  1884. <type>HASH</type>
  1885. <out />
  1886. <documentation>
  1887. <purpose>Returns an associative array with the results of the
  1888. analysis. Some types of entries are returned for all types of
  1889. analyzed messages. Other entries are specific to each type of
  1890. message.<paragraphbreak />
  1891. <tt>Type</tt><paragraphbreak />
  1892. Type of message that was analyzed. Currently it supports the
  1893. types: <tt>binary</tt>, <tt>text</tt>, <tt>html</tt>,
  1894. <tt>video</tt>, <tt>image</tt>, <tt>audio</tt>, <tt>zip</tt>,
  1895. <tt>pdf</tt>, <tt>postscript</tt>, <tt>ms-word</tt>,
  1896. <tt>ms-excel</tt>, <tt>ms-powerpoint</tt>, <tt>ms-tnef</tt>,
  1897. <tt>odf-writer</tt>, <tt>signature</tt>, <tt>report-type</tt>,
  1898. <tt>delivery-status</tt> and <tt>message</tt>.<paragraphbreak />
  1899. <tt>SubType</tt><paragraphbreak />
  1900. Name of the variant of the message type format.<paragraphbreak />
  1901. <tt>Description</tt><paragraphbreak />
  1902. Human readable description in English of the message type.<paragraphbreak />
  1903. <paragraphbreak />
  1904. <paragraphbreak />
  1905. <paragraphbreak />
  1906. <b>From message headers:</b><paragraphbreak />
  1907. <tt>Encoding</tt><paragraphbreak />
  1908. Character set encoding of the message part.<paragraphbreak />
  1909. <tt>Subject</tt><paragraphbreak />
  1910. The message subject.<paragraphbreak />
  1911. <tt>SubjectEncoding</tt><paragraphbreak />
  1912. Character set encoding of the message subject.<paragraphbreak />
  1913. <tt>Date</tt><paragraphbreak />
  1914. The message date.<paragraphbreak />
  1915. <tt>From</tt><paragraphbreak />
  1916. <tt>To</tt><paragraphbreak />
  1917. <tt>Cc</tt><paragraphbreak />
  1918. <tt>Bcc</tt><paragraphbreak />
  1919. Array of e-mail addresses found in the <tt>From</tt>,
  1920. <tt>To</tt>, <tt>Cc</tt>, <tt>Bcc</tt>.<paragraphbreak />
  1921. Each of the entries consists of an associative array with an
  1922. entry named <tt>address</tt> with the e-mail address and
  1923. optionally another named <tt>name</tt> with the associated
  1924. name.<paragraphbreak />
  1925. <paragraphbreak />
  1926. <paragraphbreak />
  1927. <b>For content message parts:</b><paragraphbreak />
  1928. <paragraphbreak />
  1929. <tt>Data</tt><paragraphbreak />
  1930. String of data of the message part.<paragraphbreak />
  1931. <tt>DataFile</tt><paragraphbreak />
  1932. File with data of the message part.<paragraphbreak />
  1933. <tt>DataLength</tt><paragraphbreak />
  1934. Length of the data of the message part.<paragraphbreak />
  1935. <paragraphbreak />
  1936. <paragraphbreak />
  1937. <paragraphbreak />
  1938. <b>For message with embedded files:</b><paragraphbreak />
  1939. <paragraphbreak />
  1940. <tt>FileName</tt><paragraphbreak />
  1941. Original name of the file.<paragraphbreak />
  1942. <tt>ContentID</tt><paragraphbreak />
  1943. Content identifier of the file to be used in references from
  1944. other message parts.<paragraphbreak />
  1945. For instance, an HTML message may reference images embedded in
  1946. the message using URLs that start with the
  1947. <stringvalue>cid:</stringvalue> followed by the content
  1948. identifier of the embedded image file part.<paragraphbreak />
  1949. <tt>Disposition</tt><paragraphbreak />
  1950. Information of whether the embedded file should be displayed
  1951. inline when the message is presented, or it is an attachment
  1952. file.<paragraphbreak />
  1953. <paragraphbreak />
  1954. <paragraphbreak />
  1955. <b>For composite message:</b><paragraphbreak />
  1956. <paragraphbreak />
  1957. <tt>Attachments</tt><paragraphbreak />
  1958. List of files attached to the message.<paragraphbreak />
  1959. <tt>Alternative</tt><paragraphbreak />
  1960. List of alternative message parts that can be displayed if the
  1961. main message type is not supported by the program displaying
  1962. the message.<paragraphbreak />
  1963. <tt>Related</tt><paragraphbreak />
  1964. List of message parts related with the main message type.<paragraphbreak />
  1965. It may list for instance embedded images or CSS files related
  1966. with an HTML message type.<paragraphbreak />
  1967. <paragraphbreak />
  1968. <paragraphbreak />
  1969. <b>For bounced messages or other types of delivery status report
  1970. messages:</b><paragraphbreak />
  1971. <paragraphbreak />
  1972. <tt>Recipients</tt><paragraphbreak />
  1973. List of recipients of the original message.<paragraphbreak />
  1974. Each entry contains an associative array that may have the
  1975. entries: <tt>Recipient</tt> with the original recipient address,
  1976. <tt>Action</tt> with the name action that triggered the delivery
  1977. status report, <tt>Status</tt> with the code of the status of
  1978. the message delivery.<paragraphbreak />
  1979. <tt>Response</tt><paragraphbreak />
  1980. Human readable response sent by the server the originated the
  1981. report.<paragraphbreak />
  1982. </purpose>
  1983. </documentation>
  1984. </argument>
  1985. <do>
  1986. {/metadocument}
  1987. */
  1988. Function Analyze($message, &$results)
  1989. {
  1990. $results = array();
  1991. if(!IsSet($message['Headers']['content-type:']))
  1992. $content_type = 'text/plain';
  1993. elseif(count($message['Headers']['content-type:']) == 1)
  1994. $content_type = $message['Headers']['content-type:'];
  1995. else
  1996. {
  1997. if(!$this->SetPositionedWarning('message contains multiple content-type headers', 0))
  1998. return(0);
  1999. $content_type = $message['Headers']['content-type:'][0];
  2000. }
  2001. $disposition = $this->ParseParameters($content_type, $content_type, $parameters, 'disposition');
  2002. $type = $this->Tokenize($content_type, '/');
  2003. $sub_type = $this->Tokenize(';');
  2004. $copy_body = 1;
  2005. $tolerate_unrecognized = 1;
  2006. switch($type)
  2007. {
  2008. case 'multipart':
  2009. $tolerate_unrecognized = 0;
  2010. $copy_body = 0;
  2011. $lp = count($message['Parts']);
  2012. if($lp == 0)
  2013. return($this->SetError($this->decode_bodies ? 'No parts were found in the '.$content_type.' part message' : 'It is not possible to analyze multipart messages without parsing the contained message parts. Please set the decode_bodies variable to 1 before parsing the message'));
  2014. $parts = array();
  2015. for($p = 0; $p < $lp; ++$p)
  2016. {
  2017. if(!$this->Analyze($message['Parts'][$p], $parts[$p]))
  2018. return(0);
  2019. }
  2020. switch($sub_type)
  2021. {
  2022. case 'alternative':
  2023. $p = $lp;
  2024. $results = $parts[--$p];
  2025. for(--$p ; $p >=0 ; --$p)
  2026. $results['Alternative'][] = $parts[$p];
  2027. break;
  2028. case 'related':
  2029. $results = $parts[0];
  2030. for($p = 1; $p < $lp; ++$p)
  2031. $results['Related'][] = $parts[$p];
  2032. break;
  2033. case 'mixed':
  2034. $results = $parts[0];
  2035. for($p = 1; $p < $lp; ++$p)
  2036. $results['Attachments'][] = $parts[$p];
  2037. break;
  2038. case 'report':
  2039. if(IsSet($parameters['report-type']))
  2040. {
  2041. switch($parameters['report-type'])
  2042. {
  2043. case 'delivery-status':
  2044. for($p = 1; $p < $lp; ++$p)
  2045. {
  2046. if(!strcmp($parts[$p]['Type'], $parameters['report-type']))
  2047. {
  2048. $results = $parts[$p];
  2049. break;
  2050. }
  2051. }
  2052. if(!$this->ReadMessageBody($parts[0], $body, 'Data'))
  2053. return(0);
  2054. if(strlen($body))
  2055. $results['Response'] = $body;
  2056. break;
  2057. }
  2058. }
  2059. $results['Type'] = $parameters['report-type'];
  2060. break;
  2061. case 'signed':
  2062. if($lp != 2)
  2063. return($this->SetError('this '.$content_type.' message does not have just 2 parts'));
  2064. if(strcmp($parts[1]['Type'], 'signature'))
  2065. {
  2066. $this->SetErrorWithContact('this '.$content_type.' message does not contain a signature');
  2067. $this->error = '';
  2068. }
  2069. $results = $parts[0];
  2070. $results['Signature'] = $parts[1];
  2071. break;
  2072. case 'appledouble':
  2073. if($lp != 2)
  2074. return($this->SetError('this '.$content_type.' message does not have just 2 parts'));
  2075. if(strcmp($parts[0]['Type'], 'applefile'))
  2076. {
  2077. $this->SetErrorWithContact('this '.$content_type.' message does not contain an Apple file header');
  2078. $this->error = '';
  2079. }
  2080. $results = $parts[1];
  2081. $results['AppleFileHeader'] = $parts[0];
  2082. break;
  2083. }
  2084. break;
  2085. case 'text':
  2086. switch($sub_type)
  2087. {
  2088. case 'plain':
  2089. $results['Type'] = 'text';
  2090. $results['Description'] = 'Text message';
  2091. break;
  2092. case 'html':
  2093. $results['Type'] = 'html';
  2094. $results['Description'] = 'HTML message';
  2095. break;
  2096. default:
  2097. $results['Type'] = $type;
  2098. $results['SubType'] = $sub_type;
  2099. $results['Description'] = 'Text file in the '.strtoupper($sub_type).' format';
  2100. break;
  2101. }
  2102. break;
  2103. case 'video':
  2104. $results['Type'] = $type;
  2105. $results['SubType'] = $sub_type;
  2106. $results['Description'] = 'Video file in the '.strtoupper($sub_type).' format';
  2107. break;
  2108. case 'image':
  2109. $results['Type'] = $type;
  2110. $results['SubType'] = $sub_type;
  2111. $results['Description'] = 'Image file in the '.strtoupper($sub_type).' format';
  2112. break;
  2113. case 'audio':
  2114. $results['Type'] = $type;
  2115. $results['SubType'] = $sub_type;
  2116. $results['Description'] = 'Audio file in the '.strtoupper($sub_type).' format';
  2117. break;
  2118. case 'application':
  2119. switch($sub_type)
  2120. {
  2121. case 'octet-stream':
  2122. case 'x-msdownload':
  2123. $results['Type'] = 'binary';
  2124. $results['Description'] = 'Binary file';
  2125. break;
  2126. case 'pdf':
  2127. $results['Type'] = $sub_type;
  2128. $results['Description'] = 'Document in PDF format';
  2129. break;
  2130. case 'postscript':
  2131. $results['Type'] = $sub_type;
  2132. $results['Description'] = 'Document in Postscript format';
  2133. break;
  2134. case 'msword':
  2135. $results['Type'] = 'ms-word';
  2136. $results['Description'] = 'Word processing document in Microsoft Word format';
  2137. break;
  2138. case 'vnd.ms-powerpoint':
  2139. $results['Type'] = 'ms-powerpoint';
  2140. $results['Description'] = 'Presentation in Microsoft PowerPoint format';
  2141. break;
  2142. case 'vnd.ms-excel':
  2143. $results['Type'] = 'ms-excel';
  2144. $results['Description'] = 'Spreadsheet in Microsoft Excel format';
  2145. break;
  2146. case 'x-compressed':
  2147. if(!IsSet($parameters['name'])
  2148. || GetType($dot = strpos($parameters['name'], '.'))!='integer'
  2149. || strcmp($extension = strtolower(substr($parameters['name'], $dot + 1)), 'zip'))
  2150. break;
  2151. case 'zip':
  2152. case 'x-zip':
  2153. case 'x-zip-compressed':
  2154. $results['Type'] = 'zip';
  2155. $results['Description'] = 'ZIP archive with compressed files';
  2156. break;
  2157. case 'ms-tnef':
  2158. $results['Type'] = $sub_type;
  2159. $results['Description'] = 'Microsoft Exchange data usually sent by Microsoft Outlook';
  2160. break;
  2161. case 'pgp-signature':
  2162. $results['Type'] = 'signature';
  2163. $results['SubType'] = $sub_type;
  2164. $results['Description'] = 'Message signature for PGP';
  2165. break;
  2166. case 'x-pkcs7-signature':
  2167. case 'pkcs7-signature':
  2168. $results['Type'] = 'signature';
  2169. $results['SubType'] = $sub_type;
  2170. $results['Description'] = 'PKCS message signature';
  2171. break;
  2172. case 'vnd.oasis.opendocument.text':
  2173. $results['Type'] = 'odf-writer';
  2174. $results['Description'] = 'Word processing document in ODF text format used by OpenOffice Writer';
  2175. break;
  2176. case 'applefile':
  2177. $results['Type'] = 'applefile';
  2178. $results['Description'] = 'Apple file resource header';
  2179. break;
  2180. }
  2181. break;
  2182. case 'message':
  2183. $tolerate_unrecognized = 0;
  2184. switch($sub_type)
  2185. {
  2186. case 'delivery-status':
  2187. $results['Type'] = $sub_type;
  2188. $results['Description'] = 'Notification of the status of delivery of a message';
  2189. if(!$this->ReadMessageBody($message, $body, 'Body'))
  2190. return(0);
  2191. if(($l = strlen($body)))
  2192. {
  2193. $position = 0;
  2194. $this->ParseHeaderString($body, $position, $headers);
  2195. $recipients = array();
  2196. for(;$position<$l;)
  2197. {
  2198. $this->ParseHeaderString($body, $position, $headers);
  2199. if(count($headers))
  2200. {
  2201. $r = count($recipients);
  2202. if(IsSet($headers['action']))
  2203. $recipients[$r]['Action'] = $headers['action'];
  2204. if(IsSet($headers['status']))
  2205. $recipients[$r]['Status'] = $headers['status'];
  2206. if(IsSet($headers['original-recipient']))
  2207. {
  2208. strtok($headers['original-recipient'], ';');
  2209. $recipients[$r]['Address'] = trim(strtok(''));
  2210. }
  2211. elseif(IsSet($headers['final-recipient']))
  2212. {
  2213. strtok($headers['final-recipient'], ';');
  2214. $recipients[$r]['Address'] = trim(strtok(''));
  2215. }
  2216. }
  2217. }
  2218. $results['Recipients'] = $recipients;
  2219. }
  2220. $copy_body = 0;
  2221. break;
  2222. case 'rfc822':
  2223. $results['Type'] = 'message';
  2224. $results['Description'] = 'E-mail message';
  2225. break;
  2226. }
  2227. break;
  2228. default:
  2229. $tolerate_unrecognized = 0;
  2230. break;
  2231. }
  2232. if(!IsSet($results['Type']))
  2233. {
  2234. $this->SetErrorWithContact($content_type.' message parts are not yet recognized');
  2235. $results['Type'] = $this->error;
  2236. $this->error = '';
  2237. }
  2238. if(IsSet($parameters['charset']))
  2239. $results['Encoding'] = strtolower($parameters['charset']);
  2240. if(IsSet($message['Headers']['subject:']))
  2241. {
  2242. if(IsSet($message['DecodedHeaders']['subject:'])
  2243. && count($message['DecodedHeaders']['subject:']) == 1
  2244. && count($message['DecodedHeaders']['subject:'][0]) == 1)
  2245. {
  2246. $results['Subject'] = $message['DecodedHeaders']['subject:'][0][0]['Value'];
  2247. $results['SubjectEncoding'] = strtolower($message['DecodedHeaders']['subject:'][0][0]['Encoding']);
  2248. }
  2249. else
  2250. $results['Subject'] = $message['Headers']['subject:'];
  2251. }
  2252. if(IsSet($message['Headers']['date:']))
  2253. {
  2254. if(IsSet($message['DecodedHeaders']['date:'])
  2255. && count($message['DecodedHeaders']['date:']) == 1
  2256. && count($message['DecodedHeaders']['date:'][0]) == 1)
  2257. $results['Date'] = $message['DecodedHeaders']['date:'][0][0]['Value'];
  2258. else
  2259. $results['Date'] = $message['Headers']['date:'];
  2260. }
  2261. $l = count($this->address_headers);
  2262. for(Reset($this->address_headers), $h = 0; $h<$l; Next($this->address_headers), ++$h)
  2263. $this->CopyAddresses($message, $results, Key($this->address_headers));
  2264. if($copy_body)
  2265. {
  2266. if(IsSet($message['Body']))
  2267. $results['Data'] = $message['Body'];
  2268. elseif(IsSet($message['BodyFile']))
  2269. $results['DataFile'] = $message['BodyFile'];
  2270. elseif(IsSet($message['BodyLength']))
  2271. $results['DataLength'] = $message['BodyLength'];
  2272. if(IsSet($message['FileName']))
  2273. $results['FileName'] = $message['FileName'];
  2274. if(IsSet($message['FileDisposition']))
  2275. $results['FileDisposition'] = $message['FileDisposition'];
  2276. if(IsSet($message['Headers']['content-id:']))
  2277. {
  2278. $content_id = trim($message['Headers']['content-id:']);
  2279. $l = strlen($content_id);
  2280. if(!strcmp($content_id[0], '<')
  2281. && !strcmp($content_id[$l - 1], '>'))
  2282. $results['ContentID'] = substr($content_id, 1, $l - 2);
  2283. }
  2284. }
  2285. return(1);
  2286. }
  2287. /*
  2288. {metadocument}
  2289. </do>
  2290. </function>
  2291. {/metadocument}
  2292. */
  2293. /*
  2294. {metadocument}
  2295. <function>
  2296. <name>GetPositionLine</name>
  2297. <type>BOOLEAN</type>
  2298. <documentation>
  2299. <purpose>Get the line number of the document that corresponds to a
  2300. given position.</purpose>
  2301. <usage>Pass the document offset number as the position to be
  2302. located. Make sure the <variablelink>track_lines</variablelink>
  2303. variable is set to <booleanvalue>1</booleanvalue> before parsing
  2304. the document.</usage>
  2305. <returnvalue>This function returns <booleanvalue>1</booleanvalue> if
  2306. the <variablelink>track_lines</variablelink> variable is set to
  2307. <booleanvalue>1</booleanvalue> and it was given a valid positive
  2308. position number that does not exceed the position of the last
  2309. parsed document line.</returnvalue>
  2310. </documentation>
  2311. <argument>
  2312. <name>position</name>
  2313. <type>INTEGER</type>
  2314. <documentation>
  2315. <purpose>Position of the line to be located.</purpose>
  2316. </documentation>
  2317. </argument>
  2318. <argument>
  2319. <name>line</name>
  2320. <type>INTEGER</type>
  2321. <out />
  2322. <documentation>
  2323. <purpose>Returns the number of the line that corresponds to the
  2324. given document position.</purpose>
  2325. </documentation>
  2326. </argument>
  2327. <argument>
  2328. <name>column</name>
  2329. <type>INTEGER</type>
  2330. <out />
  2331. <documentation>
  2332. <purpose>Returns the number of the column of the line that
  2333. corresponds to the given document position.</purpose>
  2334. </documentation>
  2335. </argument>
  2336. <do>
  2337. {/metadocument}
  2338. */
  2339. Function GetPositionLine($position, &$line, &$column)
  2340. {
  2341. if(!$this->track_lines)
  2342. return($this->SetPositionedError('line positions are not being tracked', $position));
  2343. $bottom = 0;
  2344. $top = count($this->lines) - 1;
  2345. if($position < 0)
  2346. return($this->SetPositionedError('it was not specified a valid position', $position));
  2347. for(;;)
  2348. {
  2349. $line = intval(($bottom + $top) / 2);
  2350. $current = $this->lines[$line];
  2351. if($current < $position)
  2352. $bottom = $line + 1;
  2353. elseif($current > $position)
  2354. $top = $line - 1;
  2355. else
  2356. break;
  2357. if($top < $bottom)
  2358. {
  2359. $line = $top;
  2360. break;
  2361. }
  2362. }
  2363. $column = $position - $this->lines[$line] + 1;
  2364. ++$line;
  2365. return(1);
  2366. }
  2367. /*
  2368. {metadocument}
  2369. </do>
  2370. </function>
  2371. {/metadocument}
  2372. */
  2373. };
  2374. /*
  2375. {metadocument}
  2376. </class>
  2377. {/metadocument}
  2378. */
  2379. ?>