From 67127c690f5cb90f2e516aecf9efc5611575d6b1 Mon Sep 17 00:00:00 2001
From: Jared Hancock <jared@osticket.com>
Date: Thu, 23 Apr 2015 14:50:14 -0500
Subject: [PATCH] email: Properly detect bounce message with alternative
 content

This correctly handles a bounce message with the following layout:

multipart/report; report-type=delivery-status
    multipart/alternative; differences=Content-Type
        text/plain; charset="us-ascii"
        text/html; charset="us-ascii"
    message/delivery-status
    message/rfc822
---
 include/class.mailfetch.php | 32 ++++++++++++++++++----------
 include/class.mailparse.php | 42 ++++++++++++++++++++++++++++---------
 2 files changed, 53 insertions(+), 21 deletions(-)

diff --git a/include/class.mailfetch.php b/include/class.mailfetch.php
index 1c3bf4fd6..c20c426fc 100644
--- a/include/class.mailfetch.php
+++ b/include/class.mailfetch.php
@@ -360,7 +360,7 @@ class MailFetcher {
     }
 
     //search for specific mime type parts....encoding is the desired encoding.
-    function getPart($mid, $mimeType, $encoding=false, $struct=null, $partNumber=false, $recurse=-1) {
+    function getPart($mid, $mimeType, $encoding=false, $struct=null, $partNumber=false, $recurse=-1, $recurseIntoRfc822=true) {
 
         if(!$struct && $mid)
             $struct=@imap_fetchstructure($this->mbox, $mid);
@@ -396,15 +396,21 @@ class MailFetcher {
                 && ($content = $this->tnef->getBody('text/html', $encoding)))
             return $content;
 
-        //Do recursive search
-        $text='';
-        if($struct && $struct->parts && $recurse) {
+        // Do recursive search
+        $text = '';
+        $ctype = $this->getMimeType($struct);
+        if ($struct && $struct->parts && $recurse
+            // Do not recurse into email (rfc822) attachments unless requested
+            && (strtolower($ctype) !== 'message/rfc822' || $recurseIntoRfc822)
+        ) {
             while(list($i, $substruct) = each($struct->parts)) {
-                if($partNumber)
+                if ($partNumber)
                     $prefix = $partNumber . '.';
-                if (($result=$this->getPart($mid, $mimeType, $encoding,
-                        $substruct, $prefix.($i+1), $recurse-1)))
-                    $text.=$result;
+                if ($result = $this->getPart($mid, $mimeType, $encoding,
+                    $substruct, $prefix.($i+1), $recurse-1, $recurseIntoRfc822)
+                ) {
+                    $text .= $result;
+                }
             }
         }
 
@@ -519,9 +525,13 @@ class MailFetcher {
         if (strtolower($ctype) == 'multipart/report') {
             foreach ($struct->parameters as $p) {
                 if (strtolower($p->attribute) == 'report-type'
-                        && $p->value == 'delivery-status') {
-                    return new TextThreadBody( $this->getPart(
-                                $mid, 'text/plain', $this->charset, $struct, false, 1));
+                    && $p->value == 'delivery-status'
+                ) {
+                    if ($body = $this->getPart(
+                        $mid, 'text/plain', $this->charset, $struct, false, 3, false
+                    )) {
+                        return new TextThreadBody($body);
+                    }
                 }
             }
         }
diff --git a/include/class.mailparse.php b/include/class.mailparse.php
index f8209f395..3361cf2c1 100644
--- a/include/class.mailparse.php
+++ b/include/class.mailparse.php
@@ -278,9 +278,8 @@ class Mail_Parse {
             && isset($this->struct->ctype_parameters['report-type'])
             && $this->struct->ctype_parameters['report-type'] == 'delivery-status'
         ) {
-            return new TextThreadBody(
-                $this->getPart($this->struct, 'text/plain', 1)
-            );
+            if ($body = $this->getPart($this->struct, 'text/plain', 3, false))
+                return new TextThreadBody($body);
         }
         return false;
     }
@@ -326,10 +325,27 @@ class Mail_Parse {
         return $body;
     }
 
-    function getPart($struct, $ctypepart, $recurse=-1) {
+    /**
+     * Fetch all the parts of the message for a specific MIME type. The
+     * parts are automatically transcoded to UTF-8 and concatenated together
+     * in the event more than one body of the requested type exists.
+     *
+     * Parameters:
+     * $struct - (<Mail_mime>) decoded message
+     * $ctypepart - (string) 'text/plain' or 'text/html', message body
+     *      format to retrieve from the mail
+     * $recurse - (int:-1) levels acceptable to recurse into. Default is to
+     *      recurse as needed.
+     * $recurseIntoRfc822 - (bool:true) proceed to recurse into
+     *      message/rfc822 bodies to look for the message body format
+     *      requested. For something like a bounce notice, where another
+     *      email might be attached to the email, set this to false to avoid
+     *      finding the wrong body.
+     */
+    function getPart($struct, $ctypepart, $recurse=-1, $recurseIntoRfc822=true) {
 
-        if($struct && !@$struct->parts) {
-            $ctype = @strtolower($struct->ctype_primary.'/'.$struct->ctype_secondary);
+        $ctype = @strtolower($struct->ctype_primary.'/'.$struct->ctype_secondary);
+        if ($struct && !@$struct->parts) {
             if (@$struct->disposition
                     && (strcasecmp($struct->disposition, 'inline') !== 0))
                 return '';
@@ -349,10 +365,16 @@ class Mail_Parse {
             return $content;
 
         $data='';
-        if($struct && @$struct->parts && $recurse) {
-            foreach($struct->parts as $i=>$part) {
-                if($part && ($text=$this->getPart($part,$ctypepart,$recurse - 1)))
-                    $data.=$text;
+        if ($struct && @$struct->parts && $recurse
+            // Do not recurse into email (rfc822) attachments unless requested
+            && ($ctype !== 'message/rfc822' || $recurseIntoRfc822)
+        ) {
+            foreach ($struct->parts as $i=>$part) {
+                if ($part && ($text=$this->getPart($part, $ctypepart,
+                    $recurse-1, $recursIntoRfc822))
+                ) {
+                    $data .= $text;
+                }
             }
         }
         return $data;
-- 
GitLab