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