Rfc822Header.class.php 33 KB

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