PageRenderTime 60ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 1ms

/_jppf/classes/pop3emailclient/mime_parser.php

https://bitbucket.org/countach/jpphpframework
PHP | 1281 lines | 984 code | 30 blank | 267 comment | 155 complexity | eb97d8fef192819075d204e9bd4b9834 MD5 | raw file
Possible License(s): GPL-2.0, BSD-3-Clause, LGPL-2.1
  1. <?php
  2. /*
  3. * mime_parser.php
  4. *
  5. * @(#) $Id: mime_parser.php,v 1.20 2006/09/11 21:06:36 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. /*
  22. {metadocument}<?xml version="1.0" encoding="ISO-8859-1" ?>
  23. <class>
  24. <package>net.manuellemos.mimeparser</package>
  25. <version>@(#) $Id: mime_parser.php,v 1.20 2006/09/11 21:06:36 mlemos Exp $</version>
  26. <copyright>Copyright Š (C) Manuel Lemos 2006</copyright>
  27. <title>MIME parser</title>
  28. <author>Manuel Lemos</author>
  29. <authoraddress>mlemos-at-acm.org</authoraddress>
  30. <documentation>
  31. <idiom>en</idiom>
  32. <purpose>Parse MIME encapsulated e-mail message data compliant with
  33. the RFC 2822 or aggregated in mbox format.</purpose>
  34. <usage>Use the function <functionlink>Decode</functionlink> function
  35. to retrieve the structure of the messages to be parsed. Adjust its
  36. parameters to tell how to return the decoded body data.
  37. Use the <tt>SaveBody</tt> parameter to make the body parts be saved
  38. to files when the message is larger than the available memory. Use
  39. the <tt>SkipBody</tt> parameter to just retrieve the message
  40. structure without returning the body data.<paragraphbreak />
  41. If the message data is an archive that may contain multiple messages
  42. aggregated in the mbox format, set the variable
  43. <variablelink>mbox</variablelink> to <booleanvalue>1</booleanvalue>.</usage>
  44. </documentation>
  45. {/metadocument}
  46. */
  47. class mime_parser_class
  48. {
  49. /*
  50. {metadocument}
  51. <variable>
  52. <name>error</name>
  53. <type>STRING</type>
  54. <value></value>
  55. <documentation>
  56. <purpose>Store the message that is returned when an error
  57. occurs.</purpose>
  58. <usage>Check this variable to understand what happened when a call to
  59. any of the class functions has failed.<paragraphbreak />
  60. This class uses cumulative error handling. This means that if one
  61. class functions that may fail is called and this variable was
  62. already set to an error message due to a failure in a previous call
  63. to the same or other function, the function will also fail and does
  64. not do anything.<paragraphbreak />
  65. This allows programs using this class to safely call several
  66. functions that may fail and only check the failure condition after
  67. the last function call.<paragraphbreak />
  68. Just set this variable to an empty string to clear the error
  69. condition.</usage>
  70. </documentation>
  71. </variable>
  72. {/metadocument}
  73. */
  74. var $error='';
  75. /*
  76. {metadocument}
  77. <variable>
  78. <name>error_position</name>
  79. <type>INTEGER</type>
  80. <value>-1</value>
  81. <documentation>
  82. <purpose>Point to the position of the message data or file that
  83. refers to the last error that occurred.</purpose>
  84. <usage>Check this variable to determine the relevant position of the
  85. message when a parsing error occurs.</usage>
  86. </documentation>
  87. </variable>
  88. {/metadocument}
  89. */
  90. var $error_position = -1;
  91. /*
  92. {metadocument}
  93. <variable>
  94. <name>mbox</name>
  95. <type>BOOLEAN</type>
  96. <value>0</value>
  97. <documentation>
  98. <purpose>Specify whether the message data to parse is a single RFC
  99. 2822 message or it is an archive that contain multiple messages in
  100. the mbox format.</purpose>
  101. <usage>Set this variable to <booleanvalue>1</booleanvalue> if it is
  102. it is intended to parse an mbox message archive.<br />
  103. mbox archives may contain multiple messages. Each message starts
  104. with the header <tt>From</tt>. Since all valid RFC 2822 headers
  105. must with a colon, the class will fail to parse a mbox archive if
  106. this variable is set to <booleanvalue>0</booleanvalue>.</usage>
  107. </documentation>
  108. </variable>
  109. {/metadocument}
  110. */
  111. var $mbox = 0;
  112. /*
  113. {metadocument}
  114. <variable>
  115. <name>decode_headers</name>
  116. <type>BOOLEAN</type>
  117. <value>1</value>
  118. <documentation>
  119. <purpose>Specify whether the message headers should be decoded.</purpose>
  120. <usage>Set this variable to <booleanvalue>1</booleanvalue> if it is
  121. necessary to decode message headers that may have non-ASCII
  122. characters and use other character set encodings.</usage>
  123. </documentation>
  124. </variable>
  125. {/metadocument}
  126. */
  127. var $decode_headers = 1;
  128. /*
  129. {metadocument}
  130. <variable>
  131. <name>decode_bodies</name>
  132. <type>BOOLEAN</type>
  133. <value>1</value>
  134. <documentation>
  135. <purpose>Specify whether the message body parts should be decoded.</purpose>
  136. <usage>Set this variable to <booleanvalue>1</booleanvalue> if it is
  137. necessary to parse the message bodies and extract its part
  138. structure.</usage>
  139. </documentation>
  140. </variable>
  141. {/metadocument}
  142. */
  143. var $decode_bodies = 1;
  144. /* Private variables */
  145. var $state = MIME_PARSER_START;
  146. var $buffer = '';
  147. var $buffer_position = 0;
  148. var $offset = 0;
  149. var $parts = array();
  150. var $part_position = 0;
  151. var $headers = array();
  152. var $body_parser;
  153. var $body_parser_state = MIME_PARSER_BODY_DONE;
  154. var $body_buffer = '';
  155. var $body_buffer_position = 0;
  156. var $body_offset = 0;
  157. var $current_header = '';
  158. var $file;
  159. var $body_file;
  160. var $position = 0;
  161. var $body_part_number = 1;
  162. /* Private functions */
  163. Function SetError($error)
  164. {
  165. $this->error = $error;
  166. return(0);
  167. }
  168. Function SetPositionedError($error, $position)
  169. {
  170. $this->error_position = $position;
  171. return($this->SetError($error));
  172. }
  173. Function SetPHPError($error, &$php_error_message)
  174. {
  175. if(IsSet($php_error_message)
  176. && strlen($php_error_message))
  177. $error .= ': '.$php_error_message;
  178. return($this->SetError($error));
  179. }
  180. Function ParsePart($end, &$part, &$need_more_data)
  181. {
  182. $need_more_data = 0;
  183. switch($this->state)
  184. {
  185. case MIME_PARSER_START:
  186. $part=array(
  187. 'Type'=>'MessageStart',
  188. 'Position'=>$this->offset + $this->buffer_position
  189. );
  190. $this->state = MIME_PARSER_HEADER;
  191. break;
  192. case MIME_PARSER_HEADER:
  193. if(GetType($line_break=strpos($this->buffer, $break="\r\n", $this->buffer_position))=='integer'
  194. || GetType($line_break=strpos($this->buffer, $break="\n", $this->buffer_position))=='integer'
  195. || GetType($line_break=strpos($this->buffer, $break="\r", $this->buffer_position))=='integer')
  196. {
  197. $next = $line_break + strlen($break);
  198. if(!strcmp($break,"\r")
  199. && strlen($this->buffer) == $next
  200. && !$end)
  201. {
  202. $need_more_data = 1;
  203. break;
  204. }
  205. if($line_break==$this->buffer_position)
  206. {
  207. $part=array(
  208. 'Type'=>'BodyStart',
  209. 'Position'=>$this->offset + $this->buffer_position
  210. );
  211. $this->buffer_position = $next;
  212. $this->state = MIME_PARSER_BODY;
  213. break;
  214. }
  215. }
  216. if(GetType($colon=strpos($this->buffer, ':', $this->buffer_position))=='integer')
  217. {
  218. if(GetType($space=strpos(substr($this->buffer, $this->buffer_position, $colon - $this->buffer_position), ' '))=='integer')
  219. {
  220. if(!$this->mbox
  221. || strcmp(strtolower(substr($this->buffer, $this->buffer_position, $space)), 'from'))
  222. return($this->SetPositionedError('invalid header name line', $this->buffer_position));
  223. $next = $this->buffer_position + $space + 1;
  224. }
  225. else
  226. $next = $colon+1;
  227. }
  228. else
  229. {
  230. $need_more_data = 1;
  231. break;
  232. }
  233. $part=array(
  234. 'Type'=>'HeaderName',
  235. 'Name'=>substr($this->buffer, $this->buffer_position, $next - $this->buffer_position),
  236. 'Position'=>$this->offset + $this->buffer_position
  237. );
  238. $this->buffer_position = $next;
  239. $this->state = MIME_PARSER_HEADER_VALUE;
  240. break;
  241. case MIME_PARSER_HEADER_VALUE:
  242. $position = $this->buffer_position;
  243. $value = '';
  244. for(;;)
  245. {
  246. if(GetType($line_break=strpos($this->buffer, $break="\r\n", $position))=='integer'
  247. || GetType($line_break=strpos($this->buffer, $break="\n", $position))=='integer'
  248. || GetType($line_break=strpos($this->buffer, $break="\r", $position))=='integer')
  249. {
  250. $next = $line_break + strlen($break);
  251. $line = substr($this->buffer, $position, $line_break - $position);
  252. if(strlen($this->buffer) == $next)
  253. {
  254. if(!$end)
  255. {
  256. $need_more_data = 1;
  257. break 2;
  258. }
  259. $value .= $line;
  260. $part=array(
  261. 'Type'=>'HeaderValue',
  262. 'Value'=>$value,
  263. 'Position'=>$this->offset + $this->buffer_position
  264. );
  265. $this->buffer_position = $next;
  266. $this->state = MIME_PARSER_END;
  267. break ;
  268. }
  269. else
  270. {
  271. $character = $this->buffer[$next];
  272. if(!strcmp($character, ' ')
  273. || !strcmp($character, "\t"))
  274. {
  275. $value .= $line;
  276. $position = $next;
  277. }
  278. else
  279. {
  280. $value .= $line;
  281. $part=array(
  282. 'Type'=>'HeaderValue',
  283. 'Value'=>$value,
  284. 'Position'=>$this->offset + $this->buffer_position
  285. );
  286. $this->buffer_position = $next;
  287. $this->state = MIME_PARSER_HEADER;
  288. break 2;
  289. }
  290. }
  291. }
  292. else
  293. {
  294. if(!$end)
  295. {
  296. $need_more_data = 1;
  297. break;
  298. }
  299. else
  300. {
  301. $value .= substr($this->buffer, $position);
  302. $part=array(
  303. 'Type'=>'HeaderValue',
  304. 'Value'=>$value,
  305. 'Position'=>$this->offset + $this->buffer_position
  306. );
  307. $this->buffer_position = strlen($this->buffer);
  308. $this->state = MIME_PARSER_END;
  309. break;
  310. }
  311. }
  312. }
  313. break;
  314. case MIME_PARSER_BODY:
  315. if($this->mbox)
  316. {
  317. $add = 0;
  318. $append='';
  319. if(GetType($line_break=strpos($this->buffer, $break="\r\n", $this->buffer_position))=='integer'
  320. || GetType($line_break=strpos($this->buffer, $break="\n", $this->buffer_position))=='integer'
  321. || GetType($line_break=strpos($this->buffer, $break="\r", $this->buffer_position))=='integer')
  322. {
  323. $next = $line_break + strlen($break);
  324. $following = $next + strlen($break);
  325. if($following >= strlen($this->buffer)
  326. || GetType($line=strpos($this->buffer, $break, $following))!='integer')
  327. {
  328. if(!$end)
  329. {
  330. $need_more_data = 1;
  331. break;
  332. }
  333. }
  334. $start = strtolower(substr($this->buffer, $next, strlen($break.'from ')));
  335. if(!strcmp($break.'from ', $start))
  336. {
  337. if($line_break == $this->buffer_position)
  338. {
  339. $part=array(
  340. 'Type'=>'MessageEnd',
  341. 'Position'=>$this->offset + $this->buffer_position
  342. );
  343. $this->buffer_position = $following;
  344. $this->state = MIME_PARSER_START;
  345. break;
  346. }
  347. else
  348. $add = strlen($break);
  349. $next = $line_break;
  350. }
  351. else
  352. {
  353. $start = strtolower(substr($this->buffer, $next, strlen('>from ')));
  354. if(!strcmp('>from ', $start))
  355. {
  356. $part=array(
  357. 'Type'=>'BodyData',
  358. 'Data'=>substr($this->buffer, $this->buffer_position, $next - $this->buffer_position),
  359. 'Position'=>$this->offset + $this->buffer_position
  360. );
  361. $this->buffer_position = $next + 1;
  362. break;
  363. }
  364. }
  365. }
  366. else
  367. {
  368. if(!$end)
  369. {
  370. $need_more_data = 1;
  371. break;
  372. }
  373. $next = strlen($this->buffer);
  374. $append="\r\n";
  375. }
  376. if($next > $this->buffer_position)
  377. {
  378. $part=array(
  379. 'Type'=>'BodyData',
  380. 'Data'=>substr($this->buffer, $this->buffer_position, $next + $add - $this->buffer_position).$append,
  381. 'Position'=>$this->offset + $this->buffer_position
  382. );
  383. }
  384. elseif($end)
  385. {
  386. $part=array(
  387. 'Type'=>'MessageEnd',
  388. 'Position'=>$this->offset + $this->buffer_position
  389. );
  390. $this->state = MIME_PARSER_END;
  391. }
  392. $this->buffer_position = $next;
  393. }
  394. else
  395. {
  396. if(strlen($this->buffer)-$this->buffer_position)
  397. {
  398. $data=substr($this->buffer, $this->buffer_position, strlen($this->buffer) - $this->buffer_position);
  399. if($end
  400. && strcmp(substr($data,-1),"\n")
  401. && strcmp(substr($data,-1),"\r"))
  402. $data.="\n";
  403. $part=array(
  404. 'Type'=>'BodyData',
  405. 'Data'=>$data,
  406. 'Position'=>$this->offset + $this->buffer_position
  407. );
  408. $this->buffer_position = strlen($this->buffer);
  409. $need_more_data = !$end;
  410. }
  411. else
  412. {
  413. if($end)
  414. {
  415. $part=array(
  416. 'Type'=>'MessageEnd',
  417. 'Position'=>$this->offset + $this->buffer_position
  418. );
  419. $this->state = MIME_PARSER_END;
  420. }
  421. else
  422. $need_more_data = 1;
  423. }
  424. }
  425. break;
  426. default:
  427. return($this->SetPositionedError($this->state.' is not a valid parser state', $this->buffer_position));
  428. }
  429. return(1);
  430. }
  431. Function QueueBodyParts()
  432. {
  433. for(;;)
  434. {
  435. if(!$this->body_parser->GetPart($part,$end))
  436. return($this->SetError($this->body_parser->error));
  437. if($end)
  438. return(1);
  439. if(!IsSet($part['Part']))
  440. $part['Part']=$this->headers['Boundary'];
  441. $this->parts[]=$part;
  442. }
  443. }
  444. Function DecodePart($part)
  445. {
  446. switch($part['Type'])
  447. {
  448. case 'MessageStart':
  449. $this->headers=array();
  450. break;
  451. case 'HeaderName':
  452. if($this->decode_bodies)
  453. $this->current_header = strtolower($part['Name']);
  454. break;
  455. case 'HeaderValue':
  456. if($this->decode_headers)
  457. {
  458. $value = $part['Value'];
  459. $error = '';
  460. for($decoded_header = array(), $position = 0; $position<strlen($value); )
  461. {
  462. if(GetType($encoded=strpos($value,'=?', $position))!='integer')
  463. {
  464. if($position<strlen($value))
  465. {
  466. if(count($decoded_header))
  467. $decoded_header[count($decoded_header)-1]['Value'].=substr($value, $position);
  468. else
  469. {
  470. $decoded_header[]=array(
  471. 'Value'=>substr($value, $position),
  472. 'Encoding'=>'ASCII'
  473. );
  474. }
  475. }
  476. break;
  477. }
  478. $set = $encoded + 2;
  479. if(GetType($method=strpos($value,'?', $set))!='integer')
  480. {
  481. $error = 'invalid header encoding syntax '.$part['Value'];
  482. $error_position = $part['Position'] + $set;
  483. break;
  484. }
  485. $encoding=strtoupper(substr($value, $set, $method - $set));
  486. $method += 1;
  487. if(GetType($data=strpos($value,'?', $method))!='integer')
  488. {
  489. $error = 'invalid header encoding syntax '.$part['Value'];
  490. $error_position = $part['Position'] + $set;
  491. break;
  492. }
  493. $start = $data + 1;
  494. if(GetType($end=strpos($value,'?=', $start))!='integer')
  495. {
  496. $error = 'invalid header encoding syntax '.$part['Value'];
  497. $error_position = $part['Position'] + $start;
  498. break;
  499. }
  500. if($encoded > $position)
  501. {
  502. if(count($decoded_header))
  503. $decoded_header[count($decoded_header)-1]['Value'].=substr($value, $position, $encoded - $position);
  504. else
  505. {
  506. $decoded_header[]=array(
  507. 'Value'=>substr($value, $position, $encoded - $position),
  508. 'Encoding'=>'ASCII'
  509. );
  510. }
  511. }
  512. switch(strtolower(substr($value, $method, $data - $method)))
  513. {
  514. case 'q':
  515. if($end>$start)
  516. {
  517. for($decoded = '', $position = $start; $position < $end ; )
  518. {
  519. switch($value[$position])
  520. {
  521. case '=':
  522. if($end - $position < 3
  523. || !($r=sscanf(strtolower(substr($value, $position+1, 2)), '%x', $code)))
  524. {
  525. $error = 'the header specified an invalid encoded character';
  526. $error_position = $part['Position'] + $position + 1;
  527. break 4;
  528. }
  529. $decoded .= Chr($code);
  530. $position += 3;
  531. break;
  532. case '_':
  533. $decoded .= ' ';
  534. $position++;
  535. break;
  536. default:
  537. $decoded .= $value[$position];
  538. $position++;
  539. break;
  540. }
  541. }
  542. if(count($decoded_header)
  543. && (!strcmp($decoded_header[$last = count($decoded_header)-1]['Encoding'], 'ASCII'))
  544. || !strcmp($decoded_header[$last]['Encoding'], $encoding))
  545. {
  546. $decoded_header[$last]['Value'].= $decoded;
  547. $decoded_header[$last]['Encoding']= $encoding;
  548. }
  549. else
  550. {
  551. $decoded_header[]=array(
  552. 'Value'=>$decoded,
  553. 'Encoding'=>$encoding
  554. );
  555. }
  556. }
  557. break;
  558. case 'b':
  559. if($end>$start)
  560. {
  561. $decoded=base64_decode(substr($value, $start, $end - $start));
  562. if(count($decoded_header)
  563. && (!strcmp($decoded_header[$last = count($decoded_header)-1]['Encoding'], 'ASCII'))
  564. || !strcmp($decoded_header[$last]['Encoding'], $encoding))
  565. {
  566. $decoded_header[$last]['Value'].= $decoded;
  567. $decoded_header[$last]['Encoding']= $encoding;
  568. }
  569. else
  570. {
  571. $decoded_header[]=array(
  572. 'Value'=>$decoded,
  573. 'Encoding'=>$encoding
  574. );
  575. }
  576. }
  577. break;
  578. default:
  579. $error = 'the header specified an unsupported encoding method';
  580. $error_position = $part['Position'] + $method;
  581. break 2;
  582. }
  583. $position = $end + 2;
  584. }
  585. if(strlen($error)==0)
  586. $part['Decoded']=$decoded_header;
  587. }
  588. if($this->decode_bodies
  589. || $this->decode_headers)
  590. {
  591. switch($this->current_header)
  592. {
  593. case 'content-type:':
  594. $value = $part['Value'];
  595. $type = strtolower(trim(strtok($value, ';')));
  596. $parameters = trim(strtok(''));
  597. $this->headers['Type'] = $type;
  598. if($this->decode_headers)
  599. {
  600. $part['MainValue'] = $type;
  601. $part['Parameters'] = array();
  602. }
  603. if(!strcmp(strtok($type, '/'), 'multipart'))
  604. {
  605. $this->headers['Multipart'] = 1;
  606. while(strlen($parameters))
  607. {
  608. $parameter = strtolower(strtok($parameters, '='));
  609. $value = trim(strtok(';'));
  610. if(!strcmp($value[0], '"')
  611. && !strcmp($value[strlen($value) - 1], '"'))
  612. $value = substr($value, 1, strlen($value) - 2);
  613. if($this->decode_headers)
  614. $part['Parameters'][$parameter] = $value;
  615. if(!strcmp($parameter, 'boundary'))
  616. $this->headers['Boundary'] = $value;
  617. $parameters = trim(strtok(''));
  618. }
  619. if(!IsSet($this->headers['Boundary']))
  620. return($this->SetPositionedError('multipart content-type header does not specify the boundary parameter', $part['Position']));
  621. }
  622. break;
  623. case 'content-transfer-encoding:':
  624. switch($this->headers['Encoding']=strtolower(trim($part['Value'])))
  625. {
  626. case 'quoted-printable':
  627. $this->headers['QuotedPrintable'] = 1;
  628. break;
  629. case '7bit':
  630. case '8bit':
  631. break;
  632. case 'base64':
  633. $this->headers['Base64']=1;
  634. break;
  635. default:
  636. return($this->SetPositionedError('decoding '.$this->headers['Encoding'].' encoded bodies is not yet supported', $part['Position']));
  637. }
  638. break;
  639. }
  640. }
  641. break;
  642. case 'BodyStart':
  643. if($this->decode_bodies
  644. && IsSet($this->headers['Multipart']))
  645. {
  646. $this->body_parser_state = MIME_PARSER_BODY_START;
  647. $this->body_buffer = '';
  648. $this->body_buffer_position = 0;
  649. }
  650. break;
  651. case 'MessageEnd':
  652. if($this->decode_bodies
  653. && IsSet($this->headers['Multipart'])
  654. && $this->body_parser_state != MIME_PARSER_BODY_DONE)
  655. return($this->SetPositionedError('incomplete message body part', $part['Position']));
  656. break;
  657. case 'BodyData':
  658. if($this->decode_bodies)
  659. {
  660. if(strlen($this->body_buffer)==0)
  661. {
  662. $this->body_buffer = $part['Data'];
  663. $this->body_offset = $part['Position'];
  664. }
  665. else
  666. $this->body_buffer .= $part['Data'];
  667. if(IsSet($this->headers['Multipart']))
  668. {
  669. $boundary = '--'.$this->headers['Boundary'];
  670. switch($this->body_parser_state)
  671. {
  672. case MIME_PARSER_BODY_START:
  673. for($position = $this->body_buffer_position; ;)
  674. {
  675. if(GetType($line_break=strpos($this->body_buffer, $break="\r\n", $position))!='integer'
  676. && GetType($line_break=strpos($this->body_buffer, $break="\n", $position))!='integer'
  677. && GetType($line_break=strpos($this->body_buffer, $break="\r", $position))!='integer')
  678. return(1);
  679. $next = $line_break + strlen($break);
  680. if(!strcmp(substr($this->body_buffer, $position, $line_break - $position), $boundary))
  681. {
  682. $part=array(
  683. 'Type'=>'StartPart',
  684. 'Part'=>$this->headers['Boundary'],
  685. 'Position'=>$this->body_offset + $next
  686. );
  687. $this->parts[]=$part;
  688. UnSet($this->body_parser);
  689. $this->body_parser = new mime_parser_class;
  690. $this->body_parser->decode_bodies = 1;
  691. $this->body_parser->decode_headers = $this->decode_headers;
  692. $this->body_parser->mbox = 0;
  693. $this->body_parser_state = MIME_PARSER_BODY_DATA;
  694. $this->body_buffer = substr($this->body_buffer, $next);
  695. $this->body_offset += $next;
  696. $this->body_buffer_position = 0;
  697. break;
  698. }
  699. else
  700. $position = $next;
  701. }
  702. case MIME_PARSER_BODY_DATA:
  703. for($position = $this->body_buffer_position; ;)
  704. {
  705. if(GetType($line_break=strpos($this->body_buffer, $break="\r\n", $position))!='integer'
  706. && GetType($line_break=strpos($this->body_buffer, $break="\n", $position))!='integer'
  707. && GetType($line_break=strpos($this->body_buffer, $break="\r", $position))!='integer')
  708. {
  709. if($position > 0)
  710. {
  711. if(!$this->body_parser->Parse(substr($this->body_buffer, 0, $position), 0))
  712. return($this->SetError($this->body_parser->error));
  713. if(!$this->QueueBodyParts())
  714. return(0);
  715. }
  716. $this->body_buffer = substr($this->body_buffer, $position);
  717. $this->body_buffer_position = 0;
  718. $this->body_offset += $position;
  719. return(1);
  720. }
  721. $next = $line_break + strlen($break);
  722. $line = substr($this->body_buffer, $position, $line_break - $position);
  723. if(!strcmp($line, $boundary))
  724. {
  725. if(!$this->body_parser->Parse(substr($this->body_buffer, 0, $position), 1))
  726. return($this->SetError($this->body_parser->error));
  727. if(!$this->QueueBodyParts())
  728. return(0);
  729. $part=array(
  730. 'Type'=>'EndPart',
  731. 'Part'=>$this->headers['Boundary'],
  732. 'Position'=>$this->body_offset + $position
  733. );
  734. $this->parts[] = $part;
  735. $part=array(
  736. 'Type'=>'StartPart',
  737. 'Part'=>$this->headers['Boundary'],
  738. 'Position'=>$this->body_offset + $next
  739. );
  740. $this->parts[] = $part;
  741. UnSet($this->body_parser);
  742. $this->body_parser = new mime_parser_class;
  743. $this->body_parser->decode_bodies = 1;
  744. $this->body_parser->decode_headers = $this->decode_headers;
  745. $this->body_parser->mbox = 0;
  746. $this->body_buffer = substr($this->body_buffer, $next);
  747. $this->body_buffer_position = 0;
  748. $this->body_offset += $next;
  749. $position=0;
  750. continue;
  751. }
  752. elseif(!strcmp($line, $boundary.'--'))
  753. {
  754. if(!$this->body_parser->Parse(substr($this->body_buffer, 0, $position), 1))
  755. return($this->SetError($this->body_parser->error));
  756. if(!$this->QueueBodyParts())
  757. return(0);
  758. $part=array(
  759. 'Type'=>'EndPart',
  760. 'Part'=>$this->headers['Boundary'],
  761. 'Position'=>$this->body_offset + $position
  762. );
  763. $this->body_buffer = substr($this->body_buffer, $next);
  764. $this->body_buffer_position = 0;
  765. $this->body_offset += $next;
  766. $this->body_parser_state = MIME_PARSER_BODY_DONE;
  767. break 2;
  768. }
  769. $position = $next;
  770. }
  771. break;
  772. case MIME_PARSER_BODY_DONE:
  773. return(1);
  774. default:
  775. return($this->SetPositionedError($this->state.' is not a valid body parser state', $this->body_buffer_position));
  776. }
  777. }
  778. elseif(IsSet($this->headers['QuotedPrintable']))
  779. {
  780. for($end = strlen($this->body_buffer), $decoded = '', $position = $this->body_buffer_position; $position < $end; )
  781. {
  782. if(GetType($equal = strpos($this->body_buffer, '=', $position))!='integer')
  783. {
  784. $decoded .= substr($this->body_buffer, $position);
  785. $position = $end;
  786. break;
  787. }
  788. $next = $equal + 1;
  789. switch($end - $equal)
  790. {
  791. case 1:
  792. $decoded .= substr($this->body_buffer, $position, $equal - $position);
  793. $position = $equal;
  794. break 2;
  795. case 2:
  796. $decoded .= substr($this->body_buffer, $position, $equal - $position);
  797. if(!strcmp($this->body_buffer[$next],"\n"))
  798. $position = $end;
  799. else
  800. $position = $equal;
  801. break 2;
  802. }
  803. if(!strcmp(substr($this->body_buffer, $next, 2), $break="\r\n")
  804. || !strcmp($this->body_buffer[$next], $break="\n")
  805. || !strcmp($this->body_buffer[$next], $break="\r"))
  806. {
  807. $decoded .= substr($this->body_buffer, $position, $equal - $position);
  808. $position = $next + strlen($break);
  809. continue;
  810. }
  811. $decoded .= substr($this->body_buffer, $position, $equal - $position);
  812. if(!($r=sscanf(strtolower(substr($this->body_buffer, $next, 2)), '%x', $code)))
  813. return($this->SetPositionedError('the body specified an invalid quoted-printable encoded character', $this->body_offset + $next));
  814. $decoded .= Chr($code);
  815. $position = $equal + 3;
  816. }
  817. if(strlen($decoded)==0)
  818. {
  819. $this->body_buffer_position = $position;
  820. return(1);
  821. }
  822. $part['Data'] = $decoded;
  823. $this->body_buffer = substr($this->body_buffer, $position);
  824. $this->body_buffer_position = 0;
  825. $this->body_offset += $position;
  826. }
  827. elseif(IsSet($this->headers['Base64']))
  828. {
  829. $part['Data'] = base64_decode($this->body_buffer_position ? substr($this->body_buffer,$this->body_buffer_position) : $this->body_buffer);
  830. $this->body_offset += strlen($this->body_buffer) - $this->body_buffer_position;
  831. $this->body_buffer_position = 0;
  832. $this->body_buffer = '';
  833. }
  834. else
  835. {
  836. $part['Data'] = substr($this->body_buffer, $this->body_buffer_position);
  837. $this->body_buffer_position = 0;
  838. $this->body_buffer = '';
  839. }
  840. }
  841. break;
  842. }
  843. $this->parts[]=$part;
  844. return(1);
  845. }
  846. Function DecodeStream($parameters, &$end_of_message, &$decoded)
  847. {
  848. $end_of_message = 1;
  849. $state = MIME_MESSAGE_START;
  850. for(;;)
  851. {
  852. if(!$this->GetPart($part, $end))
  853. return(0);
  854. if($end)
  855. {
  856. if(IsSet($parameters['File']))
  857. {
  858. $end_of_data = feof($this->file);
  859. if($end_of_data)
  860. break;
  861. $data = @fread($this->file, 8000);
  862. if(GetType($data)!='string')
  863. return($this->SetPHPError('could not read the message file', $php_errormsg));
  864. $end_of_data = feof($this->file);
  865. }
  866. else
  867. {
  868. $end_of_data=($this->position>=strlen($parameters['Data']));
  869. if($end_of_data)
  870. break;
  871. $data = substr($parameters['Data'], $this->position);
  872. $end_of_data = 1;
  873. $this->position = strlen($parameters['Data']);
  874. }
  875. if(!$this->Parse($data, $end_of_data))
  876. return(0);
  877. continue;
  878. }
  879. $type = $part['Type'];
  880. switch($state)
  881. {
  882. case MIME_MESSAGE_START:
  883. switch($type)
  884. {
  885. case 'MessageStart':
  886. $decoded=array(
  887. 'Headers'=>array(),
  888. 'Parts'=>array()
  889. );
  890. $end_of_message = 0;
  891. $state = MIME_MESSAGE_GET_HEADER_NAME;
  892. continue 3;
  893. }
  894. break;
  895. case MIME_MESSAGE_GET_HEADER_NAME:
  896. switch($type)
  897. {
  898. case 'HeaderName':
  899. $header = strtolower($part['Name']);
  900. $state = MIME_MESSAGE_GET_HEADER_VALUE;
  901. continue 3;
  902. case 'BodyStart':
  903. $state = MIME_MESSAGE_GET_BODY;
  904. $part_number = 0;
  905. continue 3;
  906. }
  907. break;
  908. case MIME_MESSAGE_GET_HEADER_VALUE:
  909. switch($type)
  910. {
  911. case 'HeaderValue':
  912. $value = trim($part['Value']);
  913. if(!IsSet($decoded['Headers'][$header]))
  914. {
  915. $h = 0;
  916. $decoded['Headers'][$header]=$value;
  917. }
  918. elseif(GetType($decoded['Headers'][$header])=='string')
  919. {
  920. $h = 1;
  921. $decoded['Headers'][$header]=array($decoded['Headers'][$header], $value);
  922. }
  923. else
  924. {
  925. $h = count($decoded['Headers'][$header]);
  926. $decoded['Headers'][$header][]=$value;
  927. }
  928. if(IsSet($part['Decoded'])
  929. && (count($part['Decoded'])>1
  930. || strcmp($part['Decoded'][0]['Encoding'],'ASCII')
  931. || strcmp($value, trim($part['Decoded'][0]['Value']))))
  932. {
  933. $p=$part['Decoded'];
  934. $p[0]['Value']=ltrim($p[0]['Value']);
  935. $last=count($p)-1;
  936. $p[$last]['Value']=rtrim($p[$last]['Value']);
  937. $decoded['DecodedHeaders'][$header][$h]=$p;
  938. }
  939. $state = MIME_MESSAGE_GET_HEADER_NAME;
  940. continue 3;
  941. }
  942. break;
  943. case MIME_MESSAGE_GET_BODY:
  944. switch($type)
  945. {
  946. case 'BodyData':
  947. if(IsSet($parameters['SaveBody']))
  948. {
  949. if(!IsSet($decoded['BodyFile']))
  950. {
  951. $directory_separator=(defined('DIRECTORY_SEPARATOR') ? DIRECTORY_SEPARATOR : '/');
  952. $path = (strlen($parameters['SaveBody']) ? ($parameters['SaveBody'].(strcmp($parameters['SaveBody'][strlen($parameters['SaveBody'])-1], $directory_separator) ? $directory_separator : '')) : '').strval($this->body_part_number);
  953. if(!($this->body_file = fopen($path, 'wb')))
  954. return($this->SetPHPError('could not create file '.$path.' to save the message body part', $php_errormsg));
  955. $decoded['BodyFile'] = $path;
  956. $decoded['BodyPart'] = $this->body_part_number;
  957. $decoded['BodyLength'] = 0;
  958. $this->body_part_number++;
  959. }
  960. if(strlen($part['Data'])
  961. && !fwrite($this->body_file, $part['Data']))
  962. {
  963. $this->SetPHPError('could not save the message body part to file '.$decoded['BodyFile'], $php_errormsg);
  964. fclose($this->body_file);
  965. @unlink($decoded['BodyFile']);
  966. return(0);
  967. }
  968. }
  969. elseif(IsSet($parameters['SkipBody']))
  970. {
  971. if(!IsSet($decoded['BodyPart']))
  972. {
  973. $decoded['BodyPart'] = $this->body_part_number;
  974. $decoded['BodyLength'] = 0;
  975. $this->body_part_number++;
  976. }
  977. }
  978. else
  979. {
  980. if(IsSet($decoded['Body']))
  981. $decoded['Body'].=$part['Data'];
  982. else
  983. {
  984. $decoded['Body']=$part['Data'];
  985. $decoded['BodyPart'] = $this->body_part_number;
  986. $decoded['BodyLength'] = 0;
  987. $this->body_part_number++;
  988. }
  989. }
  990. $decoded['BodyLength'] += strlen($part['Data']);
  991. continue 3;
  992. case 'StartPart':
  993. if(!$this->DecodeStream($parameters, $end_of_part, $decoded_part))
  994. return(0);
  995. $decoded['Parts'][$part_number]=$decoded_part;
  996. $part_number++;
  997. $state = MIME_MESSAGE_GET_BODY_PART;
  998. continue 3;
  999. case 'MessageEnd':
  1000. if(IsSet($decoded['BodyFile']))
  1001. fclose($this->body_file);
  1002. return(1);
  1003. }
  1004. break;
  1005. case MIME_MESSAGE_GET_BODY_PART:
  1006. switch($type)
  1007. {
  1008. case 'EndPart':
  1009. $state = MIME_MESSAGE_GET_BODY;
  1010. continue 3;
  1011. }
  1012. break;
  1013. }
  1014. return($this->SetError('unexpected decoded message part type '.$type.' in state '.$state));
  1015. }
  1016. return(1);
  1017. }
  1018. /* Public functions */
  1019. Function Parse($data, $end)
  1020. {
  1021. if(strlen($this->error))
  1022. return(0);
  1023. if($this->state==MIME_PARSER_END)
  1024. return($this->SetError('the parser already reached the end'));
  1025. $this->buffer .= $data;
  1026. do
  1027. {
  1028. Unset($part);
  1029. if(!$this->ParsePart($end, $part, $need_more_data))
  1030. return(0);
  1031. if(IsSet($part)
  1032. && !$this->DecodePart($part))
  1033. return(0);
  1034. }
  1035. while(!$need_more_data
  1036. && $this->state!=MIME_PARSER_END);
  1037. if($end
  1038. && $this->state!=MIME_PARSER_END)
  1039. return($this->SetError('reached a premature end of data'));
  1040. if($this->buffer_position>0)
  1041. {
  1042. $this->offset += $this->buffer_position;
  1043. $this->buffer = substr($this->buffer, $this->buffer_position);
  1044. $this->buffer_position = 0;
  1045. }
  1046. return(1);
  1047. }
  1048. Function ParseFile($file)
  1049. {
  1050. if(strlen($this->error))
  1051. return(0);
  1052. if(!($stream = @fopen($file, 'r')))
  1053. return($this->SetPHPError('Could not open the file '.$file, $php_errormsg));
  1054. for($end = 0;!$end;)
  1055. {
  1056. if(!($data = @fread($stream, 8000)))
  1057. {
  1058. $this->SetPHPError('Could not open the file '.$file, $php_errormsg);
  1059. fclose($stream);
  1060. return(0);
  1061. }
  1062. $end=feof($stream);
  1063. if(!$this->Parse($data, $end))
  1064. {
  1065. fclose($stream);
  1066. return(0);
  1067. }
  1068. }
  1069. fclose($stream);
  1070. return(1);
  1071. }
  1072. Function GetPart(&$part, &$end)
  1073. {
  1074. $end = ($this->part_position >= count($this->parts));
  1075. if($end)
  1076. {
  1077. if($this->part_position)
  1078. {
  1079. $this->part_position = 0;
  1080. $this->parts = array();
  1081. }
  1082. }
  1083. else
  1084. {
  1085. $part = $this->parts[$this->part_position];
  1086. $this->part_position ++;
  1087. }
  1088. return(1);
  1089. }
  1090. /*
  1091. {metadocument}
  1092. <function>
  1093. <name>Decode</name>
  1094. <type>BOOLEAN</type>
  1095. <documentation>
  1096. <purpose>Parse and decode message data and retrieve its structure.</purpose>
  1097. <usage>Pass an array to the <argumentlink>
  1098. <function>Decode</function>
  1099. <argument>parameters</argument>
  1100. </argumentlink>
  1101. parameter to define whether the message data should be read and
  1102. parsed from a file or a data string, as well additional parsing
  1103. options. The <argumentlink>
  1104. <function>Decode</function>
  1105. <argument>decoded</argument>
  1106. </argumentlink> returns the
  1107. data structure of the parsed messages.</usage>
  1108. <returnvalue>This function returns <booleanvalue>1</booleanvalue> if
  1109. the specified message data is parsed successfully. Otherwise,
  1110. check the variables <variablelink>error</variablelink> and
  1111. <variablelink>error_position</variablelink> to determine what
  1112. error occurred and the relevant message position.</returnvalue>
  1113. </documentation>
  1114. <argument>
  1115. <name>parameters</name>
  1116. <type>HASH</type>
  1117. <documentation>
  1118. <purpose>Associative array to specify parameters for the message
  1119. data parsing and decoding operation. Here follows the list of
  1120. supported parameters that should be used as indexes of the
  1121. array:<paragraphbreak />
  1122. <tt>File</tt><paragraphbreak />
  1123. Name of the file from which the message data will be read. It
  1124. may be the name of a file stream or a remote URL, as long as
  1125. your PHP installation is configured to allow accessing remote
  1126. files with the <tt>fopen()</tt> function.<paragraphbreak />
  1127. <tt>Data</tt><paragraphbreak />
  1128. String that specifies the message data. This should be used
  1129. as alternative data source for passing data available in memory,
  1130. like for instance messages stored in a database that was queried
  1131. dynamically and the message data was fetched into a string
  1132. variable.<paragraphbreak />
  1133. <tt>SaveBody</tt><paragraphbreak />
  1134. If this parameter is specified, the message body parts are saved
  1135. to files. The path of the directory where the files are saved is
  1136. defined by this parameter value. The information about the
  1137. message body part structure is returned by the <argumentlink>
  1138. <function>Decode</function>
  1139. <argument>decoded</argument>
  1140. </argumentlink> argument, but it just returns the body data part
  1141. file name instead of the actual body data. It is recommended for
  1142. retrieving messages larger than the available memory. The names
  1143. of the body part files are numbers starting from
  1144. <stringvalue>1</stringvalue>.<paragraphbreak />
  1145. <tt>SkipBody</tt><paragraphbreak />
  1146. If this parameter is specified, the message body parts are
  1147. skipped. This means the information about the message body part
  1148. structure is returned by the <argumentlink>
  1149. <function>Decode</function>
  1150. <argument>decoded</argument>
  1151. </argumentlink> but it does not return any body data. It is
  1152. recommended just for parsing messages without the need to
  1153. retrieve the message body part data.</purpose>
  1154. </documentation>
  1155. </argument>
  1156. <argument>
  1157. <name>decoded</name>
  1158. <type>ARRAY</type>
  1159. <out />
  1160. <documentation>
  1161. <purpose>Retrieve the structure of the parsed message headers and
  1162. body data.<paragraphbreak />
  1163. The argument is used to return by reference an array of message
  1164. structure definitions. Each array entry refers to the structure
  1165. of each message that is found and parsed successfully.<paragraphbreak />
  1166. Each message entry consists of an associative array with several
  1167. entries that describe the message structure. Here follows the
  1168. list of message structure entries names and the meaning of the
  1169. respective values:<paragraphbreak />
  1170. <tt>Headers</tt><paragraphbreak />
  1171. Associative array that returns the list of all the message
  1172. headers. The array entries are the header names mapped to
  1173. lower case, including the end colon. The array values are the
  1174. respective header raw values without any start or trailing white
  1175. spaces. Long header values split between multiple message lines
  1176. are gathered in single string without line breaks. If an header
  1177. with the same name appears more than once in the message, the
  1178. respective value is an array with the values of all of the
  1179. header occurrences.<paragraphbreak />
  1180. <tt>DecodedHeaders</tt><paragraphbreak />
  1181. Associative array that returns the list of all the encoded
  1182. message headers when the
  1183. <variablelink>decode_headers</variablelink> variable is set. The
  1184. array entries are the header names mapped to lower case,
  1185. including the end colon. The array values are also arrays that
  1186. list only the occurrences of the header that originally were
  1187. encoded. Each entry of the decoded header array contains more
  1188. associative arrays that describe each part of the decoded
  1189. header. Each of those associative arrays have an entry named
  1190. <tt>Value</tt> that contains the decoded header part value, and
  1191. another entry named <tt>Encoding</tt> that specifies the
  1192. character set encoding of the value in upper case.<paragraphbreak />
  1193. <tt>Parts</tt><paragraphbreak />
  1194. If this message content type is multipart, this entry is an
  1195. array that describes each of the parts contained in the message
  1196. body. Each message part is described by an associative array
  1197. with the same structure of a complete message
  1198. definition.<paragraphbreak />
  1199. <tt>Body</tt><paragraphbreak />
  1200. String with the decoded data contained in the message body. If
  1201. the <tt>SaveBody</tt> or <tt>SkipBody</tt> parameters are
  1202. defined, the <tt>Body</tt> entry is not set.<paragraphbreak />
  1203. <tt>BodyFile</tt><paragraphbreak />
  1204. Name of the file to which the message body data was saved when
  1205. the <tt>SaveBody</tt> parameter is defined.<paragraphbreak />
  1206. <tt>BodyLength</tt><paragraphbreak />
  1207. Length of the current decoded body part.<paragraphbreak />
  1208. <tt>BodyPart</tt><paragraphbreak />
  1209. Number of the current message body part.</purpose>
  1210. </documentation>
  1211. </argument>
  1212. <do>
  1213. {/metadocument}
  1214. */
  1215. Function Decode($parameters, &$decoded)
  1216. {
  1217. if(IsSet($parameters['File']))
  1218. {
  1219. if(!($this->file = @fopen($parameters['File'], 'r')))
  1220. return($this->SetPHPError('could not open the message file to decode '.$parameters['File'], $php_errormsg));
  1221. }
  1222. elseif(IsSet($parameters['Data']))
  1223. $this->position = 0;
  1224. else
  1225. return($this->SetError('it was not specified a valid message to decode'));
  1226. $decoded = array();
  1227. for($message = 0; ($success = $this->DecodeStream($parameters, $end_of_message, $decoded_message)) && !$end_of_message; $message++)
  1228. $decoded[$message]=$decoded_message;
  1229. if(IsSet($parameters['File']))
  1230. fclose($this->file);
  1231. return($success);
  1232. }
  1233. /*
  1234. {metadocument}
  1235. </do>
  1236. </function>
  1237. {/metadocument}
  1238. */
  1239. };
  1240. /*
  1241. {metadocument}
  1242. </class>
  1243. {/metadocument}
  1244. */
  1245. ?>