Rfc822Header.class.php 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023
  1. <?php
  2. /**
  3. * Rfc822Header.class.php
  4. *
  5. * This file contains functions needed to handle headers in mime messages.
  6. *
  7. * @copyright &copy; 2003-2007 The SquirrelMail Project Team
  8. * @license http://opensource.org/licenses/gpl-license.php GNU Public License
  9. * @version $Id$
  10. * @package squirrelmail
  11. * @subpackage mime
  12. * @since 1.3.2
  13. */
  14. /**
  15. * MIME header class
  16. * input: header_string or array
  17. * You must call parseHeader() function after creating object in order to fill object's
  18. * parameters.
  19. * @todo FIXME: there is no constructor function and class should ignore all input args.
  20. * @package squirrelmail
  21. * @subpackage mime
  22. * @since 1.3.0
  23. */
  24. class Rfc822Header {
  25. /**
  26. * Date header
  27. * @var mixed
  28. */
  29. var $date = -1;
  30. /**
  31. * Original date header as fallback for unparsable dates
  32. * @var mixed
  33. */
  34. var $date_unparsed = '';
  35. /**
  36. * Subject header
  37. * @var string
  38. */
  39. var $subject = '';
  40. /**
  41. * From header
  42. * @var array
  43. */
  44. var $from = array();
  45. /**
  46. * @var mixed
  47. */
  48. var $sender = '';
  49. /**
  50. * Reply-To header
  51. * @var array
  52. */
  53. var $reply_to = array();
  54. /**
  55. * Mail-Followup-To header
  56. * @var array
  57. */
  58. var $mail_followup_to = array();
  59. /**
  60. * To header
  61. * @var array
  62. */
  63. var $to = array();
  64. /**
  65. * Cc header
  66. * @var array
  67. */
  68. var $cc = array();
  69. /**
  70. * Bcc header
  71. * @var array
  72. */
  73. var $bcc = array();
  74. /**
  75. * In-reply-to header
  76. * @var string
  77. */
  78. var $in_reply_to = '';
  79. /**
  80. * Message-ID header
  81. * @var string
  82. */
  83. var $message_id = '';
  84. /**
  85. * References header
  86. * @var string
  87. */
  88. var $references = '';
  89. /**
  90. * @var mixed
  91. */
  92. var $mime = false;
  93. /**
  94. * Content Type object
  95. * @var object
  96. */
  97. var $content_type = '';
  98. /**
  99. * @var mixed
  100. */
  101. var $disposition = '';
  102. /**
  103. * X-Mailer header
  104. * @var string
  105. */
  106. var $xmailer = '';
  107. /**
  108. * Priority header
  109. * @var integer
  110. */
  111. var $priority = 3;
  112. /**
  113. * Disposition notification for requesting message delivery notification (MDN)
  114. * @var mixed
  115. */
  116. var $dnt = '';
  117. /**
  118. * Delivery notification (DR)
  119. * @var mixed
  120. */
  121. var $drnt = '';
  122. /**
  123. * @var mixed
  124. */
  125. var $encoding = '';
  126. /**
  127. * @var mixed
  128. */
  129. var $content_id = '';
  130. /**
  131. * @var mixed
  132. */
  133. var $content_desc = '';
  134. /**
  135. * @var mixed
  136. */
  137. var $mlist = array();
  138. /**
  139. * SpamAssassin 'x-spam-status' header
  140. * @var mixed
  141. */
  142. var $x_spam_status = array();
  143. /**
  144. * Extra header
  145. * only needed for constructing headers in delivery class
  146. * @var array
  147. */
  148. var $more_headers = array();
  149. /**
  150. * @param mixed $hdr string or array with message headers
  151. */
  152. function parseHeader($hdr) {
  153. if (is_array($hdr)) {
  154. $hdr = implode('', $hdr);
  155. }
  156. /* First we replace \r\n by \n and unfold the header */
  157. /* FIXME: unfolding header with multiple spaces "\n( +)" */
  158. $hdr = trim(str_replace(array("\r\n", "\n\t", "\n "),array("\n", ' ', ' '), $hdr));
  159. /* Now we can make a new header array with */
  160. /* each element representing a headerline */
  161. $hdr = explode("\n" , $hdr);
  162. foreach ($hdr as $line) {
  163. $pos = strpos($line, ':');
  164. if ($pos > 0) {
  165. $field = substr($line, 0, $pos);
  166. if (!strstr($field,' ')) { /* valid field */
  167. $value = trim(substr($line, $pos+1));
  168. $this->parseField($field, $value);
  169. }
  170. }
  171. }
  172. if (!is_object($this->content_type)) {
  173. $this->parseContentType('text/plain; charset=us-ascii');
  174. }
  175. }
  176. /**
  177. * @param string $value
  178. * @return string
  179. */
  180. function stripComments($value) {
  181. $result = '';
  182. $cnt = strlen($value);
  183. for ($i = 0; $i < $cnt; ++$i) {
  184. switch ($value{$i}) {
  185. case '"':
  186. $result .= '"';
  187. while ((++$i < $cnt) && ($value{$i} != '"')) {
  188. if ($value{$i} == '\\') {
  189. $result .= '\\';
  190. ++$i;
  191. }
  192. $result .= $value{$i};
  193. }
  194. if($i < $cnt) {
  195. $result .= $value{$i};
  196. }
  197. break;
  198. case '(':
  199. $depth = 1;
  200. while (($depth > 0) && (++$i < $cnt)) {
  201. switch($value{$i}) {
  202. case '\\':
  203. ++$i;
  204. break;
  205. case '(':
  206. ++$depth;
  207. break;
  208. case ')':
  209. --$depth;
  210. break;
  211. default:
  212. break;
  213. }
  214. }
  215. break;
  216. default:
  217. $result .= $value{$i};
  218. break;
  219. }
  220. }
  221. return $result;
  222. }
  223. /**
  224. * Parse header field according to field type
  225. * @param string $field field name
  226. * @param string $value field value
  227. */
  228. function parseField($field, $value) {
  229. $field = strtolower($field);
  230. switch($field) {
  231. case 'date':
  232. $value = $this->stripComments($value);
  233. $d = strtr($value, array(' ' => ' '));
  234. $d = explode(' ', $d);
  235. $this->date = getTimeStamp($d);
  236. $this->date_unparsed = strtr($value,'<>',' ');
  237. break;
  238. case 'subject':
  239. $this->subject = $value;
  240. break;
  241. case 'from':
  242. $this->from = $this->parseAddress($value,true);
  243. break;
  244. case 'sender':
  245. $this->sender = $this->parseAddress($value);
  246. break;
  247. case 'reply-to':
  248. $this->reply_to = $this->parseAddress($value, true);
  249. break;
  250. case 'mail-followup-to':
  251. $this->mail_followup_to = $this->parseAddress($value, true);
  252. break;
  253. case 'to':
  254. $this->to = $this->parseAddress($value, true);
  255. break;
  256. case 'cc':
  257. $this->cc = $this->parseAddress($value, true);
  258. break;
  259. case 'bcc':
  260. $this->bcc = $this->parseAddress($value, true);
  261. break;
  262. case 'in-reply-to':
  263. $this->in_reply_to = $value;
  264. break;
  265. case 'message-id':
  266. $value = $this->stripComments($value);
  267. $this->message_id = $value;
  268. break;
  269. case 'references':
  270. $value = $this->stripComments($value);
  271. $this->references = $value;
  272. break;
  273. case 'x-confirm-reading-to':
  274. case 'disposition-notification-to':
  275. $value = $this->stripComments($value);
  276. $this->dnt = $this->parseAddress($value);
  277. break;
  278. case 'return-receipt-to':
  279. $value = $this->stripComments($value);
  280. $this->drnt = $this->parseAddress($value);
  281. break;
  282. case 'mime-version':
  283. $value = $this->stripComments($value);
  284. $value = str_replace(' ', '', $value);
  285. $this->mime = ($value == '1.0' ? true : $this->mime);
  286. break;
  287. case 'content-type':
  288. $value = $this->stripComments($value);
  289. $this->parseContentType($value);
  290. break;
  291. case 'content-disposition':
  292. $value = $this->stripComments($value);
  293. $this->parseDisposition($value);
  294. break;
  295. case 'content-transfer-encoding':
  296. $this->encoding = $value;
  297. break;
  298. case 'content-description':
  299. $this->content_desc = $value;
  300. break;
  301. case 'content-id':
  302. $value = $this->stripComments($value);
  303. $this->content_id = $value;
  304. break;
  305. case 'user-agent':
  306. case 'x-mailer':
  307. $this->xmailer = $value;
  308. break;
  309. case 'x-priority':
  310. case 'importance':
  311. case 'priority':
  312. $this->priority = $this->parsePriority($value);
  313. break;
  314. case 'list-post':
  315. $value = $this->stripComments($value);
  316. $this->mlist('post', $value);
  317. break;
  318. case 'list-reply':
  319. $value = $this->stripComments($value);
  320. $this->mlist('reply', $value);
  321. break;
  322. case 'list-subscribe':
  323. $value = $this->stripComments($value);
  324. $this->mlist('subscribe', $value);
  325. break;
  326. case 'list-unsubscribe':
  327. $value = $this->stripComments($value);
  328. $this->mlist('unsubscribe', $value);
  329. break;
  330. case 'list-archive':
  331. $value = $this->stripComments($value);
  332. $this->mlist('archive', $value);
  333. break;
  334. case 'list-owner':
  335. $value = $this->stripComments($value);
  336. $this->mlist('owner', $value);
  337. break;
  338. case 'list-help':
  339. $value = $this->stripComments($value);
  340. $this->mlist('help', $value);
  341. break;
  342. case 'list-id':
  343. $value = $this->stripComments($value);
  344. $this->mlist('id', $value);
  345. break;
  346. case 'x-spam-status':
  347. $this->x_spam_status = $this->parseSpamStatus($value);
  348. break;
  349. default:
  350. break;
  351. }
  352. }
  353. /**
  354. * @param string $address
  355. * @return array
  356. */
  357. function getAddressTokens($address) {
  358. $aTokens = array();
  359. $aSpecials = array('(' ,'<' ,',' ,';' ,':');
  360. $aReplace = array(' (',' <',' ,',' ;',' :');
  361. $address = str_replace($aSpecials,$aReplace,$address);
  362. $iCnt = strlen($address);
  363. $i = 0;
  364. while ($i < $iCnt) {
  365. $cChar = $address{$i};
  366. switch($cChar)
  367. {
  368. case '<':
  369. $iEnd = strpos($address,'>',$i+1);
  370. if (!$iEnd) {
  371. $sToken = substr($address,$i);
  372. $i = $iCnt;
  373. } else {
  374. $sToken = substr($address,$i,$iEnd - $i +1);
  375. $i = $iEnd;
  376. }
  377. $sToken = str_replace($aReplace, $aSpecials,$sToken);
  378. if ($sToken) $aTokens[] = $sToken;
  379. break;
  380. case '"':
  381. $iEnd = strpos($address,$cChar,$i+1);
  382. if ($iEnd) {
  383. // skip escaped quotes
  384. $prev_char = $address{$iEnd-1};
  385. while ($prev_char === '\\' && substr($address,$iEnd-2,2) !== '\\\\') {
  386. $iEnd = strpos($address,$cChar,$iEnd+1);
  387. if ($iEnd) {
  388. $prev_char = $address{$iEnd-1};
  389. } else {
  390. $prev_char = false;
  391. }
  392. }
  393. }
  394. if (!$iEnd) {
  395. $sToken = substr($address,$i);
  396. $i = $iCnt;
  397. } else {
  398. // also remove the surrounding quotes
  399. $sToken = substr($address,$i+1,$iEnd - $i -1);
  400. $i = $iEnd;
  401. }
  402. $sToken = str_replace($aReplace, $aSpecials,$sToken);
  403. if ($sToken) $aTokens[] = $sToken;
  404. break;
  405. case '(':
  406. array_pop($aTokens); //remove inserted space
  407. $iEnd = strpos($address,')',$i);
  408. if (!$iEnd) {
  409. $sToken = substr($address,$i);
  410. $i = $iCnt;
  411. } else {
  412. $iDepth = 1;
  413. $iComment = $i;
  414. while (($iDepth > 0) && (++$iComment < $iCnt)) {
  415. $cCharComment = $address{$iComment};
  416. switch($cCharComment) {
  417. case '\\':
  418. ++$iComment;
  419. break;
  420. case '(':
  421. ++$iDepth;
  422. break;
  423. case ')':
  424. --$iDepth;
  425. break;
  426. default:
  427. break;
  428. }
  429. }
  430. if ($iDepth == 0) {
  431. $sToken = substr($address,$i,$iComment - $i +1);
  432. $i = $iComment;
  433. } else {
  434. $sToken = substr($address,$i,$iEnd - $i + 1);
  435. $i = $iEnd;
  436. }
  437. }
  438. // check the next token in case comments appear in the middle of email addresses
  439. $prevToken = end($aTokens);
  440. if (!in_array($prevToken,$aSpecials,true)) {
  441. if ($i+1<strlen($address) && !in_array($address{$i+1},$aSpecials,true)) {
  442. $iEnd = strpos($address,' ',$i+1);
  443. if ($iEnd) {
  444. $sNextToken = trim(substr($address,$i+1,$iEnd - $i -1));
  445. $i = $iEnd-1;
  446. } else {
  447. $sNextToken = trim(substr($address,$i+1));
  448. $i = $iCnt;
  449. }
  450. // remove the token
  451. array_pop($aTokens);
  452. // create token and add it again
  453. $sNewToken = $prevToken . $sNextToken;
  454. if($sNewToken) $aTokens[] = $sNewToken;
  455. }
  456. }
  457. $sToken = str_replace($aReplace, $aSpecials,$sToken);
  458. if ($sToken) $aTokens[] = $sToken;
  459. break;
  460. case ',':
  461. case ':':
  462. case ';':
  463. case ' ':
  464. $aTokens[] = $cChar;
  465. break;
  466. default:
  467. $iEnd = strpos($address,' ',$i+1);
  468. if ($iEnd) {
  469. $sToken = trim(substr($address,$i,$iEnd - $i));
  470. $i = $iEnd-1;
  471. } else {
  472. $sToken = trim(substr($address,$i));
  473. $i = $iCnt;
  474. }
  475. if ($sToken) $aTokens[] = $sToken;
  476. }
  477. ++$i;
  478. }
  479. return $aTokens;
  480. }
  481. /**
  482. * @param array $aStack
  483. * @param array $aComment
  484. * @param string $sEmail
  485. * @param string $sGroup
  486. * @return object AddressStructure object
  487. */
  488. function createAddressObject(&$aStack,&$aComment,&$sEmail,$sGroup='') {
  489. //$aStack=explode(' ',implode('',$aStack));
  490. if (!$sEmail) {
  491. while (count($aStack) && !$sEmail) {
  492. $sEmail = trim(array_pop($aStack));
  493. }
  494. }
  495. if (count($aStack)) {
  496. $sPersonal = trim(implode('',$aStack));
  497. } else {
  498. $sPersonal = '';
  499. }
  500. if (!$sPersonal && count($aComment)) {
  501. $sComment = trim(implode(' ',$aComment));
  502. $sPersonal .= $sComment;
  503. }
  504. $oAddr = new AddressStructure();
  505. if ($sPersonal && substr($sPersonal,0,2) == '=?') {
  506. $oAddr->personal = encodeHeader($sPersonal);
  507. } else {
  508. $oAddr->personal = $sPersonal;
  509. }
  510. // $oAddr->group = $sGroup;
  511. $iPosAt = strpos($sEmail,'@');
  512. if ($iPosAt) {
  513. $oAddr->mailbox = substr($sEmail, 0, $iPosAt);
  514. $oAddr->host = substr($sEmail, $iPosAt+1);
  515. } else {
  516. $oAddr->mailbox = $sEmail;
  517. $oAddr->host = false;
  518. }
  519. $sEmail = '';
  520. $aStack = $aComment = array();
  521. return $oAddr;
  522. }
  523. /**
  524. * recursive function for parsing address strings and storing them in an address stucture object.
  525. * personal name: encoded: =?charset?Q|B?string?=
  526. * quoted: "string"
  527. * normal: string
  528. * email : <mailbox@host>
  529. * : mailbox@host
  530. * This function is also used for validating addresses returned from compose
  531. * That's also the reason that the function became a little bit huge
  532. * @param string $address
  533. * @param boolean $ar return array instead of only the first element
  534. * @param array $addr_ar (obsolete) array with parsed addresses
  535. * @param string $group (obsolete)
  536. * @param string $host default domainname in case of addresses without a domainname
  537. * @param string $lookup (since) callback function for lookup of address strings which are probably nicks (without @)
  538. * @return mixed array with AddressStructure objects or only one address_structure object.
  539. */
  540. function parseAddress($address,$ar=false,$aAddress=array(),$sGroup='',$sHost='',$lookup=false) {
  541. $aTokens = $this->getAddressTokens($address);
  542. $sPersonal = $sEmail = $sGroup = '';
  543. $aStack = $aComment = array();
  544. foreach ($aTokens as $sToken) {
  545. $cChar = $sToken{0};
  546. switch ($cChar)
  547. {
  548. case '=':
  549. case '"':
  550. case ' ':
  551. $aStack[] = $sToken;
  552. break;
  553. case '(':
  554. $aComment[] = substr($sToken,1,-1);
  555. break;
  556. case ';':
  557. if ($sGroup) {
  558. $aAddress[] = $this->createAddressObject($aStack,$aComment,$sEmail,$sGroup);
  559. $oAddr = end($aAddress);
  560. if(!$oAddr || ((isset($oAddr)) && !strlen($oAddr->mailbox) && !$oAddr->personal)) {
  561. $sEmail = $sGroup . ':;';
  562. }
  563. $aAddress[] = $this->createAddressObject($aStack,$aComment,$sEmail,$sGroup);
  564. $sGroup = '';
  565. $aStack = $aComment = array();
  566. break;
  567. }
  568. case ',':
  569. $aAddress[] = $this->createAddressObject($aStack,$aComment,$sEmail,$sGroup);
  570. break;
  571. case ':':
  572. $sGroup = trim(implode(' ',$aStack));
  573. $sGroup = preg_replace('/\s+/',' ',$sGroup);
  574. $aStack = array();
  575. break;
  576. case '<':
  577. $sEmail = trim(substr($sToken,1,-1));
  578. break;
  579. case '>':
  580. /* skip */
  581. break;
  582. default: $aStack[] = $sToken; break;
  583. }
  584. }
  585. /* now do the action again for the last address */
  586. $aAddress[] = $this->createAddressObject($aStack,$aComment,$sEmail);
  587. /* try to lookup the addresses in case of invalid email addresses */
  588. $aProcessedAddress = array();
  589. foreach ($aAddress as $oAddr) {
  590. $aAddrBookAddress = array();
  591. if (!$oAddr->host) {
  592. $grouplookup = false;
  593. if ($lookup) {
  594. $aAddr = call_user_func_array($lookup,array($oAddr->mailbox));
  595. if (isset($aAddr['email'])) {
  596. if (strpos($aAddr['email'],',')) {
  597. $grouplookup = true;
  598. $aAddrBookAddress = $this->parseAddress($aAddr['email'],true);
  599. } else {
  600. $iPosAt = strpos($aAddr['email'], '@');
  601. if ($iPosAt === FALSE) {
  602. $oAddr->mailbox = $aAddr['email'];
  603. $oAddr->host = FALSE;
  604. } else {
  605. $oAddr->mailbox = substr($aAddr['email'], 0, $iPosAt);
  606. $oAddr->host = substr($aAddr['email'], $iPosAt+1);
  607. }
  608. if (isset($aAddr['name'])) {
  609. $oAddr->personal = $aAddr['name'];
  610. } else {
  611. $oAddr->personal = encodeHeader($sPersonal);
  612. }
  613. }
  614. }
  615. }
  616. if (!$grouplookup && !strlen($oAddr->mailbox)) {
  617. $oAddr->mailbox = trim($sEmail);
  618. if ($sHost && strlen($oAddr->mailbox)) {
  619. $oAddr->host = $sHost;
  620. }
  621. } else if (!$grouplookup && !$oAddr->host) {
  622. if ($sHost && strlen($oAddr->mailbox)) {
  623. $oAddr->host = $sHost;
  624. }
  625. }
  626. }
  627. if (!$aAddrBookAddress && strlen($oAddr->mailbox)) {
  628. $aProcessedAddress[] = $oAddr;
  629. } else {
  630. $aProcessedAddress = array_merge($aProcessedAddress,$aAddrBookAddress);
  631. }
  632. }
  633. if ($ar) {
  634. return $aProcessedAddress;
  635. } else {
  636. if (isset($aProcessedAddress[0]))
  637. return $aProcessedAddress[0];
  638. else
  639. return '';
  640. }
  641. }
  642. /**
  643. * Normalise the different Priority headers into a uniform value,
  644. * namely that of the X-Priority header (1, 3, 5). Supports:
  645. * Priority, X-Priority, Importance.
  646. * X-MS-Mail-Priority is not parsed because it always coincides
  647. * with one of the other headers.
  648. *
  649. * NOTE: this is actually a duplicate from the code in
  650. * functions/imap_messages:parseFetch().
  651. * I'm not sure if it's ok here to call
  652. * that function?
  653. * @param string $sValue literal priority name
  654. * @return integer
  655. */
  656. function parsePriority($sValue) {
  657. // don't use function call inside array_shift.
  658. $aValue = preg_split('/\s/',trim($sValue));
  659. $value = strtolower(array_shift($aValue));
  660. if ( is_numeric($value) ) {
  661. return $value;
  662. }
  663. if ( $value == 'urgent' || $value == 'high' ) {
  664. return 1;
  665. } elseif ( $value == 'non-urgent' || $value == 'low' ) {
  666. return 5;
  667. }
  668. // default is normal priority
  669. return 3;
  670. }
  671. /**
  672. * @param string $value content type header
  673. */
  674. function parseContentType($value) {
  675. $pos = strpos($value, ';');
  676. $props = '';
  677. if ($pos > 0) {
  678. $type = trim(substr($value, 0, $pos));
  679. $props = trim(substr($value, $pos+1));
  680. } else {
  681. $type = $value;
  682. }
  683. $content_type = new ContentType($type);
  684. if ($props) {
  685. $properties = $this->parseProperties($props);
  686. if (!isset($properties['charset'])) {
  687. $properties['charset'] = 'us-ascii';
  688. }
  689. $content_type->properties = $this->parseProperties($props);
  690. }
  691. $this->content_type = $content_type;
  692. }
  693. /**
  694. * RFC2184
  695. * @param array $aParameters
  696. * @return array
  697. */
  698. function processParameters($aParameters) {
  699. $aResults = array();
  700. $aCharset = array();
  701. // handle multiline parameters
  702. foreach($aParameters as $key => $value) {
  703. if ($iPos = strpos($key,'*')) {
  704. $sKey = substr($key,0,$iPos);
  705. if (!isset($aResults[$sKey])) {
  706. $aResults[$sKey] = $value;
  707. if (substr($key,-1) == '*') { // parameter contains language/charset info
  708. $aCharset[] = $sKey;
  709. }
  710. } else {
  711. $aResults[$sKey] .= $value;
  712. }
  713. } else {
  714. $aResults[$key] = $value;
  715. }
  716. }
  717. foreach ($aCharset as $key) {
  718. $value = $aResults[$key];
  719. // extract the charset & language
  720. $charset = substr($value,0,strpos($value,"'"));
  721. $value = substr($value,strlen($charset)+1);
  722. $language = substr($value,0,strpos($value,"'"));
  723. $value = substr($value,strlen($charset)+1);
  724. /* FIXME: What's the status of charset decode with language information ????
  725. * Maybe language information contains only ascii text and charset_decode()
  726. * only runs htmlspecialchars() on it. If it contains 8bit information, you
  727. * get html encoded text in charset used by selected translation.
  728. */
  729. $value = charset_decode($charset,$value);
  730. $aResults[$key] = $value;
  731. }
  732. return $aResults;
  733. }
  734. /**
  735. * @param string $value
  736. * @return array
  737. */
  738. function parseProperties($value) {
  739. $propArray = explode(';', $value);
  740. $propResultArray = array();
  741. foreach ($propArray as $prop) {
  742. $prop = trim($prop);
  743. $pos = strpos($prop, '=');
  744. if ($pos > 0) {
  745. $key = trim(substr($prop, 0, $pos));
  746. $val = trim(substr($prop, $pos+1));
  747. if (strlen($val) > 0 && $val{0} == '"') {
  748. $val = substr($val, 1, -1);
  749. }
  750. $propResultArray[$key] = $val;
  751. }
  752. }
  753. return $this->processParameters($propResultArray);
  754. }
  755. /**
  756. * Fills disposition object in rfc822Header object
  757. * @param string $value
  758. */
  759. function parseDisposition($value) {
  760. $pos = strpos($value, ';');
  761. $props = '';
  762. if ($pos > 0) {
  763. $name = trim(substr($value, 0, $pos));
  764. $props = trim(substr($value, $pos+1));
  765. } else {
  766. $name = $value;
  767. }
  768. $props_a = $this->parseProperties($props);
  769. $disp = new Disposition($name);
  770. $disp->properties = $props_a;
  771. $this->disposition = $disp;
  772. }
  773. /**
  774. * Fills mlist array keys in rfc822Header object
  775. * @param string $field
  776. * @param string $value
  777. */
  778. function mlist($field, $value) {
  779. $res_a = array();
  780. $value_a = explode(',', $value);
  781. foreach ($value_a as $val) {
  782. $val = trim($val);
  783. if ($val{0} == '<') {
  784. $val = substr($val, 1, -1);
  785. }
  786. if (substr($val, 0, 7) == 'mailto:') {
  787. $res_a['mailto'] = substr($val, 7);
  788. } else {
  789. $res_a['href'] = $val;
  790. }
  791. }
  792. $this->mlist[$field] = $res_a;
  793. }
  794. /**
  795. * Parses the X-Spam-Status header
  796. * @param string $value
  797. */
  798. function parseSpamStatus($value) {
  799. // Header value looks like this:
  800. // No, score=1.5 required=5.0 tests=MSGID_FROM_MTA_ID,NO_REAL_NAME,UPPERCASE_25_50 autolearn=disabled version=3.1.0-gr0
  801. $spam_status = array();
  802. if (preg_match ('/^(No|Yes),\s+score=(-?\d+\.\d+)\s+required=(-?\d+\.\d+)\s+tests=(.*?)\s+autolearn=(.*?)\s+version=(.+?)$/', $value, $matches)) {
  803. // full header
  804. $spam_status['bad_format'] = 0;
  805. $spam_status['value'] = $matches[0];
  806. // is_spam
  807. if (isset($matches[1])
  808. && strtolower($matches[1]) == 'yes') {
  809. $spam_status['is_spam'] = true;
  810. } else {
  811. $spam_status['is_spam'] = false;
  812. }
  813. // score
  814. $spam_status['score'] = $matches[2];
  815. // required
  816. $spam_status['required'] = $matches[3];
  817. // tests
  818. $tests = array();
  819. $tests = explode(',', $matches[4]);
  820. foreach ($tests as $test) {
  821. $spam_status['tests'][] = trim($test);
  822. }
  823. // autolearn
  824. $spam_status['autolearn'] = $matches[5];
  825. // version
  826. $spam_status['version'] = $matches[6];
  827. } else {
  828. $spam_status['bad_format'] = 1;
  829. $spam_status['value'] = $value;
  830. }
  831. return $spam_status;
  832. }
  833. /**
  834. * function to get the address strings out of the header.
  835. * example1: header->getAddr_s('to').
  836. * example2: header->getAddr_s(array('to', 'cc', 'bcc'))
  837. * @param mixed $arr string or array of strings
  838. * @param string $separator
  839. * @param boolean $encoded (since 1.4.0) return encoded or plain text addresses
  840. * @return string
  841. */
  842. function getAddr_s($arr, $separator = ', ', $encoded=false) {
  843. $s = '';
  844. if (is_array($arr)) {
  845. foreach($arr as $arg) {
  846. if ($this->getAddr_s($arg, $separator, $encoded)) {
  847. $s .= $separator;
  848. }
  849. }
  850. $s = ($s ? substr($s, 2) : $s);
  851. } else {
  852. $addr = $this->{$arr};
  853. if (is_array($addr)) {
  854. foreach ($addr as $addr_o) {
  855. if (is_object($addr_o)) {
  856. if ($encoded) {
  857. $s .= $addr_o->getEncodedAddress() . $separator;
  858. } else {
  859. $s .= $addr_o->getAddress() . $separator;
  860. }
  861. }
  862. }
  863. $s = substr($s, 0, -strlen($separator));
  864. } else {
  865. if (is_object($addr)) {
  866. if ($encoded) {
  867. $s .= $addr->getEncodedAddress();
  868. } else {
  869. $s .= $addr->getAddress();
  870. }
  871. }
  872. }
  873. }
  874. return $s;
  875. }
  876. /**
  877. * function to get the array of addresses out of the header.
  878. * @param mixed $arg string or array of strings
  879. * @param array $excl_arr array of excluded email addresses
  880. * @param array $arr array of added email addresses
  881. * @return array
  882. */
  883. function getAddr_a($arg, $excl_arr = array(), $arr = array()) {
  884. if (is_array($arg)) {
  885. foreach($arg as $argument) {
  886. $arr = $this->getAddr_a($argument, $excl_arr, $arr);
  887. }
  888. } else {
  889. $addr = $this->{$arg};
  890. if (is_array($addr)) {
  891. foreach ($addr as $next_addr) {
  892. if (is_object($next_addr)) {
  893. if (isset($next_addr->host) && ($next_addr->host != '')) {
  894. $email = $next_addr->mailbox . '@' . $next_addr->host;
  895. } else {
  896. $email = $next_addr->mailbox;
  897. }
  898. $email = strtolower($email);
  899. if ($email && !isset($arr[$email]) && !isset($excl_arr[$email])) {
  900. $arr[$email] = $next_addr->personal;
  901. }
  902. }
  903. }
  904. } else {
  905. if (is_object($addr)) {
  906. $email = $addr->mailbox;
  907. $email .= (isset($addr->host) ? '@' . $addr->host : '');
  908. $email = strtolower($email);
  909. if ($email && !isset($arr[$email]) && !isset($excl_arr[$email])) {
  910. $arr[$email] = $addr->personal;
  911. }
  912. }
  913. }
  914. }
  915. return $arr;
  916. }
  917. /**
  918. //FIXME: This needs some documentation (inside the function too)! Don't code w/out comments!
  919. * @param mixed $address array or string
  920. * @param boolean $recurs
  921. * @return mixed array, boolean
  922. * @since 1.3.2
  923. */
  924. function findAddress($address, $recurs = false) {
  925. $result = false;
  926. if (is_array($address)) {
  927. $i=0;
  928. foreach($address as $argument) {
  929. $match = $this->findAddress($argument, true);
  930. if ($match[1]) {
  931. return $i;
  932. } else {
  933. if (count($match[0]) && !$result) {
  934. $result = $i;
  935. }
  936. }
  937. ++$i;
  938. }
  939. } else {
  940. if (!is_array($this->cc)) $this->cc = array();
  941. if (!is_array($this->to)) $this->to = array();
  942. $srch_addr = $this->parseAddress($address);
  943. $results = array();
  944. foreach ($this->to as $to) {
  945. if (strtolower($to->host) == strtolower($srch_addr->host)) {
  946. if (strtolower($to->mailbox) == strtolower($srch_addr->mailbox)) {
  947. $results[] = $srch_addr;
  948. if (strtolower($to->personal) == strtolower($srch_addr->personal)) {
  949. if ($recurs) {
  950. return array($results, true);
  951. } else {
  952. return true;
  953. }
  954. }
  955. }
  956. }
  957. }
  958. foreach ($this->cc as $cc) {
  959. if (strtolower($cc->host) == strtolower($srch_addr->host)) {
  960. if (strtolower($cc->mailbox) == strtolower($srch_addr->mailbox)) {
  961. $results[] = $srch_addr;
  962. if (strtolower($cc->personal) == strtolower($srch_addr->personal)) {
  963. if ($recurs) {
  964. return array($results, true);
  965. } else {
  966. return true;
  967. }
  968. }
  969. }
  970. }
  971. }
  972. if ($recurs) {
  973. return array($results, false);
  974. } elseif (count($result)) {
  975. return true;
  976. } else {
  977. return false;
  978. }
  979. }
  980. //exit;
  981. return $result;
  982. }
  983. /**
  984. * @param string $type0 media type
  985. * @param string $type1 media subtype
  986. * @return array media properties
  987. * @todo check use of media type arguments
  988. */
  989. function getContentType($type0, $type1) {
  990. $type0 = $this->content_type->type0;
  991. $type1 = $this->content_type->type1;
  992. return $this->content_type->properties;
  993. }
  994. }