[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]

[tor-commits] [bridgedb/develop] Only process text/plain part of emails.



commit 61e63879157343571f89ec02e0e21dd19e270e64
Author: Philipp Winter <phw@xxxxxxxxx>
Date:   Mon Jun 1 15:12:34 2020 -0700

    Only process text/plain part of emails.
    
    When our autoresponder receives a multipart email from a user, it may
    get confused by the parts that are not text/plain.  Instead of bending
    over backwards to parse all sorts email encodings, this patch discard
    the parts of a multipart email that aren't text/plain.
    
    This fixes tpo/anti-censorship/bridgedb#33835.
---
 CHANGELOG                             |  4 +++
 bridgedb/distributors/email/server.py | 18 +++++++++-
 bridgedb/test/test_email_server.py    | 63 +++++++++++++++++++++++++++++++++++
 3 files changed, 84 insertions(+), 1 deletion(-)

diff --git a/CHANGELOG b/CHANGELOG
index 860cbda..ad20b91 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,7 @@
+    * FIXES https://gitlab.torproject.org/tpo/anti-censorship/bridgedb/-/issues/33835
+    If a user sends a multipart email to our autoresponder, we now only parse
+    the part that has a text/plain encoding.
+
     * FIXES https://bugs.torproject.org/33647
     Fix broken link in the README and fix our Makefile's pylint target.
 
diff --git a/bridgedb/distributors/email/server.py b/bridgedb/distributors/email/server.py
index 54a8231..b2baf86 100644
--- a/bridgedb/distributors/email/server.py
+++ b/bridgedb/distributors/email/server.py
@@ -50,6 +50,7 @@ Servers which interface with clients and distribute bridges over SMTP.
 from __future__ import unicode_literals
 
 import email.message
+import email.policy
 import logging
 import io
 import socket
@@ -228,6 +229,20 @@ class SMTPMessage(object):
         if not self.ignoring:
             self.message = self.getIncomingMessage()
             self.responder.reply()
+
+        # If a user sends a multipart email, we only consider the part whose
+        # content type is text/plain.
+        if self.message.is_multipart():
+            has_plaintext = False
+            for part in self.message.get_payload():
+                if part.get_content_type() != "text/plain":
+                    continue
+                self.lines = part.get_payload().split("\n")
+                has_plaintext = True
+
+            if not has_plaintext:
+                logging.warning("User email had no text/plain content type.")
+
         return defer.succeed(None)
 
     def connectionLost(self):
@@ -242,7 +257,8 @@ class SMTPMessage(object):
         :returns: A ``Message`` comprised of all lines received thus far.
         """
 
-        return email.message_from_string('\n'.join(self.lines))
+        return email.message_from_string('\n'.join(self.lines),
+                                         policy=email.policy.compat32)
 
 
 @implementer(smtp.IMessageDelivery)
diff --git a/bridgedb/test/test_email_server.py b/bridgedb/test/test_email_server.py
index fb82cc0..e0aee18 100644
--- a/bridgedb/test/test_email_server.py
+++ b/bridgedb/test/test_email_server.py
@@ -100,6 +100,69 @@ class SMTPMessageTests(unittest.TestCase):
         self.assertIsInstance(self.message.getIncomingMessage(),
                               email.message.Message)
 
+    def test_SMTPMessage_multipart1(self):
+        """`eomReceived` should get rid of HTML multiparts."""
+
+        # Gmail's web interface would send a message like this when replying to
+        # one of BridgeDB's emails.
+        multipartEmail = [
+        'MIME-Version: 1.0',
+        'Date: Mon, 1 Jun 2020 15:55:33 -0700',
+        'Subject: Re: test',
+        'From: Foo Bar <foo@xxxxxxx>',
+        'To: bridges@xxxxxxxxxxxxxxxxxxxxxx',
+        'Content-Type: multipart/alternative; boundary="00000000000041b34105a70db186"',
+        '',
+        '--00000000000041b34105a70db186',
+        'Content-Type: text/plain; charset="UTF-8"',
+        '',
+        'This is plaintext.',
+        '',
+        '--00000000000041b34105a70db186',
+        'Content-Type: text/html; charset="UTF-8"',
+        'Content-Transfer-Encoding: quoted-printable',
+        '',
+        'This is HTML.',
+        '',
+        '--00000000000041b34105a70db186--']
+
+        for line in multipartEmail:
+            self.message.lineReceived(line)
+        self.message.eomReceived()
+        content = "".join(self.message.lines)
+        self.assertTrue("This is plaintext." in content)
+        self.assertFalse("This is HTML." in content)
+
+    def test_SMTPMessage_multipart2(self):
+        """`eomReceived` should get rid of HTML multiparts."""
+
+        # Outlook would send a message like this when replying to one of
+        # BridgeDB's emails.
+        multipartEmail = [
+        'Content-Type: multipart/alternative;',
+        '              boundary="_000_VI1PR08MB4351A21D6C4A31C2B0FDA34F8CC10VI1PR08MB4351eurp_"',
+        '',
+        '--_000_VI1PR08MB4351A21D6C4A31C2B0FDA34F8CC10VI1PR08MB4351eurp_',
+        'Content-Type: text/plain; charset="us-ascii"',
+        'Content-Transfer-Encoding: quoted-printable',
+        '',
+        'This is plaintext.',
+        '',
+        '--_000_VI1PR08MB4351A21D6C4A31C2B0FDA34F8CC10VI1PR08MB4351eurp_',
+        'Content-Type: text/html; charset="us-ascii"',
+        'Content-Transfer-Encoding: quoted-printable',
+        '',
+        'This is HTML.',
+        '',
+        '--_000_VI1PR08MB4351A21D6C4A31C2B0FDA34F8CC10VI1PR08MB4351eurp_--']
+
+        for line in multipartEmail:
+            self.message.lineReceived(line)
+        self.message.eomReceived()
+        content = "".join(self.message.lines)
+        self.assertTrue("This is plaintext." in content)
+        self.assertFalse("This is HTML." in content)
+
 
 class SMTPIncomingDeliveryTests(unittest.TestCase):
     """Unittests for :class:`email.server.SMTPIncomingDelivery`."""



_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits