fsf changes, meant to be rebased on upstream
[squirrelmail.git] / class / mime / Rfc822Header.class.php
index d14a1b6b0442e14d7deb3227dc4df897d9c71c24..76c5003523c78b447ef83ea55c6569dbea62dcd4 100644 (file)
@@ -5,7 +5,7 @@
  *
  * This file contains functions needed to handle headers in mime messages.
  *
- * @copyright 2003-2015 The SquirrelMail Project Team
+ * @copyright 2003-2024 The SquirrelMail Project Team
  * @license http://opensource.org/licenses/gpl-license.php GNU Public License
  * @version $Id$
  * @package squirrelmail
  * @since 1.3.0
  */
 class Rfc822Header {
+    /**
+     * All headers, unparsed
+     * @var array
+     */
+    var $raw_headers = array();
     /**
      * Date header
      * @var mixed
@@ -117,10 +122,10 @@ class Rfc822Header {
      */
     var $dnt = '';
     /**
-     * Delivery notification (DR)
+     * Address for requesting message delivery status notification (DSN)
      * @var mixed
      */
-    var $drnt = '';
+    var $dsn = '';
     /**
      * @var mixed
      */
@@ -166,6 +171,7 @@ class Rfc822Header {
         foreach ($hdr as $line) {
             $pos = strpos($line, ':');
             if ($pos > 0) {
+                $this->raw_headers[] = $line;
                 $field = substr($line, 0, $pos);
                 if (!strstr($field,' ')) { /* valid field */
                         $value = trim(substr($line, $pos+1));
@@ -186,24 +192,24 @@ class Rfc822Header {
         $result = '';
         $cnt = strlen($value);
         for ($i = 0; $i < $cnt; ++$i) {
-            switch ($value{$i}) {
+            switch ($value[$i]) {
                 case '"':
                     $result .= '"';
-                    while ((++$i < $cnt) && ($value{$i} != '"')) {
-                        if ($value{$i} == '\\') {
+                    while ((++$i < $cnt) && ($value[$i] != '"')) {
+                        if ($value[$i] == '\\') {
                             $result .= '\\';
                             ++$i;
                         }
-                        $result .= $value{$i};
+                        $result .= $value[$i];
                     }
                     if($i < $cnt) {
-                        $result .= $value{$i};
+                        $result .= $value[$i];
                     }
                     break;
                 case '(':
                     $depth = 1;
                     while (($depth > 0) && (++$i < $cnt)) {
-                        switch($value{$i}) {
+                        switch($value[$i]) {
                             case '\\':
                                 ++$i;
                                 break;
@@ -219,7 +225,7 @@ class Rfc822Header {
                     }
                     break;
                 default:
-                    $result .= $value{$i};
+                    $result .= $value[$i];
                     break;
             }
         }
@@ -283,7 +289,7 @@ class Rfc822Header {
                 break;
             case 'return-receipt-to':
                 $value = $this->stripComments($value);
-                $this->drnt = $this->parseAddress($value);
+                $this->dsn = $this->parseAddress($value);
                 break;
             case 'mime-version':
                 $value = $this->stripComments($value);
@@ -350,8 +356,12 @@ class Rfc822Header {
                 $this->mlist('id', $value);
                 break;
             case 'x-spam-status':
+            case 'x-spam-score':
                 $this->x_spam_status = $this->parseSpamStatus($value);
                 break;
+            case 'x-sm-flag-reply':
+                $this->x_sm_flag_reply = $value;
+                break;
             default:
                 break;
         }
@@ -369,7 +379,7 @@ class Rfc822Header {
         $iCnt = strlen($address);
         $i = 0;
         while ($i < $iCnt) {
-            $cChar = $address{$i};
+            $cChar = $address[$i];
             switch($cChar)
             {
             case '<':
@@ -388,11 +398,11 @@ class Rfc822Header {
                 $iEnd = strpos($address,$cChar,$i+1);
                 if ($iEnd) {
                    // skip escaped quotes
-                   $prev_char = $address{$iEnd-1};
+                   $prev_char = $address[$iEnd-1];
                    while ($prev_char === '\\' && substr($address,$iEnd-2,2) !== '\\\\') {
                        $iEnd = strpos($address,$cChar,$iEnd+1);
                        if ($iEnd) {
-                          $prev_char = $address{$iEnd-1};
+                          $prev_char = $address[$iEnd-1];
                        } else {
                           $prev_char = false;
                        }
@@ -419,7 +429,7 @@ class Rfc822Header {
                     $iDepth = 1;
                     $iComment = $i;
                     while (($iDepth > 0) && (++$iComment < $iCnt)) {
-                        $cCharComment = $address{$iComment};
+                        $cCharComment = $address[$iComment];
                         switch($cCharComment) {
                             case '\\':
                                 ++$iComment;
@@ -445,7 +455,7 @@ class Rfc822Header {
                 // check the next token in case comments appear in the middle of email addresses
                 $prevToken = end($aTokens);
                 if (!in_array($prevToken,$aSpecials,true)) {
-                    if ($i+1<strlen($address) && !in_array($address{$i+1},$aSpecials,true)) {
+                    if ($i+1<strlen($address) && !in_array($address[$i+1],$aSpecials,true)) {
                         $iEnd = strpos($address,' ',$i+1);
                         if ($iEnd) {
                             $sNextToken = trim(substr($address,$i+1,$iEnd - $i -1));
@@ -551,7 +561,7 @@ class Rfc822Header {
         $sPersonal = $sEmail = $sGroup = '';
         $aStack = $aComment = array();
         foreach ($aTokens as $sToken) {
-            $cChar = $sToken{0};
+            $cChar = $sToken[0];
             switch ($cChar)
             {
             case '=':
@@ -758,7 +768,7 @@ class Rfc822Header {
             if ($pos > 0)  {
                 $key = trim(substr($prop, 0, $pos));
                 $val = trim(substr($prop, $pos+1));
-                if (strlen($val) > 0 && $val{0} == '"') {
+                if (strlen($val) > 0 && $val[0] == '"') {
                     $val = substr($val, 1, -1);
                 }
                 $propResultArray[$key] = $val;
@@ -796,7 +806,7 @@ class Rfc822Header {
         $value_a = explode(',', $value);
         foreach ($value_a as $val) {
             $val = trim($val);
-            if ($val{0} == '<') {
+            if ($val[0] == '<') {
                 $val = substr($val, 1, -1);
             }
             if (substr($val, 0, 7) == 'mailto:') {
@@ -809,45 +819,60 @@ class Rfc822Header {
     }
 
     /**
-     * Parses the X-Spam-Status header
+     * Parses the X-Spam-Status or X-Spam-Score header
      * @param string $value
      */
     function parseSpamStatus($value) {
         // Header value looks like this:
         // No, score=1.5 required=5.0 tests=MSGID_FROM_MTA_ID,NO_REAL_NAME,UPPERCASE_25_50 autolearn=disabled version=3.1.0-gr0
+        // Update circa 2018, this header can also be simply:
+        // No, score=1.5
+        // So we make the rest of the line optional (there are likely other permutations, so
+        // each element is made optional except the first two... maybe even that's not flexible enough)
+        //
+        // Also now allow parsing of X-Spam-Score header, whose value is just a float
 
         $spam_status = array();
 
-        if (preg_match ('/^(No|Yes),\s+score=(-?\d+\.\d+)\s+required=(-?\d+\.\d+)\s+tests=(.*?)\s+autolearn=(.*?)\s+version=(.+?)$/', $value, $matches)) {
+        if (preg_match ('/^(?:(No|Yes),\s+score=)?(-?\d+\.\d+)(?:\s+required=(-?\d+\.\d+))?(?:\s+tests=(.*?))?(?:\s+autolearn=(.*?))?(?:\s+version=(.+?))?$/i', $value, $matches)) {
+
             // full header
             $spam_status['bad_format'] = 0;
             $spam_status['value'] = $matches[0];
+
             // is_spam
-            if (isset($matches[1])
-                && strtolower($matches[1]) == 'yes') {
-                $spam_status['is_spam'] = true;
-            } else {
-                $spam_status['is_spam'] = false;
+            if (!empty($matches[1])) {
+                if (strtolower($matches[1]) == 'yes')
+                    $spam_status['is_spam'] = true;
+                else
+                    $spam_status['is_spam'] = false;
             }
 
             // score
-            $spam_status['score'] = $matches[2];
+            if (!empty($matches[2]))
+                $spam_status['score'] = $matches[2];
 
             // required
-            $spam_status['required'] = $matches[3];
+            if (!empty($matches[3]))
+                $spam_status['required'] = $matches[3];
 
             // tests
-            $tests = array();
-            $tests = explode(',', $matches[4]);
-            foreach ($tests as $test) {
-                $spam_status['tests'][] = trim($test);
+            if (isset($matches[4])) {
+                $tests = array();
+                $tests = explode(',', $matches[4]);
+                foreach ($tests as $test) {
+                    $spam_status['tests'][] = trim($test);
+                }
             }
 
             // autolearn
-            $spam_status['autolearn'] = $matches[5];
+            if (isset($matches[5]))
+                $spam_status['autolearn'] = $matches[5];
 
             // version
-            $spam_status['version'] = $matches[6];
+            if (isset($matches[6]))
+                $spam_status['version'] = $matches[6];
+
         } else {
             $spam_status['bad_format'] = 1;
             $spam_status['value'] = $value;
@@ -970,7 +995,7 @@ class Rfc822Header {
      *    Looks through this list of addresses and
      *    returns the array index (an integer even
      *    if the array is given with keys of a
-     *    different type) of the *last* matching
+     *    different type) of the first matching
      *    $address found in this message's
      *    TO or CC headers, unless there is an exact
      *    match (meaning that the "personal
@@ -1002,10 +1027,10 @@ class Rfc822Header {
             $i=0;
             foreach($address as $argument) {
                 $match = $this->findAddress($argument, true);
-                if ($match[1]) {
+                if ($match[1]) { // this indicates when the personal information matched
                     return $i;
                 } else {
-                    if (count($match[0]) && !$result) {
+                    if (count($match[0]) && $result === FALSE) {
                         $result = $i;
                     }
                 }