Missed a spot. Please use less accusatory language when telling ppl they broke plugi...
[squirrelmail.git] / class / mime / Rfc822Header.class.php
1 <?php
2
3 /**
4 * Rfc822Header.class.php
5 *
6 * This file contains functions needed to handle headers in mime messages.
7 *
8 * @copyright &copy; 2003-2006 The SquirrelMail Project Team
9 * @license http://opensource.org/licenses/gpl-license.php GNU Public License
10 * @version $Id$
11 * @package squirrelmail
12 * @subpackage mime
13 * @since 1.3.2
14 */
15
16 /**
17 * MIME header class
18 * input: header_string or array
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.
22 * @package squirrelmail
23 * @subpackage mime
24 * @since 1.3.0
25 */
26 class Rfc822Header {
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 /**
91 * Content Type object
92 * @var object
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 /**
110 * Disposition notification for requesting message delivery notification (MDN)
111 * @var mixed
112 */
113 var $dnt = '';
114 /**
115 * Delivery notification (DR)
116 * @var mixed
117 */
118 var $drnt = '';
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();
135 /**
136 * Extra header
137 * only needed for constructing headers in delivery class
138 * @var array
139 */
140 var $more_headers = array();
141
142 /**
143 * @param mixed $hdr string or array with message headers
144 */
145 function parseHeader($hdr) {
146 if (is_array($hdr)) {
147 $hdr = implode('', $hdr);
148 }
149 /* First we replace \r\n by \n and unfold the header */
150 /* FIXME: unfolding header with multiple spaces "\n( +)" */
151 $hdr = trim(str_replace(array("\r\n", "\n\t", "\n "),array("\n", ' ', ' '), $hdr));
152
153 /* Now we can make a new header array with */
154 /* each element representing a headerline */
155 $hdr = explode("\n" , $hdr);
156 foreach ($hdr as $line) {
157 $pos = strpos($line, ':');
158 if ($pos > 0) {
159 $field = substr($line, 0, $pos);
160 if (!strstr($field,' ')) { /* valid field */
161 $value = trim(substr($line, $pos+1));
162 $this->parseField($field, $value);
163 }
164 }
165 }
166 if (!is_object($this->content_type)) {
167 $this->parseContentType('text/plain; charset=us-ascii');
168 }
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 /**
220 * Parse header field according to field type
221 * @param string $field field name
222 * @param string $value field value
223 */
224 function parseField($field, $value) {
225 $field = strtolower($field);
226 switch($field) {
227 case 'date':
228 $value = $this->stripComments($value);
229 $d = strtr($value, array(' ' => ' '));
230 $d = explode(' ', $d);
231 $this->date = getTimeStamp($d);
232 break;
233 case 'subject':
234 $this->subject = $value;
235 break;
236 case 'from':
237 $this->from = $this->parseAddress($value,true);
238 break;
239 case 'sender':
240 $this->sender = $this->parseAddress($value);
241 break;
242 case 'reply-to':
243 $this->reply_to = $this->parseAddress($value, true);
244 break;
245 case 'mail-followup-to':
246 $this->mail_followup_to = $this->parseAddress($value, true);
247 break;
248 case 'to':
249 $this->to = $this->parseAddress($value, true);
250 break;
251 case 'cc':
252 $this->cc = $this->parseAddress($value, true);
253 break;
254 case 'bcc':
255 $this->bcc = $this->parseAddress($value, true);
256 break;
257 case 'in-reply-to':
258 $this->in_reply_to = $value;
259 break;
260 case 'message-id':
261 $value = $this->stripComments($value);
262 $this->message_id = $value;
263 break;
264 case 'references':
265 $value = $this->stripComments($value);
266 $this->references = $value;
267 break;
268 case 'x-confirm-reading-to':
269 case 'disposition-notification-to':
270 $value = $this->stripComments($value);
271 $this->dnt = $this->parseAddress($value);
272 break;
273 case 'return-receipt-to':
274 $value = $this->stripComments($value);
275 $this->drnt = $this->parseAddress($value);
276 break;
277 case 'mime-version':
278 $value = $this->stripComments($value);
279 $value = str_replace(' ', '', $value);
280 $this->mime = ($value == '1.0' ? true : $this->mime);
281 break;
282 case 'content-type':
283 $value = $this->stripComments($value);
284 $this->parseContentType($value);
285 break;
286 case 'content-disposition':
287 $value = $this->stripComments($value);
288 $this->parseDisposition($value);
289 break;
290 case 'content-transfer-encoding':
291 $this->encoding = $value;
292 break;
293 case 'content-description':
294 $this->content_desc = $value;
295 break;
296 case 'content-id':
297 $value = $this->stripComments($value);
298 $this->content_id = $value;
299 break;
300 case 'user-agent':
301 case 'x-mailer':
302 $this->xmailer = $value;
303 break;
304 case 'x-priority':
305 case 'importance':
306 case 'priority':
307 $this->priority = $this->parsePriority($value);
308 break;
309 case 'list-post':
310 $value = $this->stripComments($value);
311 $this->mlist('post', $value);
312 break;
313 case 'list-reply':
314 $value = $this->stripComments($value);
315 $this->mlist('reply', $value);
316 break;
317 case 'list-subscribe':
318 $value = $this->stripComments($value);
319 $this->mlist('subscribe', $value);
320 break;
321 case 'list-unsubscribe':
322 $value = $this->stripComments($value);
323 $this->mlist('unsubscribe', $value);
324 break;
325 case 'list-archive':
326 $value = $this->stripComments($value);
327 $this->mlist('archive', $value);
328 break;
329 case 'list-owner':
330 $value = $this->stripComments($value);
331 $this->mlist('owner', $value);
332 break;
333 case 'list-help':
334 $value = $this->stripComments($value);
335 $this->mlist('help', $value);
336 break;
337 case 'list-id':
338 $value = $this->stripComments($value);
339 $this->mlist('id', $value);
340 break;
341 default:
342 break;
343 }
344 }
345
346 /**
347 * @param string $address
348 * @return array
349 */
350 function getAddressTokens($address) {
351 $aTokens = array();
352 $aSpecials = array('(' ,'<' ,',' ,';' ,':');
353 $aReplace = array(' (',' <',' ,',' ;',' :');
354 $address = str_replace($aSpecials,$aReplace,$address);
355 $iCnt = strlen($address);
356 $i = 0;
357 while ($i < $iCnt) {
358 $cChar = $address{$i};
359 switch($cChar)
360 {
361 case '<':
362 $iEnd = strpos($address,'>',$i+1);
363 if (!$iEnd) {
364 $sToken = substr($address,$i);
365 $i = $iCnt;
366 } else {
367 $sToken = substr($address,$i,$iEnd - $i +1);
368 $i = $iEnd;
369 }
370 $sToken = str_replace($aReplace, $aSpecials,$sToken);
371 if ($sToken) $aTokens[] = $sToken;
372 break;
373 case '"':
374 $iEnd = strpos($address,$cChar,$i+1);
375 if ($iEnd) {
376 // skip escaped quotes
377 $prev_char = $address{$iEnd-1};
378 while ($prev_char === '\\' && substr($address,$iEnd-2,2) !== '\\\\') {
379 $iEnd = strpos($address,$cChar,$iEnd+1);
380 if ($iEnd) {
381 $prev_char = $address{$iEnd-1};
382 } else {
383 $prev_char = false;
384 }
385 }
386 }
387 if (!$iEnd) {
388 $sToken = substr($address,$i);
389 $i = $iCnt;
390 } else {
391 // also remove the surrounding quotes
392 $sToken = substr($address,$i+1,$iEnd - $i -1);
393 $i = $iEnd;
394 }
395 $sToken = str_replace($aReplace, $aSpecials,$sToken);
396 if ($sToken) $aTokens[] = $sToken;
397 break;
398 case '(':
399 array_pop($aTokens); //remove inserted space
400 $iEnd = strpos($address,')',$i);
401 if (!$iEnd) {
402 $sToken = substr($address,$i);
403 $i = $iCnt;
404 } else {
405 $iDepth = 1;
406 $iComment = $i;
407 while (($iDepth > 0) && (++$iComment < $iCnt)) {
408 $cCharComment = $address{$iComment};
409 switch($cCharComment) {
410 case '\\':
411 ++$iComment;
412 break;
413 case '(':
414 ++$iDepth;
415 break;
416 case ')':
417 --$iDepth;
418 break;
419 default:
420 break;
421 }
422 }
423 if ($iDepth == 0) {
424 $sToken = substr($address,$i,$iComment - $i +1);
425 $i = $iComment;
426 } else {
427 $sToken = substr($address,$i,$iEnd - $i + 1);
428 $i = $iEnd;
429 }
430 }
431 // check the next token in case comments appear in the middle of email addresses
432 $prevToken = end($aTokens);
433 if (!in_array($prevToken,$aSpecials,true)) {
434 if ($i+1<strlen($address) && !in_array($address{$i+1},$aSpecials,true)) {
435 $iEnd = strpos($address,' ',$i+1);
436 if ($iEnd) {
437 $sNextToken = trim(substr($address,$i+1,$iEnd - $i -1));
438 $i = $iEnd-1;
439 } else {
440 $sNextToken = trim(substr($address,$i+1));
441 $i = $iCnt;
442 }
443 // remove the token
444 array_pop($aTokens);
445 // create token and add it again
446 $sNewToken = $prevToken . $sNextToken;
447 if($sNewToken) $aTokens[] = $sNewToken;
448 }
449 }
450 $sToken = str_replace($aReplace, $aSpecials,$sToken);
451 if ($sToken) $aTokens[] = $sToken;
452 break;
453 case ',':
454 case ':':
455 case ';':
456 case ' ':
457 $aTokens[] = $cChar;
458 break;
459 default:
460 $iEnd = strpos($address,' ',$i+1);
461 if ($iEnd) {
462 $sToken = trim(substr($address,$i,$iEnd - $i));
463 $i = $iEnd-1;
464 } else {
465 $sToken = trim(substr($address,$i));
466 $i = $iCnt;
467 }
468 if ($sToken) $aTokens[] = $sToken;
469 }
470 ++$i;
471 }
472 return $aTokens;
473 }
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 /**
519 * recursive function for parsing address strings and storing them in an address stucture object.
520 * personal name: encoded: =?charset?Q|B?string?=
521 * quoted: "string"
522 * normal: string
523 * email : <mailbox@host>
524 * : mailbox@host
525 * This function is also used for validating addresses returned from compose
526 * That's also the reason that the function became a little bit huge
527 * @param string $address
528 * @param boolean $ar return array instead of only the first element
529 * @param array $addr_ar (obsolete) array with parsed addresses
530 * @param string $group (obsolete)
531 * @param string $host default domainname in case of addresses without a domainname
532 * @param string $lookup (since) callback function for lookup of address strings which are probably nicks (without @)
533 * @return mixed array with AddressStructure objects or only one address_structure object.
534 */
535 function parseAddress($address,$ar=false,$aAddress=array(),$sGroup='',$sHost='',$lookup=false) {
536 $aTokens = $this->getAddressTokens($address);
537 $sPersonal = $sEmail = $sGroup = '';
538 $aStack = $aComment = array();
539 foreach ($aTokens as $sToken) {
540 $cChar = $sToken{0};
541 switch ($cChar)
542 {
543 case '=':
544 case '"':
545 case ' ':
546 $aStack[] = $sToken;
547 break;
548 case '(':
549 $aComment[] = substr($sToken,1,-1);
550 break;
551 case ';':
552 if ($sGroup) {
553 $aAddress[] = $this->createAddressObject($aStack,$aComment,$sEmail,$sGroup);
554 $oAddr = end($aAddress);
555 if(!$oAddr || ((isset($oAddr)) && !$oAddr->mailbox && !$oAddr->personal)) {
556 $sEmail = $sGroup . ':;';
557 }
558 $aAddress[] = $this->createAddressObject($aStack,$aComment,$sEmail,$sGroup);
559 $sGroup = '';
560 $aStack = $aComment = array();
561 break;
562 }
563 case ',':
564 $aAddress[] = $this->createAddressObject($aStack,$aComment,$sEmail,$sGroup);
565 break;
566 case ':':
567 $sGroup = trim(implode(' ',$aStack));
568 $sGroup = preg_replace('/\s+/',' ',$sGroup);
569 $aStack = array();
570 break;
571 case '<':
572 $sEmail = trim(substr($sToken,1,-1));
573 break;
574 case '>':
575 /* skip */
576 break;
577 default: $aStack[] = $sToken; break;
578 }
579 }
580 /* now do the action again for the last address */
581 $aAddress[] = $this->createAddressObject($aStack,$aComment,$sEmail);
582 /* try to lookup the addresses in case of invalid email addresses */
583 $aProcessedAddress = array();
584 foreach ($aAddress as $oAddr) {
585 $aAddrBookAddress = array();
586 if (!$oAddr->host) {
587 $grouplookup = false;
588 if ($lookup) {
589 $aAddr = call_user_func_array($lookup,array($oAddr->mailbox));
590 if (isset($aAddr['email'])) {
591 if (strpos($aAddr['email'],',')) {
592 $grouplookup = true;
593 $aAddrBookAddress = $this->parseAddress($aAddr['email'],true);
594 } else {
595 $iPosAt = strpos($aAddr['email'], '@');
596 $oAddr->mailbox = substr($aAddr['email'], 0, $iPosAt);
597 $oAddr->host = substr($aAddr['email'], $iPosAt+1);
598 if (isset($aAddr['name'])) {
599 $oAddr->personal = $aAddr['name'];
600 } else {
601 $oAddr->personal = encodeHeader($sPersonal);
602 }
603 }
604 }
605 }
606 if (!$grouplookup && !$oAddr->mailbox) {
607 $oAddr->mailbox = trim($sEmail);
608 if ($sHost && $oAddr->mailbox) {
609 $oAddr->host = $sHost;
610 }
611 } else if (!$grouplookup && !$oAddr->host) {
612 if ($sHost && $oAddr->mailbox) {
613 $oAddr->host = $sHost;
614 }
615 }
616 }
617 if (!$aAddrBookAddress && $oAddr->mailbox) {
618 $aProcessedAddress[] = $oAddr;
619 } else {
620 $aProcessedAddress = array_merge($aProcessedAddress,$aAddrBookAddress);
621 }
622 }
623 if ($ar) {
624 return $aProcessedAddress;
625 } else {
626 return $aProcessedAddress[0];
627 }
628 }
629
630 /**
631 * Normalise the different Priority headers into a uniform value,
632 * namely that of the X-Priority header (1, 3, 5). Supports:
633 * Priority, X-Priority, Importance.
634 * X-MS-Mail-Priority is not parsed because it always coincides
635 * with one of the other headers.
636 *
637 * NOTE: this is actually a duplicate from the function in
638 * functions/imap_messages. I'm not sure if it's ok here to call
639 * that function?
640 * @param string $sValue literal priority name
641 * @return integer
642 */
643 function parsePriority($sValue) {
644 // don't use function call inside array_shift.
645 $aValue = split('/\w/',trim($sValue));
646 $value = strtolower(array_shift($aValue));
647
648 if ( is_numeric($value) ) {
649 return $value;
650 }
651 if ( $value == 'urgent' || $value == 'high' ) {
652 return 1;
653 } elseif ( $value == 'non-urgent' || $value == 'low' ) {
654 return 5;
655 }
656 // default is normal priority
657 return 3;
658 }
659
660 /**
661 * @param string $value content type header
662 */
663 function parseContentType($value) {
664 $pos = strpos($value, ';');
665 $props = '';
666 if ($pos > 0) {
667 $type = trim(substr($value, 0, $pos));
668 $props = trim(substr($value, $pos+1));
669 } else {
670 $type = $value;
671 }
672 $content_type = new ContentType($type);
673 if ($props) {
674 $properties = $this->parseProperties($props);
675 if (!isset($properties['charset'])) {
676 $properties['charset'] = 'us-ascii';
677 }
678 $content_type->properties = $this->parseProperties($props);
679 }
680 $this->content_type = $content_type;
681 }
682
683 /**
684 * RFC2184
685 * @param array $aParameters
686 * @return array
687 */
688 function processParameters($aParameters) {
689 $aResults = array();
690 $aCharset = array();
691 // handle multiline parameters
692 foreach($aParameters as $key => $value) {
693 if ($iPos = strpos($key,'*')) {
694 $sKey = substr($key,0,$iPos);
695 if (!isset($aResults[$sKey])) {
696 $aResults[$sKey] = $value;
697 if (substr($key,-1) == '*') { // parameter contains language/charset info
698 $aCharset[] = $sKey;
699 }
700 } else {
701 $aResults[$sKey] .= $value;
702 }
703 } else {
704 $aResults[$key] = $value;
705 }
706 }
707 foreach ($aCharset as $key) {
708 $value = $aResults[$key];
709 // extract the charset & language
710 $charset = substr($value,0,strpos($value,"'"));
711 $value = substr($value,strlen($charset)+1);
712 $language = substr($value,0,strpos($value,"'"));
713 $value = substr($value,strlen($charset)+1);
714 /* FIXME: What's the status of charset decode with language information ????
715 * Maybe language information contains only ascii text and charset_decode()
716 * only runs htmlspecialchars() on it. If it contains 8bit information, you
717 * get html encoded text in charset used by selected translation.
718 */
719 $value = charset_decode($charset,$value);
720 $aResults[$key] = $value;
721 }
722 return $aResults;
723 }
724
725 /**
726 * @param string $value
727 * @return array
728 */
729 function parseProperties($value) {
730 $propArray = explode(';', $value);
731 $propResultArray = array();
732 foreach ($propArray as $prop) {
733 $prop = trim($prop);
734 $pos = strpos($prop, '=');
735 if ($pos > 0) {
736 $key = trim(substr($prop, 0, $pos));
737 $val = trim(substr($prop, $pos+1));
738 if (strlen($val) > 0 && $val{0} == '"') {
739 $val = substr($val, 1, -1);
740 }
741 $propResultArray[$key] = $val;
742 }
743 }
744 return $this->processParameters($propResultArray);
745 }
746
747 /**
748 * Fills disposition object in rfc822Header object
749 * @param string $value
750 */
751 function parseDisposition($value) {
752 $pos = strpos($value, ';');
753 $props = '';
754 if ($pos > 0) {
755 $name = trim(substr($value, 0, $pos));
756 $props = trim(substr($value, $pos+1));
757 } else {
758 $name = $value;
759 }
760 $props_a = $this->parseProperties($props);
761 $disp = new Disposition($name);
762 $disp->properties = $props_a;
763 $this->disposition = $disp;
764 }
765
766 /**
767 * Fills mlist array keys in rfc822Header object
768 * @param string $field
769 * @param string $value
770 */
771 function mlist($field, $value) {
772 $res_a = array();
773 $value_a = explode(',', $value);
774 foreach ($value_a as $val) {
775 $val = trim($val);
776 if ($val{0} == '<') {
777 $val = substr($val, 1, -1);
778 }
779 if (substr($val, 0, 7) == 'mailto:') {
780 $res_a['mailto'] = substr($val, 7);
781 } else {
782 $res_a['href'] = $val;
783 }
784 }
785 $this->mlist[$field] = $res_a;
786 }
787
788 /**
789 * function to get the address strings out of the header.
790 * example1: header->getAddr_s('to').
791 * example2: header->getAddr_s(array('to', 'cc', 'bcc'))
792 * @param mixed $arr string or array of strings
793 * @param string $separator
794 * @param boolean $encoded (since 1.4.0) return encoded or plain text addresses
795 * @return string
796 */
797 function getAddr_s($arr, $separator = ',',$encoded=false) {
798 $s = '';
799
800 if (is_array($arr)) {
801 foreach($arr as $arg) {
802 if ($this->getAddr_s($arg, $separator, $encoded)) {
803 $s .= $separator;
804 }
805 }
806 $s = ($s ? substr($s, 2) : $s);
807 } else {
808 $addr = $this->{$arr};
809 if (is_array($addr)) {
810 foreach ($addr as $addr_o) {
811 if (is_object($addr_o)) {
812 if ($encoded) {
813 $s .= $addr_o->getEncodedAddress() . $separator;
814 } else {
815 $s .= $addr_o->getAddress() . $separator;
816 }
817 }
818 }
819 $s = substr($s, 0, -strlen($separator));
820 } else {
821 if (is_object($addr)) {
822 if ($encoded) {
823 $s .= $addr->getEncodedAddress();
824 } else {
825 $s .= $addr->getAddress();
826 }
827 }
828 }
829 }
830 return $s;
831 }
832
833 /**
834 * function to get the array of addresses out of the header.
835 * @param mixed $arg string or array of strings
836 * @param array $excl_arr array of excluded email addresses
837 * @param array $arr array of added email addresses
838 * @return array
839 */
840 function getAddr_a($arg, $excl_arr = array(), $arr = array()) {
841 if (is_array($arg)) {
842 foreach($arg as $argument) {
843 $arr = $this->getAddr_a($argument, $excl_arr, $arr);
844 }
845 } else {
846 $addr = $this->{$arg};
847 if (is_array($addr)) {
848 foreach ($addr as $next_addr) {
849 if (is_object($next_addr)) {
850 if (isset($next_addr->host) && ($next_addr->host != '')) {
851 $email = $next_addr->mailbox . '@' . $next_addr->host;
852 } else {
853 $email = $next_addr->mailbox;
854 }
855 $email = strtolower($email);
856 if ($email && !isset($arr[$email]) && !isset($excl_arr[$email])) {
857 $arr[$email] = $next_addr->personal;
858 }
859 }
860 }
861 } else {
862 if (is_object($addr)) {
863 $email = $addr->mailbox;
864 $email .= (isset($addr->host) ? '@' . $addr->host : '');
865 $email = strtolower($email);
866 if ($email && !isset($arr[$email]) && !isset($excl_arr[$email])) {
867 $arr[$email] = $addr->personal;
868 }
869 }
870 }
871 }
872 return $arr;
873 }
874
875 /**
876 * @param mixed $address array or string
877 * @param boolean $recurs
878 * @return mixed array, boolean
879 * @since 1.3.2
880 */
881 function findAddress($address, $recurs = false) {
882 $result = false;
883 if (is_array($address)) {
884 $i=0;
885 foreach($address as $argument) {
886 $match = $this->findAddress($argument, true);
887 if ($match[1]) {
888 return $i;
889 } else {
890 if (count($match[0]) && !$result) {
891 $result = $i;
892 }
893 }
894 ++$i;
895 }
896 } else {
897 if (!is_array($this->cc)) $this->cc = array();
898 $srch_addr = $this->parseAddress($address);
899 $results = array();
900 foreach ($this->to as $to) {
901 if ($to->host == $srch_addr->host) {
902 if ($to->mailbox == $srch_addr->mailbox) {
903 $results[] = $srch_addr;
904 if ($to->personal == $srch_addr->personal) {
905 if ($recurs) {
906 return array($results, true);
907 } else {
908 return true;
909 }
910 }
911 }
912 }
913 }
914 foreach ($this->cc as $cc) {
915 if ($cc->host == $srch_addr->host) {
916 if ($cc->mailbox == $srch_addr->mailbox) {
917 $results[] = $srch_addr;
918 if ($cc->personal == $srch_addr->personal) {
919 if ($recurs) {
920 return array($results, true);
921 } else {
922 return true;
923 }
924 }
925 }
926 }
927 }
928 if ($recurs) {
929 return array($results, false);
930 } elseif (count($result)) {
931 return true;
932 } else {
933 return false;
934 }
935 }
936 //exit;
937 return $result;
938 }
939
940 /**
941 * @param string $type0 media type
942 * @param string $type1 media subtype
943 * @return array media properties
944 * @todo check use of media type arguments
945 */
946 function getContentType($type0, $type1) {
947 $type0 = $this->content_type->type0;
948 $type1 = $this->content_type->type1;
949 return $this->content_type->properties;
950 }
951 }