X-Git-Url: https://vcs.fsf.org/?a=blobdiff_plain;f=class%2Fmime%2FRfc822Header.class.php;h=7ecb86bbe2617ffb2a681500bf9df1a4f6591aa3;hb=82d304a0501324b276cabab1870755d5352bd21c;hp=7b37521670127c86dd6ce1d3fdcd9480920b6731;hpb=340d67c221ae8e36ebfa286441aa9cf7d47bd990;p=squirrelmail.git diff --git a/class/mime/Rfc822Header.class.php b/class/mime/Rfc822Header.class.php index 7b375216..7ecb86bb 100644 --- a/class/mime/Rfc822Header.class.php +++ b/class/mime/Rfc822Header.class.php @@ -3,17 +3,18 @@ /** * Rfc822Header.class.php * - * Copyright (c) 2003 The SquirrelMail Project Team + * Copyright (c) 2003-2004 The SquirrelMail Project Team * Licensed under the GNU GPL. For full terms see the file COPYING. * * This contains functions needed to handle mime messages. * * $Id$ + * @package squirrelmail */ -/* - * rdc822_header class +/** * input: header_string or array + * @package squirrelmail */ class Rfc822Header { var $date = '', @@ -21,6 +22,7 @@ class Rfc822Header { $from = array(), $sender = '', $reply_to = array(), + $mail_followup_to = array(), $to = array(), $cc = array(), $bcc = array(), @@ -33,6 +35,9 @@ class Rfc822Header { $xmailer = '', $priority = 3, $dnt = '', + $encoding = '', + $content_id = '', + $content_desc = '', $mlist = array(), $more_headers = array(); /* only needed for constructing headers in smtp.php */ @@ -40,9 +45,8 @@ class Rfc822Header { if (is_array($hdr)) { $hdr = implode('', $hdr); } - /* First we unfold the header */ - $hdr = trim(str_replace(array("\r\n\t", "\r\n "),array('', ''), $hdr)); + $hdr = trim(str_replace(array("\r\n\t", "\r\n "),array(' ', ' '), $hdr)); /* Now we can make a new header array with */ /* each element representing a headerline */ @@ -125,6 +129,9 @@ class Rfc822Header { case 'reply-to': $this->reply_to = $this->parseAddress($value, true); break; + case 'mail-followup-to': + $this->mail_followup_to = $this->parseAddress($value, true); + break; case 'to': $this->to = $this->parseAddress($value, true); break; @@ -164,6 +171,16 @@ class Rfc822Header { $value = $this->stripComments($value); $this->parseDisposition($value); break; + case 'content-transfer-encoding': + $this->encoding = $value; + break; + case 'content-description': + $this->content_desc = $value; + break; + case 'content-id': + $value = $this->stripComments($value); + $this->content_id = $value; + break; case 'user-agent': case 'x-mailer': $this->xmailer = $value; @@ -176,11 +193,11 @@ class Rfc822Header { $this->mlist('post', $value); break; case 'list-reply': - $value = $this->stripComments($value); + $value = $this->stripComments($value); $this->mlist('reply', $value); break; case 'list-subscribe': - $value = $this->stripComments($value); + $value = $this->stripComments($value); $this->mlist('subscribe', $value); break; case 'list-unsubscribe': @@ -207,19 +224,181 @@ class Rfc822Header { break; } } + + function getAddressTokens($address) { + $aTokens = array(); + $aAddress = array(); + $aSpecials = array('(' ,'<' ,',' ,';' ,':'); + $aReplace = array(' (',' <',' ,',' ;',' :'); + $address = str_replace($aSpecials,$aReplace,$address); + $iCnt = strlen($address); + $i = 0; + while ($i < $iCnt) { + $cChar = $address{$i}; + switch($cChar) + { + case '<': + $iEnd = strpos($address,'>',$i+1); + if (!$iEnd) { + $sToken = substr($address,$i); + $i = $iCnt; + } else { + $sToken = substr($address,$i,$iEnd - $i +1); + $i = $iEnd; + } + $sToken = str_replace($aReplace, $aSpecials,$sToken); + if ($sToken) $aTokens[] = $sToken; + break; + case '"': + $iEnd = strpos($address,$cChar,$i+1); + if ($iEnd) { + // skip escaped quotes + $prev_char = $address{$iEnd-1}; + while ($prev_char === '\\' && substr($address,$iEnd-2,2) !== '\\\\') { + $iEnd = strpos($address,$cChar,$iEnd+1); + if ($iEnd) { + $prev_char = $address{$iEnd-1}; + } else { + $prev_char = false; + } + } + } + if (!$iEnd) { + $sToken = substr($address,$i); + $i = $iCnt; + } else { + // also remove the surrounding quotes + $sToken = substr($address,$i+1,$iEnd - $i -1); + $i = $iEnd; + } + $sToken = str_replace($aReplace, $aSpecials,$sToken); + if ($sToken) $aTokens[] = $sToken; + break; + case '(': + array_pop($aTokens); //remove inserted space + $iEnd = strpos($address,')',$i); + if (!$iEnd) { + $sToken = substr($address,$i); + $i = $iCnt; + } else { + $iDepth = 1; + $iComment = $i; + while (($iDepth > 0) && (++$iComment < $iCnt)) { + $cCharComment = $address{$iComment}; + switch($cCharComment) { + case '\\': + ++$iComment; + break; + case '(': + ++$iDepth; + break; + case ')': + --$iDepth; + break; + default: + break; + } + } + if ($iDepth == 0) { + $sToken = substr($address,$i,$iComment - $i +1); + $i = $iComment; + } else { + $sToken = substr($address,$i,$iEnd - $i + 1); + $i = $iEnd; + } + } + // check the next token in case comments appear in the middle of email addresses + $prevToken = end($aTokens); + if (!in_array($prevToken,$aSpecials,true)) { + if ($i+1personal = encodeHeader($sPersonal); + } else { + $oAddr->personal = $sPersonal; + } + // $oAddr->group = $sGroup; + $iPosAt = strpos($sEmail,'@'); + if ($iPosAt) { + $oAddr->mailbox = substr($sEmail, 0, $iPosAt); + $oAddr->host = substr($sEmail, $iPosAt+1); + } else { + $oAddr->mailbox = $sEmail; + $oAddr->host = false; + } + $sEmail = ''; + $aStack = $aComment = array(); + return $oAddr; + } + /* - * parseAddress: recursive function for parsing address strings and store + * parseAddress: recursive function for parsing address strings and store * them in an address stucture object. * input: $address = string * $ar = boolean (return array instead of only the * first element) - * $addr_ar = array with parsed addresses - * $group = string - * $host = string (default domainname in case of + * $addr_ar = array with parsed addresses // obsolete + * $group = string // obsolete + * $host = string (default domainname in case of * addresses without a domainname) * $lookup = callback function (for lookup address * strings which are probably nicks - * (without @ ) ) + * (without @ ) ) * output: array with addressstructure objects or only one * address_structure object. * personal name: encoded: =?charset?Q|B?string?= @@ -228,290 +407,102 @@ class Rfc822Header { * email : * : mailbox@host * This function is also used for validating addresses returned from compose - * That's also the reason that the function became a little bit huge and horrible - * Todo: Find a way to clean up this mess a bit (Marc Groot Koerkamp) + * That's also the reason that the function became a little bit huge */ - function parseAddress - ($address, $ar=false, $addr_ar = array(), $group = '', $host='',$lookup=false) { - $pos = 0; - $name = $addr = $comment = $is_encoded = ''; - /* - * in case of 8 bit addresses some how is represented as - * NON BRAKING SPACE - * This only happens when we validate addresses from the compose form. - * - * Note: when other charsets have dificulties with characters - * =,;:<>()" - * then we should find out the value for those characters ans replace - * them by proper ASCII values before we start parsing. - * - */ - $address = str_replace("\240",' ',$address); - - $address = trim($address); - $j = strlen($address); - while ($pos < $j) { - $char = $address{$pos}; - switch ($char) + function parseAddress($address,$ar=false,$aAddress=array(),$sGroup='',$sHost='',$lookup=false) { + $aTokens = $this->getAddressTokens($address); + $sPersonal = $sEmail = $sComment = $sGroup = ''; + $aStack = $aComment = array(); + foreach ($aTokens as $sToken) { + $cChar = $sToken{0}; + switch ($cChar) { case '=': - /* get the encoded personal name */ - if (preg_match('/^(=\?([^?]*)\?(Q|B)\?([^?]*)\?=)(.*)/Ui',substr($address,$pos),$reg)) { - $name .= $reg[1]; - $pos += strlen($reg[1]); - } - ++$pos; - $addr_start = $pos; - $is_encoded = true; - break; - case '"': /* get the personal name */ - $start_encoded = $pos; - ++$pos; - if ($address{$pos} == '"') { - ++$pos; - } else { - $personal_start = $personal_end = $pos; - while ($pos < $j) { - $personal_end = strpos($address,'"',$pos); - if (($personal_end-2)>0 && (substr($address,$personal_end-2,2) === '\\"' || - substr($address,$personal_end-2,2) === '\\\\')) { - $pos = $personal_end+1; - } else { - $name .= substr($address,$personal_start,$personal_end-$personal_start); - break; - } - } - if ($personal_end) { - $pos = $personal_end+1; - } else { - $pos = $j; - } - } - $addr_start = $pos; - break; - case '<': /* get email address */ - $addr_start = $pos; - $addr_end = strpos($address,'>',$addr_start); - $addr = substr($address,$addr_start+1,$addr_end-$addr_start-1); - if ($addr_end) { - $pos = $addr_end+1; - } else { - $addr = substr($address,$addr_start+1); - $pos = $j; - } + case '"': + case ' ': + $aStack[] = $sToken; break; - case '(': /* rip off comments */ - $addr_start = $pos; - $pos = strpos($address,')'); - if ($pos !== false) { - $comment = substr($address, $addr_start+1,($pos-$addr_start-1)); - $address_start = substr($address, 0, $addr_start); - $address_end = substr($address, $pos + 1); - $address = $address_start . $address_end; - } - $j = strlen($address); - $pos = $addr_start + 1; - break; - case ',': /* we reached a delimiter */ - if (!$name && !$addr) { - $addr = substr($address, 0, $pos); - } else if (!$addr) { - $addr = trim(substr($address, $addr_start, $pos)); - } else if ($name == '') { - $name = trim(substr($address, 0, $addr_start)); - } - $at = strpos($addr, '@'); - $addr_structure = new AddressStructure(); - if (!$name && $comment) $name = $comment; - if (!$is_encoded) { - $addr_structure->personal = encodeHeader($name); - } else { - $addr_structure->personal = $name; - } - $is_encoded = false; - $addr_structure->group = $group; - if ($at) { - $addr_structure->mailbox = substr($addr, 0, $at); - $addr_structure->host = substr($addr, $at+1); - } else { - /* if lookup function */ - if ($lookup) { - $aAddr = call_user_func_array($lookup,array($addr)); - if (isset($aAddr['email'])) { - $at = strpos($aAddr['email'], '@'); - $addr_structure->mailbox = substr($aAddr['email'], 0, $at); - $addr_structure->host = substr($aAddr['email'], $at+1); - if (isset($aAddr['name'])) { - $addr_structure->personal = $aAddr['name']; - } else { - $addr_structure->personal = encodeHeader($addr); - } - } - } - if (!$addr_structure->mailbox) { - $addr_structure->mailbox = trim($addr); - if ($host) { - $addr_structure->host = $host; - } - } - } - $address = trim(substr($address, $pos+1)); - $j = strlen($address); - $pos = 0; - $name = ''; - $addr = ''; - $addr_ar[] = $addr_structure; - break; - case ':': /* process the group addresses */ - /* group marker */ - $group = substr($address, 0, $pos); - $address = substr($address, $pos+1); - $result = $this->parseAddress($address, $ar, $addr_ar, $group); - $addr_ar = $result[0]; - $pos = $result[1]; - $address = substr($address, $pos++); - $j = strlen($address); - $group = ''; + case '(': + $aComment[] = substr($sToken,1,-1); break; case ';': - if ($group) { - $address = substr($address, 0, $pos - 1); - } - ++$pos; - break; - case ' ': - ++$pos; - break; - default: - /* - * this happens in the folowing situations : - * 1: unquoted personal name - * 2: emailaddress without < and > - * 3: unquoted personal name from compose that should be encoded. - * if it's a personal name then an emailaddress should follow - * the personal name may not have ',' inside it - * If it's a emailaddress then the personal name is not set. - * we should look for the delimiter ',' or a SPACE - */ - /* check for emailaddress */ - $i_space = strpos($address,' ',$pos); - $i_del = strpos($address,',',$pos); - if ($i_space || $i_del) { - if ($i_del) { - $address_part = substr($address,$pos,$i_del-$pos); - } else { - $address_part = substr($address,$pos); - } - if ($i = strpos($address_part,'@')) { - /* an email address is following */ - if (($i+$pos) < $i_space) { - $addr_start = $pos; - if ($i_space < $i_del && $i_del) { - if ($i_space) { - $addr = substr($address,$pos,$i_space-$pos); - $pos = $i_space; - } else { - $addr = substr($address,$pos); - $pos = $j; - } - } else { - if ($i_del) { - $addr = substr($address,$pos,$i_del-$pos); - $pos = $i_del; - } else { - $addr = substr($address,$pos); - $pos = $j; - } - } - } else { - if ($i_space) { - $name .= substr($address,$pos,$i_space-$pos) . ' '; - $addr_start = $i_space+1; - $pos = $i_space+1; - } else { - $addr = substr($address,$pos,$i_del-$pos); - $addr_start = $pos; - if ($i_del) { - $pos = $i_del; - } else { - $pos = $j; - } - } - } - } else { - /* email address without domain name, could be an alias */ - $addr_start = $pos; - $addr = $address_part; - $pos = strlen($address_part) + $pos; + if ($sGroup) { + $aAddress[] = $this->createAddressObject($aStack,$aComment,$sEmail,$sGroup); + $oAddr = end($aAddress); + if(!$oAddr || ((isset($oAddr)) && !$oAddr->mailbox && !$oAddr->personal)) { + $sEmail = $sGroup . ':;'; } - } else { - $addr = substr($address,$pos); - $addr_start = $pos; - $pos = $j; + $aAddress[] = $this->createAddressObject($aStack,$aComment,$sEmail,$sGroup); + $sGroup = ''; + $aStack = $aComment = array(); + break; } - break; + case ',': + $aAddress[] = $this->createAddressObject($aStack,$aComment,$sEmail,$sGroup); + break; + case ':': + $sGroup = trim(implode(' ',$aStack)); + $sGroup = preg_replace('/\s+/',' ',$sGroup); + $aStack = array(); + break; + case '<': + $sEmail = trim(substr($sToken,1,-1)); + break; + case '>': + /* skip */ + break; + default: $aStack[] = $sToken; break; } } - if (!$name && !$addr) { - $addr = substr($address, 0, $pos); - } else if (!$addr) { - $addr = trim(substr($address, $addr_start, $pos)); - } else if ($name == '') { - $name = trim(substr($address, 0, $addr_start)); - } - if (!$name && $comment) $name = $comment; - $at = strpos($addr, '@'); - $addr_structure = new AddressStructure(); - $addr_structure->group = $group; - if ($at) { - $addr_structure->mailbox = trim(substr($addr, 0, $at)); - $addr_structure->host = trim(substr($addr, $at+1)); - } else { - /* if lookup function */ + /* now do the action again for the last address */ + $aAddress[] = $this->createAddressObject($aStack,$aComment,$sEmail); + /* try to lookup the addresses in case of invalid email addresses */ + $aProcessedAddress = array(); + foreach ($aAddress as $oAddr) { + $aAddrBookAddress = array(); + if (!$oAddr->host) { + $grouplookup = false; if ($lookup) { - $aAddr = call_user_func_array($lookup,array($addr)); - if (isset($aAddr['email'])) { - $at = strpos($aAddr['email'], '@'); - $addr_structure->mailbox = substr($aAddr['email'], 0, $at); - $addr_structure->host = substr($aAddr['email'], $at+1); - if (isset($aAddr['name']) && $aAddr['name']) { - $name = $aAddr['name']; - } else { - $name = $addr; - } - } + $aAddr = call_user_func_array($lookup,array($oAddr->mailbox)); + if (isset($aAddr['email'])) { + if (strpos($aAddr['email'],',')) { + $grouplookup = true; + $aAddrBookAddress = $this->parseAddress($aAddr['email'],true); + } else { + $iPosAt = strpos($aAddr['email'], '@'); + $oAddr->mailbox = substr($aAddr['email'], 0, $iPosAt); + $oAddr->host = substr($aAddr['email'], $iPosAt+1); + if (isset($aAddr['name'])) { + $oAddr->personal = $aAddr['name']; + } else { + $oAddr->personal = encodeHeader($sPersonal); + } + } + } } - if (!$addr_structure->mailbox) { - $addr_structure->mailbox = trim($addr); - if ($host) { - $addr_structure->host = $host; + if (!$grouplookup && !$oAddr->mailbox) { + $oAddr->mailbox = trim($sEmail); + if ($sHost && $oAddr->mailbox) { + $oAddr->host = $sHost; + } + } else if (!$grouplookup && !$oAddr->host) { + if ($sHost && $oAddr->mailbox) { + $oAddr->host = $sHost; } } - } - $name = trim($name); - if (!$is_encoded && !$group) { - $name = encodeHeader($name); - } - if ($group && $addr == '') { /* no addresses found in group */ - $name = $group; - $addr_structure->personal = $name; - $addr_ar[] = $addr_structure; - return (array($addr_ar,$pos+1 )); - } elseif ($group) { - $addr_structure->personal = $name; - $addr_ar[] = $addr_structure; - return (array($addr_ar,$pos+1 )); - } else { - $addr_structure->personal = $name; - if ($name || $addr) { - $addr_ar[] = $addr_structure; - } + } + if (!$aAddrBookAddress && $oAddr->mailbox) { + $aProcessedAddress[] = $oAddr; + } else { + $aProcessedAddress = array_merge($aProcessedAddress,$aAddrBookAddress); + } } if ($ar) { - return ($addr_ar); + return $aProcessedAddress; + } else { + return $aProcessedAddress[0]; } - return ($addr_ar[0]); } function parseContentType($value) { @@ -519,7 +510,7 @@ class Rfc822Header { $props = ''; if ($pos > 0) { $type = trim(substr($value, 0, $pos)); - $props = trim(substr($type, $pos+1)); + $props = trim(substr($value, $pos+1)); } else { $type = $value; } @@ -534,6 +525,40 @@ class Rfc822Header { $this->content_type = $content_type; } + /* RFC2184 */ + function processParameters($aParameters) { + $aResults = array(); + $aCharset = array(); + // handle multiline parameters + foreach($aParameters as $key => $value) { + if ($iPos = strpos($key,'*')) { + $sKey = substr($key,0,$iPos); + if (!isset($aResults[$sKey])) { + $aResults[$sKey] = $value; + if (substr($key,-1) == '*') { // parameter contains language/charset info + $aCharset[] = $sKey; + } + } else { + $aResults[$sKey] .= $value; + } + } else { + $aResults[$key] = $value; + } + } + foreach ($aCharset as $key) { + $value = $aResults[$key]; + // extract the charset & language + $charset = substr($value,0,strpos($value,"'")); + $value = substr($value,strlen($charset)+1); + $language = substr($value,0,strpos($value,"'")); + $value = substr($value,strlen($charset)+1); + // FIX ME What's the status of charset decode with language information ???? + $value = charset_decode($charset,$value); + $aResults[$key] = $value; + } + return $aResults; + } + function parseProperties($value) { $propArray = explode(';', $value); $propResultArray = array(); @@ -549,7 +574,7 @@ class Rfc822Header { $propResultArray[$key] = $val; } } - return $propResultArray; + return $this->processParameters($propResultArray); } function parseDisposition($value) { @@ -660,7 +685,7 @@ class Rfc822Header { } return $arr; } - + function findAddress($address, $recurs = false) { $result = false; if (is_array($address)) { @@ -675,7 +700,7 @@ class Rfc822Header { $result = $i; } } - ++$i; + ++$i; } } else { if (!is_array($this->cc)) $this->cc = array(); @@ -715,7 +740,7 @@ class Rfc822Header { return true; } else { return false; - } + } } //exit; return $result;