4 * Rfc822Header.class.php
6 * Copyright (c) 2003 The SquirrelMail Project Team
7 * Licensed under the GNU GPL. For full terms see the file COPYING.
9 * This contains functions needed to handle mime messages.
16 * input: header_string or array
24 $mail_followup_to = array(),
39 $more_headers = array(); /* only needed for constructing headers
41 function parseHeader($hdr) {
43 $hdr = implode('', $hdr);
45 /* First we unfold the header */
46 $hdr = trim(str_replace(array("\r\n\t", "\r\n "),array('', ''), $hdr));
48 /* Now we can make a new header array with */
49 /* each element representing a headerline */
50 $hdr = explode("\r\n" , $hdr);
51 foreach ($hdr as $line) {
52 $pos = strpos($line, ':');
54 $field = substr($line, 0, $pos);
55 if (!strstr($field,' ')) { /* valid field */
56 $value = trim(substr($line, $pos+
1));
57 $this->parseField($field, $value);
61 if ($this->content_type
== '') {
62 $this->parseContentType('text/plain; charset=us-ascii');
66 function stripComments($value) {
68 $cnt = strlen($value);
69 for ($i = 0; $i < $cnt; ++
$i) {
73 while ((++
$i < $cnt) && ($value{$i} != '"')) {
74 if ($value{$i} == '\\') {
78 $result .= $value{$i};
80 $result .= $value{$i};
84 while (($depth > 0) && (++
$i < $cnt)) {
101 $result .= $value{$i};
108 function parseField($field, $value) {
109 $field = strtolower($field);
112 $value = $this->stripComments($value);
113 $d = strtr($value, array(' ' => ' '));
114 $d = explode(' ', $d);
115 $this->date
= getTimeStamp($d);
118 $this->subject
= $value;
121 $this->from
= $this->parseAddress($value,true);
124 $this->sender
= $this->parseAddress($value);
127 $this->reply_to
= $this->parseAddress($value, true);
129 case 'mail-followup-to':
130 $this->mail_followup_to
= $this->parseAddress($value, true);
133 $this->to
= $this->parseAddress($value, true);
136 $this->cc
= $this->parseAddress($value, true);
139 $this->bcc
= $this->parseAddress($value, true);
142 $this->in_reply_to
= $value;
145 $value = $this->stripComments($value);
146 $this->message_id
= $value;
149 $value = $this->stripComments($value);
150 $this->references
= $value;
152 case 'x-confirm-reading-to':
153 case 'return-receipt-to':
154 case 'disposition-notification-to':
155 $value = $this->stripComments($value);
156 $this->dnt
= $this->parseAddress($value);
159 $value = $this->stripComments($value);
160 $value = str_replace(' ', '', $value);
161 $this->mime
= ($value == '1.0' ?
true : $this->mime
);
164 $value = $this->stripComments($value);
165 $this->parseContentType($value);
167 case 'content-disposition':
168 $value = $this->stripComments($value);
169 $this->parseDisposition($value);
173 $this->xmailer
= $value;
176 $this->priority
= $value;
179 $value = $this->stripComments($value);
180 $this->mlist('post', $value);
183 $value = $this->stripComments($value);
184 $this->mlist('reply', $value);
186 case 'list-subscribe':
187 $value = $this->stripComments($value);
188 $this->mlist('subscribe', $value);
190 case 'list-unsubscribe':
191 $value = $this->stripComments($value);
192 $this->mlist('unsubscribe', $value);
195 $value = $this->stripComments($value);
196 $this->mlist('archive', $value);
199 $value = $this->stripComments($value);
200 $this->mlist('owner', $value);
203 $value = $this->stripComments($value);
204 $this->mlist('help', $value);
207 $value = $this->stripComments($value);
208 $this->mlist('id', $value);
215 function getAddressTokens($address) {
218 $aSpecials = array('(' ,'<' ,',' ,';' ,':');
219 $aReplace = array(' (',' <',' ,',' ;',' :');
220 $address = str_replace($aSpecials,$aReplace,$address);
221 $iCnt = strlen($address);
224 $cChar = $address{$i};
228 $iEnd = strpos($address,'>',$i+
1);
230 $sToken = substr($address,$i);
233 $sToken = substr($address,$i,$iEnd - $i +
1);
236 $sToken = str_replace($aReplace, $aSpecials,$sToken);
237 if($sToken) $aTokens[] = $sToken;
240 $iEnd = strpos($address,$cChar,$i+
1);
242 // skip escaped quotes
243 $prev_char = $address{$iEnd-1};
244 while ($prev_char === '\\' && substr($address,$iEnd-2,2) !== '\\\\') {
245 $iEnd = strpos($address,$cChar,$iEnd+
1);
247 $prev_char = $address{$iEnd-1};
254 $sToken = substr($address,$i);
257 // also remove the surrounding quotes
258 $sToken = substr($address,$i+
1,$iEnd - $i -1);
261 $sToken = str_replace($aReplace, $aSpecials,$sToken);
262 if ($sToken) $aTokens[] = $sToken;
265 array_pop($aTokens); //remove inserted space
266 $iEnd = strpos($address,')',$i);
268 $sToken = substr($address,$i);
273 while (($iDepth > 0) && (++
$iComment < $iCnt)) {
274 $cCharComment = $address{$iComment};
275 switch($cCharComment) {
290 $sToken = substr($address,$i,$iComment - $i +
1);
293 $sToken = substr($address,$i,$iEnd - $i +
1);
297 // check the next token in case comments appear in the middle of email addresses
298 $prevToken = end($aTokens);
299 if (!in_array($prevToken,$aSpecials,true)) {
300 if ($i+
1<strlen($address) && !in_array($address{$i+
1},$aSpecials,true)) {
301 $iEnd = strpos($address,' ',$i+
1);
303 $sNextToken = trim(substr($address,$i+
1,$iEnd - $i -1));
306 $sNextToken = trim(substr($address,$i+
1));
311 // create token and add it again
312 $sNewToken = $prevToken . $sNextToken;
313 if($sNewToken) $aTokens[] = $sNewToken;
316 $sToken = str_replace($aReplace, $aSpecials,$sToken);
317 if($sToken) $aTokens[] = $sToken;
326 $iEnd = strpos($address,' ',$i+
1);
328 $sToken = trim(substr($address,$i,$iEnd - $i));
331 $sToken = trim(substr($address,$i));
334 if ($sToken) $aTokens[] = $sToken;
340 function createAddressObject(&$aStack,&$aComment,&$sEmail,$sGroup='') {
341 //$aStack=explode(' ',implode('',$aStack));
343 while (count($aStack) && !$sEmail) {
344 $sEmail = trim(array_pop($aStack));
347 if (count($aStack)) {
348 $sPersonal = trim(implode('',$aStack));
352 if (!$sPersonal && count($aComment)) {
353 $sComment = trim(implode(' ',$aComment));
354 $sPersonal .= $sComment;
356 $oAddr =& new AddressStructure();
357 if ($sPersonal && substr($sPersonal,0,2) == '=?') {
358 $oAddr->personal
= encodeHeader($sPersonal);
360 $oAddr->personal
= $sPersonal;
362 // $oAddr->group = $sGroup;
363 $iPosAt = strpos($sEmail,'@');
365 $oAddr->mailbox
= substr($sEmail, 0, $iPosAt);
366 $oAddr->host
= substr($sEmail, $iPosAt+
1);
368 $oAddr->mailbox
= $sEmail;
369 $oAddr->host
= false;
372 $aStack = $aComment = array();
377 * parseAddress: recursive function for parsing address strings and store
378 * them in an address stucture object.
379 * input: $address = string
380 * $ar = boolean (return array instead of only the
382 * $addr_ar = array with parsed addresses // obsolete
383 * $group = string // obsolete
384 * $host = string (default domainname in case of
385 * addresses without a domainname)
386 * $lookup = callback function (for lookup address
387 * strings which are probably nicks
389 * output: array with addressstructure objects or only one
390 * address_structure object.
391 * personal name: encoded: =?charset?Q|B?string?=
394 * email : <mailbox@host>
396 * This function is also used for validating addresses returned from compose
397 * That's also the reason that the function became a little bit huge
400 function parseAddress($address,$ar=false,$aAddress=array(),$sGroup='',$sHost='',$lookup=false) {
401 $aTokens = $this->getAddressTokens($address);
402 $sPersonal = $sEmail = $sComment = $sGroup = '';
403 $aStack = $aComment = array();
404 foreach ($aTokens as $sToken) {
414 $aComment[] = substr($sToken,1,-1);
418 $aAddress[] = $this->createAddressObject($aStack,$aComment,$sEmail,$sGroup);
419 $oAddr = end($aAddress);
420 if(!$oAddr ||
((isset($oAddr)) && !$oAddr->mailbox
&& !$oAddr->personal
)) {
421 $sEmail = $sGroup . ':;';
423 $aAddress[] = $this->createAddressObject($aStack,$aComment,$sEmail,$sGroup);
425 $aStack = $aComment = array();
429 $aAddress[] = $this->createAddressObject($aStack,$aComment,$sEmail,$sGroup);
432 $sGroup = trim(implode(' ',$aStack));
433 $sGroup = preg_replace('/\s+/',' ',$sGroup);
437 $sEmail = trim(substr($sToken,1,-1));
442 default: $aStack[] = $sToken; break;
445 /* now do the action again for the last address */
446 $aAddress[] = $this->createAddressObject($aStack,$aComment,$sEmail);
447 /* try to lookup the addresses in case of invalid email addresses */
448 $aProcessedAddress = array();
449 foreach ($aAddress as $oAddr) {
450 $aAddrBookAddress = array();
452 $grouplookup = false;
454 $aAddr = call_user_func_array($lookup,array($oAddr->mailbox
));
455 if (isset($aAddr['email'])) {
456 if (strpos($aAddr['email'],',')) {
458 $aAddrBookAddress = $this->parseAddress($aAddr['email'],true);
460 $iPosAt = strpos($aAddr['email'], '@');
461 $oAddr->mailbox
= substr($aAddr['email'], 0, $iPosAt);
462 $oAddr->host
= substr($aAddr['email'], $iPosAt+
1);
463 if (isset($aAddr['name'])) {
464 $oAddr->personal
= $aAddr['name'];
466 $oAddr->personal
= encodeHeader($sPersonal);
471 if (!$grouplookup && !$oAddr->mailbox
) {
472 $oAddr->mailbox
= trim($sEmail);
473 if ($sHost && $oAddr->mailbox
) {
474 $oAddr->host
= $sHost;
476 } else if (!$grouplookup && !$oAddr->host
) {
477 if ($sHost && $oAddr->mailbox
) {
478 $oAddr->host
= $sHost;
482 if (!$aAddrBookAddress && $oAddr->mailbox
) {
483 $aProcessedAddress[] = $oAddr;
485 $aProcessedAddress = array_merge($aProcessedAddress,$aAddrBookAddress);
489 return $aProcessedAddress;
491 return $aProcessedAddress[0];
495 function parseContentType($value) {
496 $pos = strpos($value, ';');
499 $type = trim(substr($value, 0, $pos));
500 $props = trim(substr($value, $pos+
1));
504 $content_type = new ContentType($type);
506 $properties = $this->parseProperties($props);
507 if (!isset($properties['charset'])) {
508 $properties['charset'] = 'us-ascii';
510 $content_type->properties
= $this->parseProperties($props);
512 $this->content_type
= $content_type;
516 function processParameters($aParameters) {
519 // handle multiline parameters
520 foreach($aParameters as $key => $value) {
521 if ($iPos = strpos($key,'*')) {
522 $sKey = substr($key,0,$iPos);
523 if (!isset($aResults[$sKey])) {
524 $aResults[$sKey] = $value;
525 if (substr($key,-1) == '*') { // parameter contains language/charset info
529 $aResults[$sKey] .= $value;
533 foreach ($aCharset as $key) {
534 $value = $aResults[$key];
535 // extract the charset & language
536 $charset = substr($value,0,strpos($value,"'"));
537 $value = substr($value,strlen($charset)+
1);
538 $language = substr($value,0,strpos($value,"'"));
539 $value = substr($value,strlen($charset)+
1);
540 // FIX ME What's the status of charset decode with language information ????
541 $value = charset_decode($charset,$value);
542 $aResults[$key] = $value;
547 function parseProperties($value) {
548 $propArray = explode(';', $value);
549 $propResultArray = array();
550 foreach ($propArray as $prop) {
552 $pos = strpos($prop, '=');
554 $key = trim(substr($prop, 0, $pos));
555 $val = trim(substr($prop, $pos+
1));
556 if ($val{0} == '"') {
557 $val = substr($val, 1, -1);
559 $propResultArray[$key] = $val;
562 return $this->processParameters($propResultArray);
565 function parseDisposition($value) {
566 $pos = strpos($value, ';');
569 $name = trim(substr($value, 0, $pos));
570 $props = trim(substr($value, $pos+
1));
574 $props_a = $this->parseProperties($props);
575 $disp = new Disposition($name);
576 $disp->properties
= $props_a;
577 $this->disposition
= $disp;
580 function mlist($field, $value) {
582 $value_a = explode(',', $value);
583 foreach ($value_a as $val) {
585 if ($val{0} == '<') {
586 $val = substr($val, 1, -1);
588 if (substr($val, 0, 7) == 'mailto:') {
589 $res_a['mailto'] = substr($val, 7);
591 $res_a['href'] = $val;
594 $this->mlist
[$field] = $res_a;
598 * function to get the addres strings out of the header.
599 * Arguments: string or array of strings !
600 * example1: header->getAddr_s('to').
601 * example2: header->getAddr_s(array('to', 'cc', 'bcc'))
603 function getAddr_s($arr, $separator = ',',$encoded=false) {
606 if (is_array($arr)) {
607 foreach($arr as $arg) {
608 if ($this->getAddr_s($arg, $separator, $encoded)) {
609 $s .= $separator . $result;
612 $s = ($s ?
substr($s, 2) : $s);
614 $addr = $this->{$arr};
615 if (is_array($addr)) {
616 foreach ($addr as $addr_o) {
617 if (is_object($addr_o)) {
619 $s .= $addr_o->getEncodedAddress() . $separator;
621 $s .= $addr_o->getAddress() . $separator;
625 $s = substr($s, 0, -strlen($separator));
627 if (is_object($addr)) {
629 $s .= $addr->getEncodedAddress();
631 $s .= $addr->getAddress();
639 function getAddr_a($arg, $excl_arr = array(), $arr = array()) {
640 if (is_array($arg)) {
641 foreach($arg as $argument) {
642 $arr = $this->getAddr_a($argument, $excl_arr, $arr);
645 $addr = $this->{$arg};
646 if (is_array($addr)) {
647 foreach ($addr as $next_addr) {
648 if (is_object($next_addr)) {
649 if (isset($next_addr->host
) && ($next_addr->host
!= '')) {
650 $email = $next_addr->mailbox
. '@' . $next_addr->host
;
652 $email = $next_addr->mailbox
;
654 $email = strtolower($email);
655 if ($email && !isset($arr[$email]) && !isset($excl_arr[$email])) {
656 $arr[$email] = $next_addr->personal
;
661 if (is_object($addr)) {
662 $email = $addr->mailbox
;
663 $email .= (isset($addr->host
) ?
'@' . $addr->host
: '');
664 $email = strtolower($email);
665 if ($email && !isset($arr[$email]) && !isset($excl_arr[$email])) {
666 $arr[$email] = $addr->personal
;
674 function findAddress($address, $recurs = false) {
676 if (is_array($address)) {
678 foreach($address as $argument) {
679 $match = $this->findAddress($argument, true);
684 if (count($match[0]) && !$result) {
691 if (!is_array($this->cc
)) $this->cc
= array();
692 $srch_addr = $this->parseAddress($address);
694 foreach ($this->to
as $to) {
695 if ($to->host
== $srch_addr->host
) {
696 if ($to->mailbox
== $srch_addr->mailbox
) {
697 $results[] = $srch_addr;
698 if ($to->personal
== $srch_addr->personal
) {
700 return array($results, true);
708 foreach ($this->cc
as $cc) {
709 if ($cc->host
== $srch_addr->host
) {
710 if ($cc->mailbox
== $srch_addr->mailbox
) {
711 $results[] = $srch_addr;
712 if ($cc->personal
== $srch_addr->personal
) {
714 return array($results, true);
723 return array($results, false);
724 } elseif (count($result)) {
734 function getContentType($type0, $type1) {
735 $type0 = $this->content_type
->type0
;
736 $type1 = $this->content_type
->type1
;
737 return $this->content_type
->properties
;