CRM-21521: fix nested multipart handling
authorElliott Eggleston <ejegg@ejegg.com>
Tue, 5 Dec 2017 16:17:34 +0000 (11:17 -0500)
committerElliott Eggleston <ejegg@ejegg.com>
Mon, 8 Jan 2018 17:27:13 +0000 (12:27 -0500)
When for example a multipart-report email body's first part is
multipart-related, extract the text correctly instead of crashing.

This logic should handle all kinds of weird multipart nesting, up
to MIME_MAX_RECURSION levels deep.

CRM/Utils/Mail/EmailProcessor.php
tests/phpunit/CRM/Utils/Mail/EmailProcessorTest.php
tests/phpunit/CRM/Utils/Mail/data/bounces/test_nested_message.eml [new file with mode: 0644]

index f08fa56272e525539ede4beb1770a2691fac617c..eb66e2b21c0843e9efd53b9dad83ded85c3e2350 100644 (file)
@@ -41,6 +41,8 @@ define('MAIL_BATCH_SIZE', 50);
  */
 class CRM_Utils_Mail_EmailProcessor {
 
+  const MIME_MAX_RECURSION = 10;
+
   /**
    * Process the default mailbox (ie. that is used by civiMail for the bounce)
    *
@@ -286,55 +288,7 @@ class CRM_Utils_Mail_EmailProcessor {
                 $text = $mail->body->text;
               }
               elseif ($mail->body instanceof ezcMailMultipart) {
-                if ($mail->body instanceof ezcMailMultipartReport) {
-                  $part = $mail->body->getMachinePart();
-                  if ($part instanceof ezcMailDeliveryStatus) {
-                    foreach ($part->recipients as $rec) {
-                      if (isset($rec["Diagnostic-Code"])) {
-                        $text = $rec["Diagnostic-Code"];
-                        break;
-                      }
-                      elseif (isset($rec["Description"])) {
-                        $text = $rec["Description"];
-                        break;
-                      }
-                      // no diagnostic info present - try getting the human readable part
-                      elseif (isset($rec["Status"])) {
-                        $text = $rec["Status"];
-                        $textpart = $mail->body->getReadablePart();
-                        if ($textpart != NULL and isset($textpart->text)) {
-                          $text .= " " . $textpart->text;
-                        }
-                        else {
-                          $text .= " Delivery failed but no diagnostic code or description.";
-                        }
-                        break;
-                      }
-                    }
-                  }
-                  elseif ($part != NULL and isset($part->text)) {
-                    $text = $part->text;
-                  }
-                  elseif (($part = $mail->body->getReadablePart()) != NULL) {
-                    $text = $part->text;
-                  }
-                }
-                elseif ($mail->body instanceof ezcMailMultipartRelated) {
-                  foreach ($mail->body->getRelatedParts() as $part) {
-                    if (isset($part->subType) and $part->subType == 'plain') {
-                      $text = $part->text;
-                      break;
-                    }
-                  }
-                }
-                else {
-                  foreach ($mail->body->getParts() as $part) {
-                    if (isset($part->subType) and $part->subType == 'plain') {
-                      $text = $part->text;
-                      break;
-                    }
-                  }
-                }
+                $text = self::getTextFromMultipart($mail->body);
               }
 
               if (
@@ -475,4 +429,107 @@ class CRM_Utils_Mail_EmailProcessor {
     }
   }
 
+  /**
+   * @param \ezcMailMultipart $multipart
+   * @param int $recursionLevel
+   *
+   * @return array
+   */
+  protected static function getTextFromMultipart($multipart, $recursionLevel = 0) {
+    if ($recursionLevel >= self::MIME_MAX_RECURSION) {
+      return NULL;
+    }
+    $recursionLevel += 1;
+    $text = NULL;
+    if ($multipart instanceof ezcMailMultipartReport) {
+      $text = self::getTextFromMulipartReport($multipart, $recursionLevel);
+    }
+    elseif ($multipart instanceof ezcMailMultipartRelated) {
+      $text = self::getTextFromMultipartRelated($multipart, $recursionLevel);
+    }
+    else {
+      foreach ($multipart->getParts() as $part) {
+        if (isset($part->subType) and $part->subType === 'plain') {
+          $text = $part->text;
+        }
+        elseif ($part instanceof ezcMailMultipart) {
+          $text = self::getTextFromMultipart($part, $recursionLevel);
+        }
+        if ($text) {
+          break;
+        }
+      }
+    }
+    return $text;
+  }
+
+  /**
+   * @param \ezcMailMultipartRelated $related
+   * @param int $recursionLevel
+   *
+   * @return array
+   */
+  protected static function getTextFromMultipartRelated($related, $recursionLevel) {
+    $text = NULL;
+    foreach ($related->getRelatedParts() as $part) {
+      if (isset($part->subType) and $part->subType === 'plain') {
+        $text = $part->text;
+      }
+      elseif ($part instanceof ezcMailMultipart) {
+        $text = self::getTextFromMultipart($part, $recursionLevel);
+      }
+      if ($text) {
+        break;
+      }
+    }
+    return $text;
+  }
+
+  /**
+   * @param \ezcMailMultipartReport $multipart
+   * @param $recursionLevel
+   *
+   * @return array
+   */
+  protected static function getTextFromMulipartReport($multipart, $recursionLevel) {
+    $text = NULL;
+    $part = $multipart->getMachinePart();
+    if ($part instanceof ezcMailDeliveryStatus) {
+      foreach ($part->recipients as $rec) {
+        if (isset($rec["Diagnostic-Code"])) {
+          $text = $rec["Diagnostic-Code"];
+          break;
+        }
+        elseif (isset($rec["Description"])) {
+          $text = $rec["Description"];
+          break;
+        }
+        // no diagnostic info present - try getting the human readable part
+        elseif (isset($rec["Status"])) {
+          $text = $rec["Status"];
+          $textpart = $multipart->getReadablePart();
+          if ($textpart !== NULL and isset($textpart->text)) {
+            $text .= " " . $textpart->text;
+          }
+          else {
+            $text .= " Delivery failed but no diagnostic code or description.";
+          }
+          break;
+        }
+      }
+    }
+    elseif ($part !== NULL and isset($part->text)) {
+      $text = $part->text;
+    }
+    elseif (($part = $multipart->getReadablePart()) !== NULL) {
+      if (isset($part->text)) {
+        $text = $part->text;
+      }
+      elseif ($part instanceof ezcMailMultipart) {
+        $text = self::getTextFromMultipart($part, $recursionLevel);
+      }
+    }
+    return $text;
+  }
+
 }
index 217b4d4aeb0e7afb061ce6b3a7e8eb7f2724d79a..59a1d0c078445cfaa3b0d3d2075fe13255de8065 100644 (file)
@@ -69,6 +69,21 @@ class CRM_Utils_EmailProcessorTest extends CiviUnitTestCase {
     $this->checkMailingBounces(1);
   }
 
+  /**
+   * Tests that a nested multipart email does not cause pain & misery & fatal errors.
+   *
+   * Sample anonymized from an email that broke bounce processing at Wikimedia
+   */
+  public function testProcessingNestedMultipartEmail() {
+    $this->setUpMailing();
+    $mail = 'test_nested_message.eml';
+
+    copy(__DIR__ . '/data/bounces/' . $mail, __DIR__ . '/data/mail/' . $mail);
+    $this->callAPISuccess('job', 'fetch_bounces', array());
+    $this->assertFalse(file_exists(__DIR__ . '/data/mail/' . $mail));
+    $this->checkMailingBounces(1);
+  }
+
   /**
    * Test that a deleted email does not cause a hard fail.
    *
diff --git a/tests/phpunit/CRM/Utils/Mail/data/bounces/test_nested_message.eml b/tests/phpunit/CRM/Utils/Mail/data/bounces/test_nested_message.eml
new file mode 100644 (file)
index 0000000..96caf13
--- /dev/null
@@ -0,0 +1,313 @@
+Return-Path: <>
+X-Original-To: b.2.1.aaaaaaaaaaaaaaaa@donate.example.org
+Delivered-To: b.2.1.aaaaaaaaaaaaaaaa@example.com
+Received: from mx1001.example.org (mx1001.example.org [127.0.0.1])
+by civicrm.example.org (Postfix) with ESMTPS id 381BD2E95DC
+for <b.aaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaa@donate.example.org>; Sun,  3 Dec 2017 11:29:12 +0000 (UTC)
+Received: from mail-qt0-x241.example.org ([1111:2222:3333:c0d::241]:34862)
+by mx1001.example.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128)
+(Exim 4.84_2)
+id 1eLSSM-0003Vj-7E
+for b.aaaaaaaaaaaaaaaa.b322af9943c6facb@donate.example.org; Sun, 03 Dec 2017 11:29:12 +0000
+Received: by mail-qt0-x241.example.org with SMTP id u10so18073433qtg.2
+       for <b.aaaaaaaaaaaaaaaa.b322af9943c6facb@donate.example.org>; Sun, 03 Dec 2017 03:29:10 -0800 (PST)
+X-Received: by 127.0.0.1 with SMTP id f2mr16998556qth.140.1512300550024;
+       Sun, 03 Dec 2017 03:29:10 -0800 (PST)
+Content-Type: multipart/report; boundary="f403043ae60413e73f055f6de970"; report-type=delivery-status
+Received: by 127.0.0.1 with SMTP id xxxxxxxx.140; Sun, 03 Dec 2017
+03:29:10 -0800 (PST)
+From: Mail Delivery Subsystem <mailer-daemon@example.com>
+To: b.2.1.aaaaaaaaaaaaaaaa@example.com
+Auto-Submitted: auto-replied
+Subject: Delivery Status Notification (Failure)
+References: <xxxxxxxxxxxxxxxxxxxxxxxx@civicrm-mailer>
+In-Reply-To: <xxxxxxxxxxxxxxxxxxxxxxxx@civicrm-mailer>
+X-Failed-Recipients: bob@example.com
+Message-ID: <xxxxxxx.xxxxxx.xxxxx.xxxxx.GMRIR@mx.example.org>
+Date: Sun, 03 Dec 2017 03:29:10 -0800 (PST)
+
+--f403043ae60413e73f055f6de970
+Content-Type: multipart/related; boundary="f403043ae60413e7e5055f6de971"
+
+--f403043ae60413e7e5055f6de971
+Content-Type: multipart/alternative; boundary="f403043ae60413e7eb055f6de972"
+
+--f403043ae60413e7eb055f6de972
+Content-Type: text/plain; charset="UTF-8"
+Content-Transfer-Encoding: quoted-printable
+
+
+** Message not delivered **
+
+There was a problem delivering your message to bob@example.com. See the te=
+chnical details below, or try resending in a few minutes.
+
+Learn more here: https://checkspam.secureserver.net/?sid=xxxxxxxxxxxx=
+1&mid=xxxxxxxxxxxxxxxx
+(Warning: This link will take you to a third-party site)
+
+The response from the remote server was:
+552 5.2.0 aaaaaaaaaaaaaaaa - bbbbbbbbbbbbbbbb This message has been =
+rejected due to content judged to be spam by the internet community IB212 -=
+If you feel this is in error, please submit a request using the following =
+page. <https://checkspam.secureserver.net/?sid=xxxxxxxxxxxx&mid=xxxxx=
+xxxxxxxx>
+
+--f403043ae60413e7eb055f6de972
+Content-Type: text/html; charset="UTF-8"
+Content-Transfer-Encoding: quoted-printable
+
+
+<html>
+<head>
+<style>
+* {
+font-family:Roboto, "Helvetica Neue", Helvetica, Arial, sans-serif;
+}
+</style>
+</head>
+<body>
+<table cellpadding=3D"0" cellspacing=3D"0" class=3D"email-wrapper" style=3D=
+"padding-top:32px;background-color:#ffffff;"><tbody>
+<tr><td>
+<table cellpadding=3D0 cellspacing=3D0><tbody>
+<tr><td style=3D"max-width:560px;padding:24px 24px 32px;background-color:#f=
+afafa;border:1px solid #e0e0e0;border-radius:2px">
+<img style=3D"padding:0 24px 16px 0;float:left" width=3D72 height=3D72 alt=
+=3D"Error Icon" src=3D"cid:icon.png">
+<table style=3D"min-width:272px;padding-top:8px"><tbody>
+<tr><td><h2 style=3D"font-size:20px;color:#212121;font-weight:bold;margin:0=
+">
+Message not delivered
+</h2></td></tr>
+<tr><td style=3D"padding-top:20px;color:#757575;font-size:16px;font-weight:=
+normal;text-align:left">
+There was a problem delivering your message to <a style=3D'color:#212121;te=
+xt-decoration:none'><b>bob@example.com</b></a>. See the technical details =
+below, or try resending in a few minutes.
+</td></tr>
+<tr><td style=3D"padding-top:24px;color:#4285F4;font-size:14px;font-weight:=
+bold;text-align:left">
+<a style=3D"text-decoration:none" href=3D"https://checkspam.secureserver.ne=
+t/?sid=xxxxxxxxxxxxxxxxxxx&mid=xxxxxxxxxxxxxxxx">LEARN MORE</a>
+</td></tr>
+<tr><td style=3D"margin-top:8px;font-style:italic;font-size:12px;color:#757=
+575">
+<img style=3D"padding:0 4 0 0;float:left" width=3D12 height=3D12 alt=3D"War=
+ning" src=3D"cid:warning_triangle.png">
+This link will take you to a third-party site
+</td></tr>
+</tbody></table>
+</td></tr>
+</tbody></table>
+</td></tr>
+<tr style=3D"border:none;background-color:#fff;font-size:12.8px;width:90%">
+<td align=3D"left" style=3D"padding:48px 10px">
+The response from the remote server was:<br/>
+<p style=3D"font-family:monospace">
+552 5.2.0 aaaaaaaaaaaaaaaa - bbbbbbbbbbbbbbbbbbb This message has been =
+rejected due to content judged to be spam by the internet community IB212 -=
+If you feel this is in error, please submit a request using the following =
+page. &lt;https://checkspam.secureserver.net/?sid=xxxxxxxx&amp;m=
+id=xxxxxxxxxxxxxxxx&gt;
+</p>
+</td>
+</tr>
+</tbody></table>
+</body>
+</html>
+
+--f403043ae60413e7eb055f6de972--
+--f403043ae60413e7e5055f6de971
+Content-Type: image/png; name="icon.png"
+Content-Disposition: attachment; filename="icon.png"
+Content-Transfer-Encoding: base64
+Content-ID: <icon.png>
+
+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
+--f403043ae60413e7e5055f6de971
+Content-Type: image/png; name="warning_triangle.png"
+Content-Disposition: attachment; filename="warning_triangle.png"
+Content-Transfer-Encoding: base64
+Content-ID: <warning_triangle.png>
+
+iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAYxJREFUSA3t
+VLFOhEAUBKLFXXMd3VlqZXGJrX6AP+AHWFsZ4CpDoQmBhMTyfkdbEwsrLaiJnQ1XmIAzCuTt3nKs
+xk432fB2dmYeb9+C4/ypEQTBKed3ivZsyavVatd13ZyTsa3OOkFRFBdN0xxwMrZN4NoQ4zj2q6p6
+gfmMfFTxNp1O94G/jumtKoD5TWdOQ8bExsy5P1rBcrlc1HX9AFPlZVBF7XneUZIkj9sSKSITEea3
+ujl5xLhn0khsawJcyTMYHUuBjLlHjsT0eDBBnucTkDMpyLLM5ZQYOS1Xg7+WgwnKsoxAmRtVKjhv
+uSrarowJoijaQ/mhUWEAyaXGsOUYE0CQgswjsh2TVrPB18/TQdPY1LsNph1wgh7dS6pSAb5MD/d7
+9OpJAxlTSw+JKYv1en2OUheSIOOBW9RTqKVHDyDoE6BJM3w413LzJzE96NVpd7oA2a8Q+93a9ER/
+GhOuYX7rdUn8s8lhGPI3/IS19X9eM9WX7+jHYZqmz90R5b9ozmR8UXr+j/ET+ADSfKckAihanAAA
+AABJRU5ErkJggg==
+--f403043ae60413e7e5055f6de971--
+--f403043ae60413e73f055f6de970
+Content-Type: message/global-delivery-status
+
+Reporting-MTA: dns; example.com
+Received-From-MTA: dns; b.aaaaaaaaaaaaaaaa.b322af9943c6facb@donate.example.org
+Arrival-Date: Sun, 03 Dec 2017 03:29:08 -0800 (PST)
+X-Original-Message-ID: <xxxxxxxxxxxxxxxxxxxxxxxx@civicrm-mailer>
+
+Final-Recipient: rfc822; bob@example.com
+Action: failed
+Status: 5.2.0
+Remote-MTA: dns; smtp.secureserver.net (22.33.44.55, the relay for the domain.)
+Diagnostic-Code: smtp; 552 5.2.0 aaaaaaaaaaaaaaaaa - bbbbbbbbbbbbbbbbbbb This message has been rejected due to content judged to b
+e spam by the internet community IB212 - If you feel this is in error, please submit a request using the following page. <https://che
+ckspam.secureserver.net/?sid=aaaaaaaaaaaaaaaaaaa&mid=bbbbbbbbbbbbbbbbbbb>
+Last-Attempt-Date: Sun, 03 Dec 2017 03:29:10 -0800 (PST)
+
+--f403043ae60413e73f055f6de970
+Content-Type: message/rfc822
+
+X-Received: by 127.0.0.1 with SMTP id f2mr16998507qth.140.1512300548827;
+       Sun, 03 Dec 2017 03:29:08 -0800 (PST)
+Return-Path: <b.aaaaaaaaaaaaaaaa.b322af9943c6facb@donate.example.org>
+Received: from civicrm.example.org (civicrm.example.org. [111.222.33.44])
+       by mx.example.org with ESMTPS id w61si1072744qte.335.2017.12.22.33.44.55
+       for <bob@example.com>
+       (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128);
+       Sun, 03 Dec 2017 03:29:08 -0800 (PST)
+Received: by civicrm.example.org (Postfix, from userid 33)
+id 5657F2E7E23; Sun,  3 Dec 2017 11:29:08 +0000 (UTC)
+To: Bob The Bobby one <bob@example.com>
+Subject: =?utf-8?Q?We=E2=80=99re_here_for_you._Thanks_for_being_here_for_us?=
+X-PHP-Originating-Script: 0:class.phpmailer.php
+Date: Sun, 3 Dec 2017 03:29:08 -0800
+From: Donations Manager <donate@example.org>
+Reply-To: Donations Manager <donate@example.org>
+Message-ID: <xxxxxxxxxxxxxxxxxxxxxxxx@civicrm-mailer>
+X-Mailer: PHPMailer 5.2.25 (https://github.com/PHPMailer/PHPMailer)
+List-Unsubscribe: <https://payments.example.org/index.php/>
+MIME-Version: 1.0
+Content-Type: multipart/alternative;
+boundary="b1_xxxxxxxxxxxxxxxxxxxxxxxx"
+
+This is a multi-part message in MIME format.
+
+--b1_xxxxxxxxxxxxxxxxxxxxxxxx
+Content-Type: text/plain; charset=utf-8
+Content-Transfer-Encoding: quoted-printable
+
+Dear Bob, You sent us lots of money, we like money.
+
+--b1_xxxxxxxxxxxxxxxxxxxxxxxx
+Content-Type: text/html; charset=utf-8
+Content-Transfer-Encoding: quoted-printable
+
+
+<html lang=3D"en-us">
+<head>
+   <meta http-equiv=3D"Content-Type" content=3D"text/html; charset=3Dutf-8=
+" />
+</head>
+<body >
+<p>Dear Bob,</p>
+
+
+<p>Thank you for supporting us and sending us lots of money.</p>
+
+----- Message truncated -----
+
+--f403043ae60413e73f055f6de970--