Skip to content

Commit 5987e83

Browse files
committed
Postmark inbound: Fix ValueError with long, non-ASCII name
Receiving a Postmark inbound message with a long From or recipient Name field could result in "ValueError: Header values may not contain linefeed or carriage return characters" if the name included non-ASCII characters. Anymail's EmailAddress calls Django's sanitize_address(), which can introduce folding whitespace, causing an error in the AnymailInboundMessage constructor. Only Postmark inbound is affected, as no other webhooks construct an EmailAddress. (Longer-term, Anymail should stop using the undocumented Django sanitize_address.)
1 parent 77b9701 commit 5987e83

File tree

3 files changed

+37
-1
lines changed

3 files changed

+37
-1
lines changed

CHANGELOG.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ Fixes
5353
"queued" in the ``anymail_status`` (rather than throwing an error or reporting
5454
"sent"). (Thanks to `@jmduke`_ for reporting the issue.)
5555

56+
* **Postmark:** Fix an error in inbound handling with long email address display
57+
names that include non-ASCII characters.
58+
5659

5760
v12.0
5861
-----

anymail/utils.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import base64
22
import mimetypes
3+
import re
34
from base64 import b64encode
45
from collections.abc import Mapping, MutableMapping
56
from copy import copy, deepcopy
@@ -342,7 +343,10 @@ def formataddr(self, encoding=None):
342343
default None uses ascii if possible, else 'utf-8'
343344
(quoted-printable utf-8/base64)
344345
"""
345-
return sanitize_address((self.display_name, self.addr_spec), encoding)
346+
sanitized = sanitize_address((self.display_name, self.addr_spec), encoding)
347+
# sanitize_address() can introduce FWS with a long, non-ASCII display name.
348+
# Must unfold it:
349+
return re.sub(r"(\r|\n|\r\n)[ \t]", "", sanitized)
346350

347351
def __str__(self):
348352
return self.address

tests/test_postmark_inbound.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,3 +392,32 @@ def test_check_payload(self):
392392
)
393393
# Don't care about the actual test message contents here,
394394
# just want to make sure it parses and signals inbound without error.
395+
396+
def test_spammy_address(self):
397+
# This long FromFull.Name caused an error in AnymailInboundMessage construction,
398+
# due to folding introduced in EmailAddress.formataddr()
399+
from_name = (
400+
"* * * 💲 Snag Your Free Gift! Click Here: "
401+
"https://spam.example.com/uploads/phish.php?123456 💲 * * *"
402+
)
403+
raw_event = {
404+
"MessageStream": "inbound",
405+
"FromFull": {
406+
"Email": "noreply@spam.example.com",
407+
"Name": from_name,
408+
},
409+
}
410+
response = self.client.post(
411+
"/anymail/postmark/inbound/",
412+
content_type="application/json",
413+
data=json.dumps(raw_event),
414+
)
415+
self.assertEqual(response.status_code, 200)
416+
kwargs = self.assert_handler_called_once_with(
417+
self.inbound_handler,
418+
sender=PostmarkInboundWebhookView,
419+
event=ANY,
420+
esp_name="Postmark",
421+
)
422+
message = kwargs["event"].message
423+
self.assertEqual(message.from_email.display_name, from_name)

0 commit comments

Comments
 (0)