Source for file Rfc822Header.class.php

Documentation is available at 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 2003-2020 The SquirrelMail Project Team
  9.  * @license http://opensource.org/licenses/gpl-license.php GNU Public License
  10.  * @version $Id: Rfc822Header.class.php 14840 2020-01-07 07:42:38Z pdontthink $
  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.      * All headers, unparsed
  29.      * @var array 
  30.      */
  31.     var $raw_headers = array();
  32.     /**
  33.      * Date header
  34.      * @var mixed 
  35.      */
  36.     var $date = -1;
  37.     /**
  38.      * Original date header as fallback for unparsable dates
  39.      * @var mixed 
  40.      */
  41.     var $date_unparsed = '';
  42.     /**
  43.      * Subject header
  44.      * @var string 
  45.      */
  46.     var $subject = '';
  47.     /**
  48.      * From header
  49.      * @var array 
  50.      */
  51.     var $from = array();
  52.     /**
  53.      * @var mixed 
  54.      */
  55.     var $sender = '';
  56.     /**
  57.      * Reply-To header
  58.      * @var array 
  59.      */
  60.     var $reply_to = array();
  61.     /**
  62.      * Mail-Followup-To header
  63.      * @var array 
  64.      */
  65.     var $mail_followup_to = array();
  66.     /**
  67.      * To header
  68.      * @var array 
  69.      */
  70.     var $to = array();
  71.     /**
  72.      * Cc header
  73.      * @var array 
  74.      */
  75.     var $cc = array();
  76.     /**
  77.      * Bcc header
  78.      * @var array 
  79.      */
  80.     var $bcc = array();
  81.     /**
  82.      * In-reply-to header
  83.      * @var string 
  84.      */
  85.     var $in_reply_to = '';
  86.     /**
  87.      * Message-ID header
  88.      * @var string 
  89.      */
  90.     var $message_id = '';
  91.     /**
  92.      * References header
  93.      * @var string 
  94.      */
  95.     var $references = '';
  96.     /**
  97.      * @var mixed 
  98.      */
  99.     var $mime = false;
  100.     /**
  101.      * @var mixed 
  102.      */
  103.     var $content_type = '';
  104.     /**
  105.      * @var mixed 
  106.      */
  107.     var $disposition = '';
  108.     /**
  109.      * X-Mailer header
  110.      * @var string 
  111.      */
  112.     var $xmailer = '';
  113.     /**
  114.      * Priority header
  115.      * @var integer 
  116.      */
  117.     var $priority = 3;
  118.     /**
  119.      * @var mixed 
  120.      */
  121.     var $dnt = '';
  122.     /**
  123.      * @var mixed 
  124.      */
  125.     var $encoding = '';
  126.     /**
  127.      * @var mixed 
  128.      */
  129.     var $mlist = array();
  130.     /**
  131.      * SpamAssassin 'x-spam-status' header
  132.      * @var mixed 
  133.      */
  134.     var $x_spam_status = 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.                 $this->raw_headers[] = $line;
  160.                 $field = substr($line, 0, $pos);
  161.                 if (!strstr($field,' ')) { /* valid field */
  162.                         $value = trim(substr($line, $pos+1));
  163.                         $this->parseField($field, $value);
  164.                 }
  165.             }
  166.         }
  167.         if (!is_object($this->content_type)) {
  168.             $this->parseContentType('text/plain; charset=us-ascii');
  169.         }
  170.     }
  171.  
  172.     /**
  173.      * @param string $value 
  174.      * @return string 
  175.      */
  176.     function stripComments($value) {
  177.         $result = '';
  178.         $cnt = strlen($value);
  179.         for ($i = 0; $i < $cnt; ++$i) {
  180.             switch ($value{$i}) {
  181.                 case '"':
  182.                     $result .= '"';
  183.                     while ((++$i < $cnt) && ($value{$i} != '"')) {
  184.                         if ($value{$i} == '\\') {
  185.                             $result .= '\\';
  186.                             ++$i;
  187.                         }
  188.                         $result .= $value{$i};
  189.                     }
  190.                     $result .= $value{$i};
  191.                     break;
  192.                 case '(':
  193.                     $depth = 1;
  194.                     while (($depth > 0) && (++$i < $cnt)) {
  195.                         switch($value{$i}) {
  196.                             case '\\':
  197.                                 ++$i;
  198.                                 break;
  199.                             case '(':
  200.                                 ++$depth;
  201.                                 break;
  202.                             case ')':
  203.                                 --$depth;
  204.                                 break;
  205.                             default:
  206.                                 break;
  207.                         }
  208.                     }
  209.                     break;
  210.                 default:
  211.                     $result .= $value{$i};
  212.                     break;
  213.             }
  214.         }
  215.         return $result;
  216.     }
  217.  
  218.     /**
  219.      * Parse header field according to field type
  220.      * @param string $field field name
  221.      * @param string $value field value
  222.      */
  223.     function parseField($field, $value) {
  224.         $field = strtolower($field);
  225.         switch($field) {
  226.             case 'date':
  227.                 $value = $this->stripComments($value);
  228.                 $d = strtr($value, array('  ' => ' '));
  229.                 $d = explode(' ', $d);
  230.                 $this->date = getTimeStamp($d);
  231.                 $this->date_unparsed = strtr($value,'<>','  ');
  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 'return-receipt-to':
  270.             case 'disposition-notification-to':
  271.                 $value = $this->stripComments($value);
  272.                 $this->dnt = $this->parseAddress($value);
  273.                 break;
  274.             case 'mime-version':
  275.                 $value = $this->stripComments($value);
  276.                 $value = str_replace(' ', '', $value);
  277.                 $this->mime = ($value == '1.0' ? true : $this->mime);
  278.                 break;
  279.             case 'content-type':
  280.                 $value = $this->stripComments($value);
  281.                 $this->parseContentType($value);
  282.                 break;
  283.             case 'content-disposition':
  284.                 $value = $this->stripComments($value);
  285.                 $this->parseDisposition($value);
  286.                 break;
  287.             case 'user-agent':
  288.             case 'x-mailer':
  289.                 $this->xmailer = $value;
  290.                 break;
  291.             case 'x-priority':
  292.             case 'importance':
  293.             case 'priority':
  294.                 $this->priority = $this->parsePriority($value);
  295.                 break;
  296.             case 'list-post':
  297.                 $value = $this->stripComments($value);
  298.                 $this->mlist('post', $value);
  299.                 break;
  300.             case 'list-reply':
  301.                 $value = $this->stripComments($value);
  302.                 $this->mlist('reply', $value);
  303.                 break;
  304.             case 'list-subscribe':
  305.                 $value = $this->stripComments($value);
  306.                 $this->mlist('subscribe', $value);
  307.                 break;
  308.             case 'list-unsubscribe':
  309.                 $value = $this->stripComments($value);
  310.                 $this->mlist('unsubscribe', $value);
  311.                 break;
  312.             case 'list-archive':
  313.                 $value = $this->stripComments($value);
  314.                 $this->mlist('archive', $value);
  315.                 break;
  316.             case 'list-owner':
  317.                 $value = $this->stripComments($value);
  318.                 $this->mlist('owner', $value);
  319.                 break;
  320.             case 'list-help':
  321.                 $value = $this->stripComments($value);
  322.                 $this->mlist('help', $value);
  323.                 break;
  324.             case 'list-id':
  325.                 $value = $this->stripComments($value);
  326.                 $this->mlist('id', $value);
  327.                 break;
  328.             case 'x-spam-status':
  329.             case 'x-spam-score':
  330.                 $this->x_spam_status = $this->parseSpamStatus($value);
  331.                 break;
  332.             case 'x-sm-flag-reply':
  333.                 $this->x_sm_flag_reply = $value;
  334.                 break;
  335.             default:
  336.                 break;
  337.         }
  338.     }
  339.  
  340.     /**
  341.      * @param string $address 
  342.      * @return array 
  343.      */
  344.     function getAddressTokens($address) {
  345.         $aTokens = array();
  346.         $aSpecials = array('(' ,'<' ,',' ,';' ,':');
  347.         $aReplace =  array(' (',' <',' ,',' ;',' :');
  348.         $address = str_replace($aSpecials,$aReplace,$address);
  349.         $iCnt = strlen($address);
  350.         $i = 0;
  351.         while ($i < $iCnt) {
  352.             $cChar = $address{$i};
  353.             switch($cChar)
  354.             {
  355.             case '<':
  356.                 $iEnd = strpos($address,'>',$i+1);
  357.                 if (!$iEnd) {
  358.                    $sToken = substr($address,$i);
  359.                    $i = $iCnt;
  360.                 } else {
  361.                    $sToken = substr($address,$i,$iEnd - $i +1);
  362.                    $i = $iEnd;
  363.                 }
  364.                 $sToken = str_replace($aReplace, $aSpecials,$sToken);
  365.                 if ($sToken) $aTokens[] = $sToken;
  366.                 break;
  367.             case '"':
  368.                 $iEnd = strpos($address,$cChar,$i+1);
  369.                 if ($iEnd) {
  370.                    // skip escaped quotes
  371.                    $prev_char = $address{$iEnd-1};
  372.                    while ($prev_char === '\\' && substr($address,$iEnd-2,2) !== '\\\\') {
  373.                        $iEnd = strpos($address,$cChar,$iEnd+1);
  374.                        if ($iEnd) {
  375.                           $prev_char = $address{$iEnd-1};
  376.                        } else {
  377.                           $prev_char = false;
  378.                        }
  379.                    }
  380.                 }
  381.                 if (!$iEnd) {
  382.                     $sToken = substr($address,$i);
  383.                     $i = $iCnt;
  384.                 } else {
  385.                     // also remove the surrounding quotes
  386.                     $sToken = substr($address,$i+1,$iEnd - $i -1);
  387.                     $i = $iEnd;
  388.                 }
  389.                 $sToken = str_replace($aReplace, $aSpecials,$sToken);
  390.                 if ($sToken) $aTokens[] = $sToken;
  391.                 break;
  392.             case '(':
  393.                 array_pop($aTokens); //remove inserted space
  394.                 $iEnd = strpos($address,')',$i);
  395.                 if (!$iEnd) {
  396.                     $sToken = substr($address,$i);
  397.                     $i = $iCnt;
  398.                 } else {
  399.                     $iDepth = 1;
  400.                     $iComment = $i;
  401.                     while (($iDepth > 0) && (++$iComment < $iCnt)) {
  402.                         $cCharComment = $address{$iComment};
  403.                         switch($cCharComment) {
  404.                             case '\\':
  405.                                 ++$iComment;
  406.                                 break;
  407.                             case '(':
  408.                                 ++$iDepth;
  409.                                 break;
  410.                             case ')':
  411.                                 --$iDepth;
  412.                                 break;
  413.                             default:
  414.                                 break;
  415.                         }
  416.                     }
  417.                     if ($iDepth == 0) {
  418.                         $sToken = substr($address,$i,$iComment - $i +1);
  419.                         $i = $iComment;
  420.                     } else {
  421.                         $sToken = substr($address,$i,$iEnd - $i + 1);
  422.                         $i = $iEnd;
  423.                     }
  424.                 }
  425.                 // check the next token in case comments appear in the middle of email addresses
  426.                 $prevToken = end($aTokens);
  427.                 if (!in_array($prevToken,$aSpecials,true)) {
  428.                     if ($i+1<strlen($address) && !in_array($address{$i+1},$aSpecials,true)) {
  429.                         $iEnd = strpos($address,' ',$i+1);
  430.                         if ($iEnd) {
  431.                             $sNextToken = trim(substr($address,$i+1,$iEnd - $i -1));
  432.                             $i = $iEnd-1;
  433.                         } else {
  434.                             $sNextToken = trim(substr($address,$i+1));
  435.                             $i = $iCnt;
  436.                         }
  437.                         // remove the token
  438.                         array_pop($aTokens);
  439.                         // create token and add it again
  440.                         $sNewToken = $prevToken . $sNextToken;
  441.                         if($sNewToken) $aTokens[] = $sNewToken;
  442.                     }
  443.                 }
  444.                 $sToken = str_replace($aReplace, $aSpecials,$sToken);
  445.                 if ($sToken) $aTokens[] = $sToken;
  446.                 break;
  447.             case ',':
  448.             case ':':
  449.             case ';':
  450.             case ' ':
  451.                 $aTokens[] = $cChar;
  452.                 break;
  453.             default:
  454.                 $iEnd = strpos($address,' ',$i+1);
  455.                 if ($iEnd) {
  456.                     $sToken = trim(substr($address,$i,$iEnd - $i));
  457.                     $i = $iEnd-1;
  458.                 } else {
  459.                     $sToken = trim(substr($address,$i));
  460.                     $i = $iCnt;
  461.                 }
  462.                 if ($sToken) $aTokens[] = $sToken;
  463.             }
  464.             ++$i;
  465.         }
  466.         return $aTokens;
  467.     }
  468.  
  469.     /**
  470.      * @param array $aStack 
  471.      * @param array $aComment 
  472.      * @param string $sEmail 
  473.      * @param string $sGroup 
  474.      * @return object AddressStructure object
  475.      */
  476.     function createAddressObject(&$aStack,&$aComment,&$sEmail,$sGroup='') {
  477.         //$aStack=explode(' ',implode('',$aStack));
  478.         if (!$sEmail) {
  479.             while (count($aStack) && !$sEmail) {
  480.                 $sEmail = trim(array_pop($aStack));
  481.             }
  482.         }
  483.         if (count($aStack)) {
  484.             $sPersonal = trim(implode('',$aStack));
  485.         } else {
  486.             $sPersonal = '';
  487.         }
  488.         if (!$sPersonal && count($aComment)) {
  489.             $sComment = trim(implode(' ',$aComment));
  490.             $sPersonal .= $sComment;
  491.         }
  492.         $oAddr = new AddressStructure();
  493.         if ($sPersonal && substr($sPersonal,0,2) == '=?') {
  494.             $oAddr->personal = encodeHeader($sPersonal);
  495.         } else {
  496.             $oAddr->personal = $sPersonal;
  497.         }
  498.  //       $oAddr->group = $sGroup;
  499.         $iPosAt = strpos($sEmail,'@');
  500.         if ($iPosAt) {
  501.            $oAddr->mailbox = substr($sEmail, 0, $iPosAt);
  502.            $oAddr->host = substr($sEmail, $iPosAt+1);
  503.         } else {
  504.            $oAddr->mailbox = $sEmail;
  505.            $oAddr->host = false;
  506.         }
  507.         $sEmail = '';
  508.         $aStack = $aComment = array();
  509.         return $oAddr;
  510.     }
  511.  
  512.     /**
  513.      * recursive function for parsing address strings and storing them in an address stucture object.
  514.      *  personal name: encoded: =?charset?Q|B?string?=
  515.      *                 quoted:  "string"
  516.      *                 normal:  string
  517.      *  email        : <mailbox@host>
  518.      *               : mailbox@host
  519.      *  This function is also used for validating addresses returned from compose
  520.      *  That's also the reason that the function became a little bit huge
  521.      * @param string $address 
  522.      * @param boolean $ar return array instead of only the first element
  523.      * @param array $addr_ar (obsolete) array with parsed addresses
  524.      * @param string $group (obsolete)
  525.      * @param string $host default domainname in case of addresses without a domainname
  526.      * @param string $lookup (since) callback function for lookup of address strings which are probably nicks (without @)
  527.      * @return mixed array with AddressStructure objects or only one address_structure object.
  528.      */
  529.     function parseAddress($address,$ar=false,$aAddress=array(),$sGroup='',$sHost='',$lookup=false) {
  530.         $aTokens = $this->getAddressTokens($address);
  531.         $sPersonal = $sEmail = $sComment = $sGroup = '';
  532.         $aStack = $aComment = array();
  533.         foreach ($aTokens as $sToken) {
  534.             $cChar = $sToken{0};
  535.             switch ($cChar)
  536.             {
  537.             case '=':
  538.             case '"':
  539.             case ' ':
  540.                 $aStack[] = $sToken;
  541.                 break;
  542.             case '(':
  543.                 $aComment[] = substr($sToken,1,-1);
  544.                 break;
  545.             case ';':
  546.                 if ($sGroup) {
  547.                     $aAddress[] = $this->createAddressObject($aStack,$aComment,$sEmail,$sGroup);
  548.                     $oAddr = end($aAddress);
  549.                     if(!$oAddr || ((isset($oAddr)) && !strlen($oAddr->mailbox) && !$oAddr->personal)) {
  550.                         $sEmail = $sGroup . ':;';
  551.                     }
  552.                     $aAddress[] = $this->createAddressObject($aStack,$aComment,$sEmail,$sGroup);
  553.                     $sGroup = '';
  554.                     $aStack = $aComment = array();
  555.                     break;
  556.                 }
  557.             case ',':
  558.                 $aAddress[] = $this->createAddressObject($aStack,$aComment,$sEmail,$sGroup);
  559.                 break;
  560.             case ':':
  561.                 $sGroup = trim(implode(' ',$aStack));
  562.                 $sGroup = preg_replace('/\s+/',' ',$sGroup);
  563.                 $aStack = array();
  564.                 break;
  565.             case '<':
  566.                $sEmail = trim(substr($sToken,1,-1));
  567.                break;
  568.             case '>':
  569.                /* skip */
  570.                break;
  571.             default: $aStack[] = $sToken; break;
  572.             }
  573.         }
  574.         /* now do the action again for the last address */
  575.         $aAddress[] = $this->createAddressObject($aStack,$aComment,$sEmail);
  576.         /* try to lookup the addresses in case of invalid email addresses */
  577.         $aProcessedAddress = array();
  578.         foreach ($aAddress as $oAddr) {
  579.           $aAddrBookAddress = array();
  580.           if (!$oAddr->host) {
  581.             $grouplookup = false;
  582.             if ($lookup) {
  583.                  $aAddr = call_user_func_array($lookup,array($oAddr->mailbox));
  584.                  if (isset($aAddr['email'])) {
  585.                      if (strpos($aAddr['email'],',')) {
  586.                          $grouplookup = true;
  587.                          $aAddrBookAddress = $this->parseAddress($aAddr['email'],true);
  588.                      } else {
  589.                          $iPosAt = strpos($aAddr['email'], '@');
  590.                          if ($iPosAt === FALSE) {
  591.                              $oAddr->mailbox = $aAddr['email'];
  592.                              $oAddr->host = FALSE;
  593.                          } else {
  594.                              $oAddr->mailbox = substr($aAddr['email'], 0, $iPosAt);
  595.                              $oAddr->host = substr($aAddr['email'], $iPosAt+1);
  596.                          }
  597.                          if (isset($aAddr['name'])) {
  598.                              $oAddr->personal = $aAddr['name'];
  599.                          } else {
  600.                              $oAddr->personal = encodeHeader($sPersonal);
  601.                          }
  602.                      }
  603.                  }
  604.             }
  605.             if (!$grouplookup && !strlen($oAddr->mailbox)) {
  606.                 $oAddr->mailbox = trim($sEmail);
  607.                 if ($sHost && strlen($oAddr->mailbox)) {
  608.                     $oAddr->host = $sHost;
  609.                 }
  610.             } else if (!$grouplookup && !$oAddr->host) {
  611.                 if ($sHost && strlen($oAddr->mailbox)) {
  612.                     $oAddr->host = $sHost;
  613.                 }
  614.             }
  615.           }
  616.           if (!$aAddrBookAddress && strlen($oAddr->mailbox)) {
  617.               $aProcessedAddress[] = $oAddr;
  618.           } else {
  619.               $aProcessedAddress = array_merge($aProcessedAddress,$aAddrBookAddress);
  620.           }
  621.         }
  622.         if ($ar) {
  623.             return $aProcessedAddress;
  624.         } else {
  625.             if (isset($aProcessedAddress[0]))
  626.                 return $aProcessedAddress[0];
  627.             else
  628.                 return '';
  629.         }
  630.     }
  631.  
  632.     /**
  633.      * Normalise the different Priority headers into a uniform value,
  634.      * namely that of the X-Priority header (1, 3, 5). Supports:
  635.      * Priority, X-Priority, Importance.
  636.      * X-MS-Mail-Priority is not parsed because it always coincides
  637.      * with one of the other headers.
  638.      *
  639.      * NOTE: this is actually a duplicate from the function in
  640.      * functions/imap_messages. I'm not sure if it's ok here to call
  641.      * that function?
  642.      * @param string $sValue literal priority name
  643.      * @return integer 
  644.      */
  645.     function parsePriority($sValue) {
  646.         // don't use function call inside array_shift.
  647.         $aValue = preg_split('/\s/',trim($sValue));
  648.         $value = strtolower(array_shift($aValue));
  649.  
  650.         if ( is_numeric($value) ) {
  651.             return $value;
  652.         }
  653.         if ( $value == 'urgent' || $value == 'high' ) {
  654.             return 1;
  655.         } elseif ( $value == 'non-urgent' || $value == 'low' ) {
  656.             return 5;
  657.         }
  658.         // default is normal priority
  659.         return 3;
  660.     }
  661.  
  662.     /**
  663.      * @param string $value content type header
  664.      */
  665.     function parseContentType($value) {
  666.         $pos = strpos($value, ';');
  667.         $props = '';
  668.         if ($pos > 0) {
  669.            $type = trim(substr($value, 0, $pos));
  670.            $props = trim(substr($value, $pos+1));
  671.         } else {
  672.            $type = $value;
  673.         }
  674.         $content_type = new ContentType($type);
  675.         if ($props) {
  676.             $properties = $this->parseProperties($props);
  677.             if (!isset($properties['charset'])) {
  678.                 $properties['charset'] = 'us-ascii';
  679.             }
  680.             $content_type->properties = $this->parseProperties($props);
  681.         }
  682.         $this->content_type = $content_type;
  683.     }
  684.  
  685.     /**
  686.      * RFC2184
  687.      * @param array $aParameters 
  688.      * @return array 
  689.      */
  690.     function processParameters($aParameters) {
  691.         $aResults = array();
  692.         $aCharset = array();
  693.         // handle multiline parameters
  694.         foreach($aParameters as $key => $value) {
  695.             if ($iPos = strpos($key,'*')) {
  696.                 $sKey = substr($key,0,$iPos);
  697.                 if (!isset($aResults[$sKey])) {
  698.                     $aResults[$sKey] = $value;
  699.                     if (substr($key,-1) == '*') { // parameter contains language/charset info
  700.                         $aCharset[] = $sKey;
  701.                     }
  702.                 } else {
  703.                     $aResults[$sKey] .= $value;
  704.                 }
  705.             } else {
  706.                 $aResults[$key] = $value;
  707.             }
  708.         }
  709.         foreach ($aCharset as $key) {
  710.             $value = $aResults[$key];
  711.             // extract the charset & language
  712.             $charset = substr($value,0,strpos($value,"'"));
  713.             $value = substr($value,strlen($charset)+1);
  714.             $language = substr($value,0,strpos($value,"'"));
  715.             $value = substr($value,strlen($charset)+1);
  716.             /* FIXME: What's the status of charset decode with language information ????
  717.              * Maybe language information contains only ascii text and charset_decode() 
  718.              * only runs sm_encode_html_special_chars() on it. If it contains 8bit information, you 
  719.              * get html encoded text in charset used by selected translation.
  720.              */
  721.             $value = charset_decode($charset,$value);
  722.             $aResults[$key] = $value;
  723.         }
  724.         return $aResults;
  725.     }
  726.  
  727.     /**
  728.      * @param string $value 
  729.      * @return array 
  730.      */
  731.     function parseProperties($value) {
  732.         $propArray = explode(';', $value);
  733.         $propResultArray = array();
  734.         foreach ($propArray as $prop) {
  735.             $prop = trim($prop);
  736.             $pos = strpos($prop, '=');
  737.             if ($pos > 0)  {
  738.                 $key = trim(substr($prop, 0, $pos));
  739.                 $val = trim(substr($prop, $pos+1));
  740.                 if (strlen($val) > 0 && $val{0} == '"') {
  741.                     $val = substr($val, 1, -1);
  742.                 }
  743.                 $propResultArray[$key] = $val;
  744.             }
  745.         }
  746.         return $this->processParameters($propResultArray);
  747.     }
  748.  
  749.     /**
  750.      * Fills disposition object in rfc822Header object
  751.      * @param string $value 
  752.      */
  753.     function parseDisposition($value) {
  754.         $pos = strpos($value, ';');
  755.         $props = '';
  756.         if ($pos > 0) {
  757.             $name = trim(substr($value, 0, $pos));
  758.             $props = trim(substr($value, $pos+1));
  759.         } else {
  760.             $name = $value;
  761.         }
  762.         $props_a = $this->parseProperties($props);
  763.         $disp = new Disposition($name);
  764.         $disp->properties = $props_a;
  765.         $this->disposition = $disp;
  766.     }
  767.  
  768.     /**
  769.      * Fills mlist array keys in rfc822Header object
  770.      * @param string $field 
  771.      * @param string $value 
  772.      */
  773.     function mlist($field, $value) {
  774.         $res_a = array();
  775.         $value_a = explode(',', $value);
  776.         foreach ($value_a as $val) {
  777.             $val = trim($val);
  778.             if ($val{0} == '<') {
  779.                 $val = substr($val, 1, -1);
  780.             }
  781.             if (substr($val, 0, 7) == 'mailto:') {
  782.                 $res_a['mailto'] = substr($val, 7);
  783.             } else {
  784.                 $res_a['href'] = $val;
  785.             }
  786.         }
  787.         $this->mlist[$field] = $res_a;
  788.     }
  789.  
  790.     /**
  791.      * Parses the X-Spam-Status or X-Spam-Score header
  792.      * @param string $value 
  793.      */
  794.     function parseSpamStatus($value) {
  795.         // Header value looks like this:
  796.         // 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
  797.         // Update circa 2018, this header can also be simply:
  798.         // No, score=1.5
  799.         // So we make the rest of the line optional (there are likely other permutations, so
  800.         // each element is made optional except the first two... maybe even that's not flexible enough)
  801.         //
  802.         // Also now allow parsing of X-Spam-Score header, whose value is just a float
  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=(.+?))?$/i', $value, $matches)) {
  807.  
  808.             // full header
  809.             $spam_status['bad_format'] = 0;
  810.             $spam_status['value'] = $matches[0];
  811.  
  812.             // is_spam
  813.             if (!empty($matches[1])) {
  814.                 if (strtolower($matches[1]) == 'yes')
  815.                     $spam_status['is_spam'] = true;
  816.                 else
  817.                     $spam_status['is_spam'] = false;
  818.             }
  819.  
  820.             // score
  821.             if (isset($matches[2]))
  822.                 $spam_status['score'] = $matches[2];
  823.  
  824.             // required
  825.             if (isset($matches[3]))
  826.                $spam_status['required'] = $matches[3];
  827.  
  828.             // tests
  829.             if (isset($matches[4])) {
  830.                 $tests = array();
  831.                 $tests = explode(',', $matches[4]);
  832.                 foreach ($tests as $test) {
  833.                     $spam_status['tests'][] = trim($test);
  834.                 }
  835.             }
  836.  
  837.             // autolearn
  838.             if (isset($matches[5]))
  839.                 $spam_status['autolearn'] = $matches[5];
  840.  
  841.             // version
  842.             if (isset($matches[6]))
  843.                 $spam_status['version'] = $matches[6];
  844.  
  845.         } else {
  846.             $spam_status['bad_format'] = 1;
  847.             $spam_status['value'] = $value;
  848.         }
  849.         return $spam_status;
  850.     }
  851.  
  852.     /**
  853.      * function to get the address strings out of the header.
  854.      * example1: header->getAddr_s('to').
  855.      * example2: header->getAddr_s(array('to', 'cc', 'bcc'))
  856.      * @param mixed $arr string or array of strings
  857.      * @param string $separator 
  858.      * @param boolean $encoded (since 1.4.0) return encoded or plain text addresses
  859.      * @param boolean $unconditionally_quote (since 1.4.21/1.5.2) When TRUE, always
  860.      *                                                       quote the personal part,
  861.      *                                                       whether or not it is
  862.      *                                                       encoded, otherwise quoting
  863.      *                                                       is only added if the
  864.      *                                                       personal part is not encoded
  865.      * @return string 
  866.      */
  867.     function getAddr_s($arr, $separator = ',',$encoded=false,$unconditionally_quote=FALSE) {
  868.         $s = '';
  869.  
  870.         if (is_array($arr)) {
  871.             foreach($arr as $arg) {
  872.                 if ($this->getAddr_s($arg, $separator, $encoded, $unconditionally_quote)) {
  873.                     $s .= $separator;
  874.                 }
  875.             }
  876.             $s = ($s ? substr($s, 2) : $s);
  877.         } else {
  878.             $addr = $this->{$arr};
  879.             if (is_array($addr)) {
  880.                 foreach ($addr as $addr_o) {
  881.                     if (is_object($addr_o)) {
  882.                         if ($encoded) {
  883.                             $s .= $addr_o->getEncodedAddress($unconditionally_quote) . $separator;
  884.                         } else {
  885.                             $s .= $addr_o->getAddress(TRUE, FALSE, $unconditionally_quote) . $separator;
  886.                         }
  887.                     }
  888.                 }
  889.                 $s = substr($s, 0, -strlen($separator));
  890.             } else {
  891.                 if (is_object($addr)) {
  892.                     if ($encoded) {
  893.                         $s .= $addr->getEncodedAddress($unconditionally_quote);
  894.                     } else {
  895.                         $s .= $addr->getAddress(TRUE, FALSE, $unconditionally_quote);
  896.                     }
  897.                 }
  898.             }
  899.         }
  900.         return $s;
  901.     }
  902.  
  903.     /**
  904.      * function to get the array of addresses out of the header.
  905.      * @param mixed $arg string or array of strings
  906.      * @param array $excl_arr array of excluded email addresses
  907.      * @param array $arr array of added email addresses
  908.      * @return array 
  909.      */
  910.     function getAddr_a($arg, $excl_arr = array(), $arr = array()) {
  911.         if (is_array($arg)) {
  912.             foreach($arg as $argument) {
  913.                 $arr = $this->getAddr_a($argument, $excl_arr, $arr);
  914.             }
  915.         } else {
  916.             $addr = $this->{$arg};
  917.             if (is_array($addr)) {
  918.                 foreach ($addr as $next_addr) {
  919.                     if (is_object($next_addr)) {
  920.                         if (isset($next_addr->host) && ($next_addr->host != '')) {
  921.                             $email = $next_addr->mailbox . '@' . $next_addr->host;
  922.                         } else {
  923.                             $email = $next_addr->mailbox;
  924.                         }
  925.                         $email = strtolower($email);
  926.                         if ($email && !isset($arr[$email]) && !isset($excl_arr[$email])) {
  927.                             $arr[$email] = $next_addr->personal;
  928.                         }
  929.                     }
  930.                 }
  931.             } else {
  932.                 if (is_object($addr)) {
  933.                     $email  = $addr->mailbox;
  934.                     $email .= (isset($addr->host) ? '@' . $addr->host : '');
  935.                     $email  = strtolower($email);
  936.                     if ($email && !isset($arr[$email]) && !isset($excl_arr[$email])) {
  937.                         $arr[$email] = $addr->personal;
  938.                     }
  939.                 }
  940.             }
  941.         }
  942.         return $arr;
  943.     }
  944.  
  945.     /**
  946. //FIXME: This needs some documentation (inside the function too)!  Don't code w/out comments!
  947.      * Looking at the code years after it was written,
  948.      * this is my (Paul) best guess as to what this
  949.      * function does (note that docs previously claimed
  950.      * that this function returns boolean or an array,
  951.      * but it no longer appears to return an array - an
  952.      * integer instead):
  953.      *
  954.      * Inspects the TO and CC (or FROM) headers of the
  955.      * message represented by this object, looking for
  956.      * the address(es) given by $address
  957.      *
  958.      * If $address is a string:
  959.      *    Serves as a test (returns boolean) as to
  960.      *    whether or not the given address is found
  961.      *    anywhere in the TO or CC (or FROM) headers
  962.      *
  963.      * If $address is an array:
  964.      *    Looks through this list of addresses and
  965.      *    returns the array index (an integer even
  966.      *    if the array is given with keys of a
  967.      *    different type) of the first matching
  968.      *    $address found in this message's
  969.      *    TO or CC (or FROM) headers, unless there
  970.      *    is an exact match (meaning that the "personal
  971.      *    information" in addition to the email
  972.      *    address also matches), in which case that
  973.      *    index (the first one found) is returned
  974.      *
  975.      * @param mixed $address Address(es) to search for in this
  976.      *                        message's TO and CC (or FROM)
  977.      *                        headers - please see above how the
  978.      *                        format of this argument affects the
  979.      *                        return value of this function
  980.      * @param boolean $use_from When TRUE, this function will ONLY
  981.      *                           search the FROM headers and NOT the
  982.      *                           TO and CC headers, when FALSE, ONLY
  983.      *                           the TO and CC headers are searched
  984.      *                           (OPTIONAL; default is FALSE)
  985.      * @param boolean $recurs FOR INTERNAL USE ONLY
  986.      *
  987.      * @return mixed Boolean when $address is a scalar,
  988.      *                indicating whether or not the address
  989.      *                was found in the TO or CC headers.
  990.      *                An integer when $address is an array,
  991.      *                containing the index of the value in
  992.      *                that array that was found in the TO
  993.      *                or CC headers, or boolean FALSE if
  994.      *                there were no matches at all
  995.      *
  996.      * @since 1.3.2
  997.      */
  998.     function findAddress($address, $use_from=FALSE, $recurs=FALSE) {
  999.         $result = false;
  1000.         if (is_array($address)) {
  1001.             $i=0;
  1002.             foreach($address as $argument) {
  1003.                 $match = $this->findAddress($argument, $use_from, true);
  1004.                 if ($match[1]) { // this indicates when the personal information matched
  1005.                     return $i;
  1006.                 } else {
  1007.                     if (count($match[0]) && $result === FALSE) {
  1008.                         $result = $i;
  1009.                     }
  1010.                 }
  1011.                 ++$i;
  1012.             }
  1013.         } else {
  1014.             $srch_addr = $this->parseAddress($address);
  1015.             $results = array();
  1016.             if ($use_from) {
  1017.                 if (!is_array($this->from)) $this->from = array();
  1018.                 foreach ($this->from as $from) {
  1019.                     if (strtolower($from->host) == strtolower($srch_addr->host)) {
  1020.                         if (strtolower($from->mailbox) == strtolower($srch_addr->mailbox)) {
  1021.                             $results[] = $srch_addr;
  1022.                             if (strtolower($from->personal) == strtolower($srch_addr->personal)) {
  1023.                                 if ($recurs) {
  1024.                                     return array($results, true);
  1025.                                 } else {
  1026.                                     return true;
  1027.                                 }
  1028.                             }
  1029.                         }
  1030.                     }
  1031.                 }
  1032.             } else {
  1033.                 if (!is_array($this->cc)) $this->cc = array();
  1034.                 if (!is_array($this->to)) $this->to = array();
  1035.                 foreach ($this->to as $to) {
  1036.                     if (strtolower($to->host) == strtolower($srch_addr->host)) {
  1037.                         if (strtolower($to->mailbox) == strtolower($srch_addr->mailbox)) {
  1038.                             $results[] = $srch_addr;
  1039.                             if (strtolower($to->personal) == strtolower($srch_addr->personal)) {
  1040.                                 if ($recurs) {
  1041.                                     return array($results, true);
  1042.                                 } else {
  1043.                                     return true;
  1044.                                 }
  1045.                             }
  1046.                         }
  1047.                     }
  1048.                 }
  1049.                 foreach ($this->cc as $cc) {
  1050.                     if (strtolower($cc->host) == strtolower($srch_addr->host)) {
  1051.                         if (strtolower($cc->mailbox) == strtolower($srch_addr->mailbox)) {
  1052.                             $results[] = $srch_addr;
  1053.                             if (strtolower($cc->personal) == strtolower($srch_addr->personal)) {
  1054.                                 if ($recurs) {
  1055.                                     return array($results, true);
  1056.                                 } else {
  1057.                                     return true;
  1058.                                 }
  1059.                             }
  1060.                         }
  1061.                     }
  1062.                 }
  1063.             }
  1064.             if ($recurs) {
  1065.                 return array($results, false);
  1066.             } elseif (count($results)) {
  1067.                 return true;
  1068.             } else {
  1069.                 return false;
  1070.             }
  1071.         }
  1072.         //exit;
  1073.         return $result;
  1074.     }
  1075.  
  1076.     /**
  1077.      * @param string $type0 media type
  1078.      * @param string $type1 media subtype
  1079.      * @return array media properties
  1080.      * @todo check use of media type arguments
  1081.      */
  1082.     function getContentType($type0, $type1) {
  1083.         $type0 = $this->content_type->type0;
  1084.         $type1 = $this->content_type->type1;
  1085.         return $this->content_type->properties;
  1086.     }
  1087. }

Documentation generated on Mon, 13 Jan 2020 04:25:14 +0100 by phpDocumentor 1.4.3