Rfc822Header.class.php 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020
  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. return $aProcessedAddress[0];
  637. }
  638. }
  639. /**
  640. * Normalise the different Priority headers into a uniform value,
  641. * namely that of the X-Priority header (1, 3, 5). Supports:
  642. * Priority, X-Priority, Importance.
  643. * X-MS-Mail-Priority is not parsed because it always coincides
  644. * with one of the other headers.
  645. *
  646. * NOTE: this is actually a duplicate from the code in
  647. * functions/imap_messages:parseFetch().
  648. * I'm not sure if it's ok here to call
  649. * that function?
  650. * @param string $sValue literal priority name
  651. * @return integer
  652. */
  653. function parsePriority($sValue) {
  654. // don't use function call inside array_shift.
  655. $aValue = preg_split('/\s/',trim($sValue));
  656. $value = strtolower(array_shift($aValue));
  657. if ( is_numeric($value) ) {
  658. return $value;
  659. }
  660. if ( $value == 'urgent' || $value == 'high' ) {
  661. return 1;
  662. } elseif ( $value == 'non-urgent' || $value == 'low' ) {
  663. return 5;
  664. }
  665. // default is normal priority
  666. return 3;
  667. }
  668. /**
  669. * @param string $value content type header
  670. */
  671. function parseContentType($value) {
  672. $pos = strpos($value, ';');
  673. $props = '';
  674. if ($pos > 0) {
  675. $type = trim(substr($value, 0, $pos));
  676. $props = trim(substr($value, $pos+1));
  677. } else {
  678. $type = $value;
  679. }
  680. $content_type = new ContentType($type);
  681. if ($props) {
  682. $properties = $this->parseProperties($props);
  683. if (!isset($properties['charset'])) {
  684. $properties['charset'] = 'us-ascii';
  685. }
  686. $content_type->properties = $this->parseProperties($props);
  687. }
  688. $this->content_type = $content_type;
  689. }
  690. /**
  691. * RFC2184
  692. * @param array $aParameters
  693. * @return array
  694. */
  695. function processParameters($aParameters) {
  696. $aResults = array();
  697. $aCharset = array();
  698. // handle multiline parameters
  699. foreach($aParameters as $key => $value) {
  700. if ($iPos = strpos($key,'*')) {
  701. $sKey = substr($key,0,$iPos);
  702. if (!isset($aResults[$sKey])) {
  703. $aResults[$sKey] = $value;
  704. if (substr($key,-1) == '*') { // parameter contains language/charset info
  705. $aCharset[] = $sKey;
  706. }
  707. } else {
  708. $aResults[$sKey] .= $value;
  709. }
  710. } else {
  711. $aResults[$key] = $value;
  712. }
  713. }
  714. foreach ($aCharset as $key) {
  715. $value = $aResults[$key];
  716. // extract the charset & language
  717. $charset = substr($value,0,strpos($value,"'"));
  718. $value = substr($value,strlen($charset)+1);
  719. $language = substr($value,0,strpos($value,"'"));
  720. $value = substr($value,strlen($charset)+1);
  721. /* FIXME: What's the status of charset decode with language information ????
  722. * Maybe language information contains only ascii text and charset_decode()
  723. * only runs htmlspecialchars() on it. If it contains 8bit information, you
  724. * get html encoded text in charset used by selected translation.
  725. */
  726. $value = charset_decode($charset,$value);
  727. $aResults[$key] = $value;
  728. }
  729. return $aResults;
  730. }
  731. /**
  732. * @param string $value
  733. * @return array
  734. */
  735. function parseProperties($value) {
  736. $propArray = explode(';', $value);
  737. $propResultArray = array();
  738. foreach ($propArray as $prop) {
  739. $prop = trim($prop);
  740. $pos = strpos($prop, '=');
  741. if ($pos > 0) {
  742. $key = trim(substr($prop, 0, $pos));
  743. $val = trim(substr($prop, $pos+1));
  744. if (strlen($val) > 0 && $val{0} == '"') {
  745. $val = substr($val, 1, -1);
  746. }
  747. $propResultArray[$key] = $val;
  748. }
  749. }
  750. return $this->processParameters($propResultArray);
  751. }
  752. /**
  753. * Fills disposition object in rfc822Header object
  754. * @param string $value
  755. */
  756. function parseDisposition($value) {
  757. $pos = strpos($value, ';');
  758. $props = '';
  759. if ($pos > 0) {
  760. $name = trim(substr($value, 0, $pos));
  761. $props = trim(substr($value, $pos+1));
  762. } else {
  763. $name = $value;
  764. }
  765. $props_a = $this->parseProperties($props);
  766. $disp = new Disposition($name);
  767. $disp->properties = $props_a;
  768. $this->disposition = $disp;
  769. }
  770. /**
  771. * Fills mlist array keys in rfc822Header object
  772. * @param string $field
  773. * @param string $value
  774. */
  775. function mlist($field, $value) {
  776. $res_a = array();
  777. $value_a = explode(',', $value);
  778. foreach ($value_a as $val) {
  779. $val = trim($val);
  780. if ($val{0} == '<') {
  781. $val = substr($val, 1, -1);
  782. }
  783. if (substr($val, 0, 7) == 'mailto:') {
  784. $res_a['mailto'] = substr($val, 7);
  785. } else {
  786. $res_a['href'] = $val;
  787. }
  788. }
  789. $this->mlist[$field] = $res_a;
  790. }
  791. /**
  792. * Parses the X-Spam-Status header
  793. * @param string $value
  794. */
  795. function parseSpamStatus($value) {
  796. // Header value looks like this:
  797. // 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
  798. $spam_status = array();
  799. if (preg_match ('/^(No|Yes),\s+score=(-?\d+\.\d+)\s+required=(-?\d+\.\d+)\s+tests=(.*?)\s+autolearn=(.*?)\s+version=(.+?)$/', $value, $matches)) {
  800. // full header
  801. $spam_status['bad_format'] = 0;
  802. $spam_status['value'] = $matches[0];
  803. // is_spam
  804. if (isset($matches[1])
  805. && strtolower($matches[1]) == 'yes') {
  806. $spam_status['is_spam'] = true;
  807. } else {
  808. $spam_status['is_spam'] = false;
  809. }
  810. // score
  811. $spam_status['score'] = $matches[2];
  812. // required
  813. $spam_status['required'] = $matches[3];
  814. // tests
  815. $tests = array();
  816. $tests = explode(',', $matches[4]);
  817. foreach ($tests as $test) {
  818. $spam_status['tests'][] = trim($test);
  819. }
  820. // autolearn
  821. $spam_status['autolearn'] = $matches[5];
  822. // version
  823. $spam_status['version'] = $matches[6];
  824. } else {
  825. $spam_status['bad_format'] = 1;
  826. $spam_status['value'] = $value;
  827. }
  828. return $spam_status;
  829. }
  830. /**
  831. * function to get the address strings out of the header.
  832. * example1: header->getAddr_s('to').
  833. * example2: header->getAddr_s(array('to', 'cc', 'bcc'))
  834. * @param mixed $arr string or array of strings
  835. * @param string $separator
  836. * @param boolean $encoded (since 1.4.0) return encoded or plain text addresses
  837. * @return string
  838. */
  839. function getAddr_s($arr, $separator = ', ', $encoded=false) {
  840. $s = '';
  841. if (is_array($arr)) {
  842. foreach($arr as $arg) {
  843. if ($this->getAddr_s($arg, $separator, $encoded)) {
  844. $s .= $separator;
  845. }
  846. }
  847. $s = ($s ? substr($s, 2) : $s);
  848. } else {
  849. $addr = $this->{$arr};
  850. if (is_array($addr)) {
  851. foreach ($addr as $addr_o) {
  852. if (is_object($addr_o)) {
  853. if ($encoded) {
  854. $s .= $addr_o->getEncodedAddress() . $separator;
  855. } else {
  856. $s .= $addr_o->getAddress() . $separator;
  857. }
  858. }
  859. }
  860. $s = substr($s, 0, -strlen($separator));
  861. } else {
  862. if (is_object($addr)) {
  863. if ($encoded) {
  864. $s .= $addr->getEncodedAddress();
  865. } else {
  866. $s .= $addr->getAddress();
  867. }
  868. }
  869. }
  870. }
  871. return $s;
  872. }
  873. /**
  874. * function to get the array of addresses out of the header.
  875. * @param mixed $arg string or array of strings
  876. * @param array $excl_arr array of excluded email addresses
  877. * @param array $arr array of added email addresses
  878. * @return array
  879. */
  880. function getAddr_a($arg, $excl_arr = array(), $arr = array()) {
  881. if (is_array($arg)) {
  882. foreach($arg as $argument) {
  883. $arr = $this->getAddr_a($argument, $excl_arr, $arr);
  884. }
  885. } else {
  886. $addr = $this->{$arg};
  887. if (is_array($addr)) {
  888. foreach ($addr as $next_addr) {
  889. if (is_object($next_addr)) {
  890. if (isset($next_addr->host) && ($next_addr->host != '')) {
  891. $email = $next_addr->mailbox . '@' . $next_addr->host;
  892. } else {
  893. $email = $next_addr->mailbox;
  894. }
  895. $email = strtolower($email);
  896. if ($email && !isset($arr[$email]) && !isset($excl_arr[$email])) {
  897. $arr[$email] = $next_addr->personal;
  898. }
  899. }
  900. }
  901. } else {
  902. if (is_object($addr)) {
  903. $email = $addr->mailbox;
  904. $email .= (isset($addr->host) ? '@' . $addr->host : '');
  905. $email = strtolower($email);
  906. if ($email && !isset($arr[$email]) && !isset($excl_arr[$email])) {
  907. $arr[$email] = $addr->personal;
  908. }
  909. }
  910. }
  911. }
  912. return $arr;
  913. }
  914. /**
  915. //FIXME: This needs some documentation (inside the function too)! Don't code w/out comments!
  916. * @param mixed $address array or string
  917. * @param boolean $recurs
  918. * @return mixed array, boolean
  919. * @since 1.3.2
  920. */
  921. function findAddress($address, $recurs = false) {
  922. $result = false;
  923. if (is_array($address)) {
  924. $i=0;
  925. foreach($address as $argument) {
  926. $match = $this->findAddress($argument, true);
  927. if ($match[1]) {
  928. return $i;
  929. } else {
  930. if (count($match[0]) && !$result) {
  931. $result = $i;
  932. }
  933. }
  934. ++$i;
  935. }
  936. } else {
  937. if (!is_array($this->cc)) $this->cc = array();
  938. if (!is_array($this->to)) $this->to = array();
  939. $srch_addr = $this->parseAddress($address);
  940. $results = array();
  941. foreach ($this->to as $to) {
  942. if (strtolower($to->host) == strtolower($srch_addr->host)) {
  943. if (strtolower($to->mailbox) == strtolower($srch_addr->mailbox)) {
  944. $results[] = $srch_addr;
  945. if (strtolower($to->personal) == strtolower($srch_addr->personal)) {
  946. if ($recurs) {
  947. return array($results, true);
  948. } else {
  949. return true;
  950. }
  951. }
  952. }
  953. }
  954. }
  955. foreach ($this->cc as $cc) {
  956. if (strtolower($cc->host) == strtolower($srch_addr->host)) {
  957. if (strtolower($cc->mailbox) == strtolower($srch_addr->mailbox)) {
  958. $results[] = $srch_addr;
  959. if (strtolower($cc->personal) == strtolower($srch_addr->personal)) {
  960. if ($recurs) {
  961. return array($results, true);
  962. } else {
  963. return true;
  964. }
  965. }
  966. }
  967. }
  968. }
  969. if ($recurs) {
  970. return array($results, false);
  971. } elseif (count($result)) {
  972. return true;
  973. } else {
  974. return false;
  975. }
  976. }
  977. //exit;
  978. return $result;
  979. }
  980. /**
  981. * @param string $type0 media type
  982. * @param string $type1 media subtype
  983. * @return array media properties
  984. * @todo check use of media type arguments
  985. */
  986. function getContentType($type0, $type1) {
  987. $type0 = $this->content_type->type0;
  988. $type1 = $this->content_type->type1;
  989. return $this->content_type->properties;
  990. }
  991. }