From c4ed6660b30b7d2a7d5fb565df9f95d738e6f221 Mon Sep 17 00:00:00 2001 From: medmunds Date: Thu, 23 Jul 2020 13:06:25 -0700 Subject: [PATCH] Mailjet: fix TypeError in sanitize_address Fix TypeError when sending to or from addresses with display names containing commas. Rewrite Anymail's workaround for Mailjet's problem with commas in display names, to avoid calling Django's internal sanitize_address in an unsupported way. The TypeError results from Django changes that will be introduced in Django 2.2.15, 3.0.9, and 3.1. --- CHANGELOG.rst | 4 ++++ anymail/backends/mailjet.py | 14 ++++++++++---- tests/test_mailjet_backend.py | 8 +++++++- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 6aed100..cca8edd 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -39,6 +39,10 @@ Fixes * **Amazon SES:** Fix bcc, which wasn't working at all on non-template sends. (Thanks to `@mwheels`_ for reporting the issue.) +* **Mailjet:** Fix TypeError when sending to or from addresses with display names + containing commas (introduced in Django 2.2.15, 3.0.9, and 3.1). + + Features ~~~~~~~~ diff --git a/anymail/backends/mailjet.py b/anymail/backends/mailjet.py index 12d57a1..62363e0 100644 --- a/anymail/backends/mailjet.py +++ b/anymail/backends/mailjet.py @@ -1,3 +1,5 @@ +from email.header import Header + from six.moves.urllib.parse import quote from ..exceptions import AnymailRequestsAPIError @@ -158,12 +160,16 @@ class MailjetPayload(RequestsPayload): def _format_email_for_mailjet(self, email): """Return EmailAddress email converted to a string that Mailjet can parse properly""" - # Workaround Mailjet 3.0 bug parsing display-name with commas + # Workaround Mailjet 3.0 bug parsing RFC-2822 quoted display-name with commas # (see test_comma_in_display_name in test_mailjet_backend for details) if "," in email.display_name: - return EmailAddress(email.display_name.encode('utf-8'), email.addr_spec).formataddr('utf-8') - else: - return email.address + # Force MIME "encoded-word" encoding on name, to hide comma from Mailjet. + # We just want the RFC-2047 quoting, not the header wrapping (which will + # be Mailjet's responsibility), so set a huge maxlinelen. + encoded_name = Header(email.display_name.encode('utf-8'), + charset='utf-8', maxlinelen=1000000).encode() + email = EmailAddress(encoded_name, email.addr_spec) + return email.address # # Payload construction diff --git a/tests/test_mailjet_backend.py b/tests/test_mailjet_backend.py index 749e392..d18d157 100644 --- a/tests/test_mailjet_backend.py +++ b/tests/test_mailjet_backend.py @@ -106,13 +106,19 @@ class MailjetBackendStandardEmailTests(MailjetBackendMockAPITestCase): # (This shouldn't be necessary in Mailjet 3.1, where Name becomes a separate json field for Cc/Bcc.) msg = mail.EmailMessage( 'Subject', 'Message', '"Example, Inc." ', - ['"Recipient, Ltd." ']) + ['"Recipient, Ltd." '], + cc=['"This is a very long display name, intended to test our workaround does not insert carriage returns' + ' or newlines into the encoded value, which would cause other problems" ') # this doesn't work self.assertEqual(data['To'], '=?utf-8?q?Recipient=2C_Ltd=2E?= ') # workaround + self.assertEqual(data['Cc'], '=?utf-8?q?This_is_a_very_long_display_name=2C_intended_to_test_our_workaround' + '_does_not_insert_carriage_returns_or_newlines_into_the_encoded_value=2C_which' + '_would_cause_other_problems?= ') def test_email_message(self): email = mail.EmailMessage(