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($line0$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($valuearray('  ' => ' '));
  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($valuetrue);
  244.                 break;
  245.             case 'mail-followup-to':
  246.                 $this->mail_followup_to = $this->parseAddress($valuetrue);
  247.                 break;
  248.             case 'to':
  249.                 $this->to = $this->parseAddress($valuetrue);
  250.                 break;
  251.             case 'cc':
  252.                 $this->cc = $this->parseAddress($valuetrue);
  253.                 break;
  254.             case 'bcc':
  255.                 $this->bcc = $this->parseAddress($valuetrue);
  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($sEmail0$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[$sTokenbreak;
  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($value0$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($prop0$pos));
  739.                 $val trim(substr($prop$pos+1));
  740.                 if (strlen($val&& $val{0== '"'{
  741.                     $val substr($val1-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($value0$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($val1-1);
  780.             }
  781.             if (substr($val07== 'mailto:'{
  782.                 $res_a['mailto'substr($val7);
  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($s2$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(TRUEFALSE$unconditionally_quote$separator;
  886.                         }
  887.                     }
  888.                 }
  889.                 $s substr($s0-strlen($separator));
  890.             else {
  891.                 if (is_object($addr)) {
  892.                     if ($encoded{
  893.                         $s .= $addr->getEncodedAddress($unconditionally_quote);
  894.                     else {
  895.                         $s .= $addr->getAddress(TRUEFALSE$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_fromtrue);
  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($resultstrue);
  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($resultstrue);
  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($resultstrue);
  1056.                                 else {
  1057.                                     return true;
  1058.                                 }
  1059.                             }
  1060.                         }
  1061.                     }
  1062.                 }
  1063.             }
  1064.             if ($recurs{
  1065.                 return array($resultsfalse);
  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