Source for file Rfc822Header.class.php
Documentation is available at Rfc822Header.class.php
 * This file contains functions needed to handle headers in mime messages.  
 * @copyright 2003-2020 The SquirrelMail Project Team  
 * @license http://opensource.org/licenses/gpl-license.php GNU Public License  
 * @version $Id: Rfc822Header.class.php 14840 2020-01-07 07:42:38Z pdontthink $  
 * input: header_string or array  
 * You must call parseHeader() function after creating object in order to fill object's  
 * @todo FIXME: there is no constructor function and class should ignore all input args.  
     * Original date header as fallback for unparsable dates  
     * Mail-Followup-To header  
     * SpamAssassin 'x-spam-status' header  
     * only needed for constructing headers in delivery class  
     * @param mixed $hdr string or array with message headers  
        /* First we replace \r\n by \n and unfold the header */  
        /* FIXME: unfolding header with multiple spaces "\n( +)" */  
        $hdr = 
trim(str_replace(array("\r\n", "\n\t", "\n "),array("\n", ' ', ' '), $hdr));  
        /* Now we can make a new header array with */  
        /* each element representing a headerline  */  
        foreach ($hdr as $line) {  
                $field = 
substr($line, 0, $pos);  
                if (!strstr($field,' ')) { /* valid field */  
        for ($i = 
0; $i < 
$cnt; ++
$i) {  
                    while ((++
$i < 
$cnt) && 
($value{$i} != 
'"')) {  
                        if ($value{$i} == 
'\\') {  
                    while (($depth > 
0) && 
(++
$i < 
$cnt)) {  
     * Parse header field according to field type  
     * @param string $field field name  
     * @param string $value field value  
                $d = 
strtr($value, array('  ' => 
' '));  
            case 'x-confirm-reading-to':
  
            case 'return-receipt-to':
  
            case 'disposition-notification-to':
  
                $this->mime = 
($value == 
'1.0' ? 
true : 
$this->mime);  
            case 'content-disposition':
  
                $this->mlist('post', $value);  
                $this->mlist('reply', $value);  
                $this->mlist('subscribe', $value);  
                $this->mlist('unsubscribe', $value);  
                $this->mlist('archive', $value);  
                $this->mlist('owner', $value);  
                $this->mlist('help', $value);  
                $this->mlist('id', $value);  
                $this->x_sm_flag_reply = 
$value;  
        $aSpecials = 
array('(' ,'<' ,',' ,';' ,':');  
        $aReplace =  
array(' (',' <',' ,',' ;',' :');  
                $iEnd = 
strpos($address,'>',$i+
1);  
                   $sToken = 
substr($address,$i);  
                   $sToken = 
substr($address,$i,$iEnd - 
$i +
1);  
                if ($sToken) $aTokens[] = 
$sToken;  
                $iEnd = 
strpos($address,$cChar,$i+
1);  
                   $prev_char = 
$address{$iEnd-
1};  
                   while ($prev_char === 
'\\' && 
substr($address,$iEnd-
2,2) !== 
'\\\\') {  
                       $iEnd = 
strpos($address,$cChar,$iEnd+
1);  
                          $prev_char = 
$address{$iEnd-
1};  
                    $sToken = 
substr($address,$i);  
                    // also remove the surrounding quotes  
                    $sToken = 
substr($address,$i+
1,$iEnd - 
$i -
1);  
                if ($sToken) $aTokens[] = 
$sToken;  
                $iEnd = 
strpos($address,')',$i);  
                    $sToken = 
substr($address,$i);  
                    while (($iDepth > 
0) && 
(++
$iComment < 
$iCnt)) {  
                        $cCharComment = 
$address{$iComment};  
                        $sToken = 
substr($address,$i,$iComment - 
$i +
1);  
                        $sToken = 
substr($address,$i,$iEnd - 
$i + 
1);  
                // 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+
1<
strlen($address) && 
!in_array($address{$i+
1},$aSpecials,true)) {  
                        $iEnd = 
strpos($address,' ',$i+
1);  
                            $sNextToken = 
trim(substr($address,$i+
1,$iEnd - 
$i -
1));  
                        // create token and add it again  
                        $sNewToken = 
$prevToken . 
$sNextToken;  
                        if($sNewToken) $aTokens[] = 
$sNewToken;  
                if ($sToken) $aTokens[] = 
$sToken;  
                $iEnd = 
strpos($address,' ',$i+
1);  
                if ($sToken) $aTokens[] = 
$sToken;  
     * @return object AddressStructure object  
        //$aStack=explode(' ',implode('',$aStack));  
            while (count($aStack) && 
!$sEmail) {  
        if (!$sPersonal && 
count($aComment)) {  
        if ($sPersonal && 
substr($sPersonal,0,2) == 
'=?') {  
            $oAddr->personal = 
$sPersonal;  
 //       $oAddr->group = $sGroup;  
        $iPosAt = 
strpos($sEmail,'@');  
           $oAddr->mailbox = 
substr($sEmail, 0, $iPosAt);  
           $oAddr->host = 
substr($sEmail, $iPosAt+
1);  
           $oAddr->mailbox = 
$sEmail;  
        $aStack = 
$aComment = 
array();  
     * recursive function for parsing address strings and storing them in an address stucture object.  
     *  personal name: encoded: =?charset?Q|B?string?=  
     *  This function is also used for validating addresses returned from compose  
     *  That's also the reason that the function became a little bit huge  
     * @param boolean $ar return array instead of only the first element  
     * @param array $addr_ar (obsolete) array with parsed addresses  
     * @param string $group (obsolete)  
     * @param string $host default domainname in case of addresses without a domainname  
     * @param string $lookup (since) callback function for lookup of address strings which are probably nicks (without @)  
     * @return mixed array with AddressStructure objects or only one address_structure object.  
    function parseAddress($address,$ar=
false,$aAddress=
array(),$sGroup=
'',$sHost=
'',$lookup=
false) {  
        $sPersonal = 
$sEmail = 
$sComment = 
$sGroup = 
'';  
        $aStack = 
$aComment = 
array();  
        foreach ($aTokens as $sToken) {  
                $aComment[] = 
substr($sToken,1,-
1);  
                    if(!$oAddr || 
((isset
($oAddr)) && 
!strlen($oAddr->mailbox) && 
!$oAddr->personal)) {  
                        $sEmail = 
$sGroup . 
':;';  
                    $aStack = 
$aComment = 
array();  
            default: 
$aStack[] = 
$sToken; break;  
        /* now do the action again for the last address */  
        /* try to lookup the addresses in case of invalid email addresses */  
        $aProcessedAddress = 
array();  
        foreach ($aAddress as $oAddr) {  
          $aAddrBookAddress = 
array();  
                 if (isset
($aAddr['email'])) {  
                     if (strpos($aAddr['email'],',')) {  
                         $aAddrBookAddress = 
$this->parseAddress($aAddr['email'],true);  
                         $iPosAt = 
strpos($aAddr['email'], '@');  
                             $oAddr->mailbox = 
$aAddr['email'];  
                             $oAddr->mailbox = 
substr($aAddr['email'], 0, $iPosAt);  
                             $oAddr->host = 
substr($aAddr['email'], $iPosAt+
1);  
                         if (isset
($aAddr['name'])) {  
                             $oAddr->personal = 
$aAddr['name'];  
            if (!$grouplookup && 
!strlen($oAddr->mailbox)) {  
                $oAddr->mailbox = 
trim($sEmail);  
                if ($sHost && 
strlen($oAddr->mailbox)) {  
            } else if (!$grouplookup && 
!$oAddr->host) {  
                if ($sHost && 
strlen($oAddr->mailbox)) {  
          if (!$aAddrBookAddress && 
strlen($oAddr->mailbox)) {  
              $aProcessedAddress[] = 
$oAddr;  
              $aProcessedAddress = 
array_merge($aProcessedAddress,$aAddrBookAddress);  
            return $aProcessedAddress;  
            if (isset
($aProcessedAddress[0]))  
                return $aProcessedAddress[0];  
     * Normalise the different Priority headers into a uniform value,  
     * namely that of the X-Priority header (1, 3, 5). Supports:  
     * Priority, X-Priority, Importance.  
     * X-MS-Mail-Priority is not parsed because it always coincides  
     * with one of the other headers.  
     * NOTE: this is actually a duplicate from the function in  
     * functions/imap_messages. I'm not sure if it's ok here to call  
     * @param string $sValue literal priority name  
        // don't use function call inside array_shift.  
        if ( $value == 
'urgent' || 
$value == 
'high' ) {  
        } elseif ( $value == 
'non-urgent' || 
$value == 
'low' ) {  
        // default is normal priority  
     * @param string $value content type header  
            if (!isset
($properties['charset'])) {  
                $properties['charset'] = 
'us-ascii';  
     * @param array $aParameters   
        // handle multiline parameters  
        foreach($aParameters as $key => 
$value) {  
            if ($iPos = 
strpos($key,'*')) {  
                if (!isset
($aResults[$sKey])) {  
                    $aResults[$sKey] = 
$value;  
                    if (substr($key,-
1) == 
'*') { // parameter contains language/charset info  
                    $aResults[$sKey] .= 
$value;  
                $aResults[$key] = 
$value;  
        foreach ($aCharset as $key) {  
            $value = 
$aResults[$key];  
            // extract the charset & language  
            /* FIXME: What's the status of charset decode with language information ????  
             * Maybe language information contains only ascii text and charset_decode()   
             * only runs sm_encode_html_special_chars() on it. If it contains 8bit information, you   
             * get html encoded text in charset used by selected translation.  
            $aResults[$key] = 
$value;  
        $propResultArray = 
array();  
        foreach ($propArray as $prop) {  
                if (strlen($val) > 
0 && 
$val{0} == 
'"') {  
                $propResultArray[$key] = 
$val;  
     * Fills disposition object in rfc822Header object  
        $disp->properties = 
$props_a;  
     * Fills mlist array keys in rfc822Header object  
    function mlist($field, $value) {  
        foreach ($value_a as $val) {  
            if (substr($val, 0, 7) == 
'mailto:') {  
                $res_a['mailto'] = 
substr($val, 7);  
        $this->mlist[$field] = 
$res_a;  
     * Parses the X-Spam-Status or X-Spam-Score header  
        // Header value looks like this:  
        // 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  
        // Update circa 2018, this header can also be simply:  
        // So we make the rest of the line optional (there are likely other permutations, so  
        // each element is made optional except the first two... maybe even that's not flexible enough)  
        // Also now allow parsing of X-Spam-Score header, whose value is just a float  
        if (preg_match ('/^(?:(No|Yes),\s+score=)?(-?\d+\.\d+)(?:\s+required=(-?\d+\.\d+))?(?:\s+tests=(.*?))?(?:\s+autolearn=(.*?))?(?:\s+version=(.+?))?$/i', $value, $matches)) {  
            $spam_status['bad_format'] = 
0;  
            $spam_status['value'] = 
$matches[0];  
            if (!empty($matches[1])) {  
                    $spam_status['is_spam'] = 
true;  
                    $spam_status['is_spam'] = 
false;  
                $spam_status['score'] = 
$matches[2];  
               $spam_status['required'] = 
$matches[3];  
            if (isset
($matches[4])) {  
                $tests = 
explode(',', $matches[4]);  
                foreach ($tests as $test) {  
                    $spam_status['tests'][] = 
trim($test);  
                $spam_status['autolearn'] = 
$matches[5];  
                $spam_status['version'] = 
$matches[6];  
            $spam_status['bad_format'] = 
1;  
            $spam_status['value'] = 
$value;  
     * function to get the address strings out of the header.  
     * example1: header->getAddr_s('to').  
     * example2: header->getAddr_s(array('to', 'cc', 'bcc'))  
     * @param mixed $arr string or array of strings  
     * @param string $separator   
     * @param boolean $encoded (since 1.4.0) return encoded or plain text addresses  
     * @param boolean $unconditionally_quote (since 1.4.21/1.5.2) When TRUE, always  
     *                                                       quote the personal part,  
     *                                                       encoded, otherwise quoting  
     *                                                       personal part is not encoded  
    function getAddr_s($arr, $separator = 
',',$encoded=
false,$unconditionally_quote=
FALSE) {  
                if ($this->getAddr_s($arg, $separator, $encoded, $unconditionally_quote)) {  
            $s = 
($s ? 
substr($s, 2) : 
$s);  
                foreach ($addr as $addr_o) {  
                            $s .= 
$addr_o->getEncodedAddress($unconditionally_quote) . 
$separator;  
                            $s .= 
$addr_o->getAddress(TRUE, FALSE, $unconditionally_quote) . 
$separator;  
                        $s .= 
$addr->getEncodedAddress($unconditionally_quote);  
                        $s .= 
$addr->getAddress(TRUE, FALSE, $unconditionally_quote);  
     * function to get the array of addresses out of the header.  
     * @param mixed $arg string or array of strings  
     * @param array $excl_arr array of excluded email addresses  
     * @param array $arr array of added email addresses  
    function getAddr_a($arg, $excl_arr = 
array(), $arr = 
array()) {  
            foreach($arg as $argument) {  
                $arr = 
$this->getAddr_a($argument, $excl_arr, $arr);  
                foreach ($addr as $next_addr) {  
                        if (isset
($next_addr->host) && 
($next_addr->host != 
'')) {  
                            $email = 
$next_addr->mailbox . 
'@' . 
$next_addr->host;  
                            $email = 
$next_addr->mailbox;  
                        if ($email && 
!isset
($arr[$email]) && 
!isset
($excl_arr[$email])) {  
                            $arr[$email] = 
$next_addr->personal;  
                    $email .= 
(isset
($addr->host) ? 
'@' . 
$addr->host : 
'');  
                    if ($email && 
!isset
($arr[$email]) && 
!isset
($excl_arr[$email])) {  
                        $arr[$email] = 
$addr->personal;  
//FIXME: This needs some documentation (inside the function too)!  Don't code w/out comments!  
     * Looking at the code years after it was written,  
     * this is my (Paul) best guess as to what this  
     * function does (note that docs previously claimed  
     * that this function returns boolean or an array,  
     * but it no longer appears to return an array - an  
     * Inspects the TO and CC (or FROM) headers of the  
     * message represented by this object, looking for  
     * the address(es) given by $address  
     * If $address is a string:  
     *    Serves as a test (returns boolean) as to  
     *    whether or not the given address is found  
     *    anywhere in the TO or CC (or FROM) headers  
     * If $address is an array:  
     *    Looks through this list of addresses and  
     *    returns the array index (an integer even  
     *    if the array is given with keys of a  
     *    different type) of the first matching  
     *    $address found in this message's  
     *    TO or CC (or FROM) headers, unless there  
     *    is an exact match (meaning that the "personal  
     *    information" in addition to the email  
     *    address also matches), in which case that  
     *    index (the first one found) is returned  
     * @param mixed $address Address(es) to search for in this  
     *                        message's TO and CC (or FROM)  
     *                        headers - please see above how the  
     *                        format of this argument affects the  
     *                        return value of this function  
     * @param boolean $use_from When TRUE, this function will ONLY  
     *                           search the FROM headers and NOT the  
     *                           TO and CC headers, when FALSE, ONLY  
     *                           the TO and CC headers are searched  
     *                           (OPTIONAL; default is FALSE)  
     * @param boolean $recurs FOR INTERNAL USE ONLY  
     * @return mixed Boolean when $address is a scalar,  
     *                indicating whether or not the address  
     *                was found in the TO or CC headers.  
     *                An integer when $address is an array,  
     *                containing the index of the value in  
     *                that array that was found in the TO  
     *                or CC headers, or boolean FALSE if  
     *                there were no matches at all  
    function findAddress($address, $use_from=
FALSE, $recurs=
FALSE) {  
            foreach($address as $argument) {  
                $match = 
$this->findAddress($argument, $use_from, true);  
                if ($match[1]) { // this indicates when the personal information matched  
                    if (count($match[0]) && 
$result === 
FALSE) {  
                foreach ($this->from as $from) {  
                                    return array($results, true);  
                foreach ($this->to as $to) {  
                                    return array($results, true);  
                foreach ($this->cc as $cc) {  
                                    return array($results, true);  
                return array($results, false);  
            } elseif (count($results)) {  
     * @param string $type0 media type  
     * @param string $type1 media subtype  
     * @return array media properties  
     * @todo check use of media type arguments  
 
 
	
		Documentation generated on Mon, 13 Jan 2020 04:25:14 +0100 by phpDocumentor 1.4.3