summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Apolloner <florian@apolloner.eu>2020-07-15 07:30:15 +0200
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2020-07-20 08:11:30 +0200
commit1a3835fdf364acb3beebccffbc2aa0c1efe66150 (patch)
treeab933ad9736c21d6c808c4e993052683dd30b583
parentf1a6e6c817f3205ea7da6f973f51524ff630dda5 (diff)
downloaddjango-1a3835fdf364acb3beebccffbc2aa0c1efe66150.tar.gz
[2.2.x] Fixed #31784 -- Fixed crash when sending emails on Python 3.6.11+, 3.7.8+, and 3.8.4+.
Fixed sending emails crash on email addresses with display names longer then 75 chars on Python 3.6.11+, 3.7.8+, and 3.8.4+. Wrapped display names were passed to email.headerregistry.Address() what caused raising an exception because address parts cannot contain CR or LF. See https://bugs.python.org/issue39073 Co-Authored-By: Mariusz Felisiak <felisiak.mariusz@gmail.com> Backport of 96a3ea39ef0790dbc413dde0a3e19f6a769356a2 from master.
-rw-r--r--django/core/mail/message.py29
-rw-r--r--docs/releases/2.2.15.txt5
-rw-r--r--tests/mail/tests.py33
3 files changed, 58 insertions, 9 deletions
diff --git a/django/core/mail/message.py b/django/core/mail/message.py
index a0e80acce9..7a3ee799f5 100644
--- a/django/core/mail/message.py
+++ b/django/core/mail/message.py
@@ -10,7 +10,9 @@ from email.mime.base import MIMEBase
from email.mime.message import MIMEMessage
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
-from email.utils import formatdate, getaddresses, make_msgid, parseaddr
+from email.utils import (
+ formataddr, formatdate, getaddresses, make_msgid, parseaddr,
+)
from io import BytesIO, StringIO
from pathlib import Path
@@ -103,7 +105,15 @@ def sanitize_address(addr, encoding):
addr = parseaddr(addr)
nm, addr = addr
localpart, domain = None, None
- nm = Header(nm, encoding).encode()
+ if '\n' in nm or '\r' in nm:
+ raise ValueError('Invalid address; address parts cannot contain newlines.')
+
+ # Avoid UTF-8 encode, if it's possible.
+ try:
+ nm.encode('ascii')
+ nm = Header(nm).encode()
+ except UnicodeEncodeError:
+ nm = Header(nm, encoding).encode()
try:
addr.encode('ascii')
except UnicodeEncodeError: # IDN or non-ascii in the local part
@@ -112,15 +122,20 @@ def sanitize_address(addr, encoding):
# An `email.headerregistry.Address` object is used since
# email.utils.formataddr() naively encodes the name as ascii (see #25986).
if localpart and domain:
- address = Address(nm, username=localpart, domain=domain)
- return str(address)
+ address_parts = localpart + domain
+ if '\n' in address_parts or '\r' in address_parts:
+ raise ValueError('Invalid address; address parts cannot contain newlines.')
+ address = Address(username=localpart, domain=domain)
+ return formataddr((nm, address.addr_spec))
try:
- address = Address(nm, addr_spec=addr)
+ if '\n' in addr or '\r' in addr:
+ raise ValueError('Invalid address; address parts cannot contain newlines.')
+ address = Address(addr_spec=addr)
except (InvalidHeaderDefect, NonASCIILocalPartDefect):
localpart, domain = split_addr(addr, encoding)
- address = Address(nm, username=localpart, domain=domain)
- return str(address)
+ address = Address(username=localpart, domain=domain)
+ return formataddr((nm, address.addr_spec))
class MIMEMixin:
diff --git a/docs/releases/2.2.15.txt b/docs/releases/2.2.15.txt
index df26962029..d8ed58b596 100644
--- a/docs/releases/2.2.15.txt
+++ b/docs/releases/2.2.15.txt
@@ -4,10 +4,13 @@ Django 2.2.15 release notes
*Expected August 3, 2020*
-Django 2.2.15 fixes a bug in 2.2.14.
+Django 2.2.15 fixes two bugs in 2.2.14.
Bugfixes
========
* Allowed setting the ``SameSite`` cookie flag in
:meth:`.HttpResponse.delete_cookie` (:ticket:`31790`).
+
+* Fixed crash when sending emails to addresses with display names longer than
+ 75 chars on Python 3.6.11+, 3.7.8+, and 3.8.4+ (:ticket:`31784`).
diff --git a/tests/mail/tests.py b/tests/mail/tests.py
index f06e053074..e62141c040 100644
--- a/tests/mail/tests.py
+++ b/tests/mail/tests.py
@@ -720,7 +720,7 @@ class MailTests(HeadersCheckMixin, SimpleTestCase):
)
self.assertEqual(
sanitize_address(('A name', 'to@example.com'), 'utf-8'),
- '=?utf-8?q?A_name?= <to@example.com>'
+ 'A name <to@example.com>'
)
# Unicode characters are are supported in RFC-6532.
@@ -732,6 +732,37 @@ class MailTests(HeadersCheckMixin, SimpleTestCase):
sanitize_address(('Tó Example', 'tó@example.com'), 'utf-8'),
'=?utf-8?q?T=C3=B3_Example?= <=?utf-8?b?dMOz?=@example.com>'
)
+ # Addresses with long unicode display names.
+ self.assertEqual(
+ sanitize_address('Tó Example very long' * 4 + ' <to@example.com>', 'utf-8'),
+ '=?utf-8?q?T=C3=B3_Example_very_longT=C3=B3_Example_very_longT=C3='
+ 'B3_Example_?=\n'
+ ' =?utf-8?q?very_longT=C3=B3_Example_very_long?= <to@example.com>'
+ )
+ self.assertEqual(
+ sanitize_address(('Tó Example very long' * 4, 'to@example.com'), 'utf-8'),
+ '=?utf-8?q?T=C3=B3_Example_very_longT=C3=B3_Example_very_longT=C3='
+ 'B3_Example_?=\n'
+ ' =?utf-8?q?very_longT=C3=B3_Example_very_long?= <to@example.com>'
+ )
+ # Address with long display name and unicode domain.
+ self.assertEqual(
+ sanitize_address(('To Example very long' * 4, 'to@exampl€.com'), 'utf-8'),
+ 'To Example very longTo Example very longTo Example very longTo Ex'
+ 'ample very\n'
+ ' long <to@xn--exampl-nc1c.com>'
+ )
+
+ def test_sanitize_address_header_injection(self):
+ msg = 'Invalid address; address parts cannot contain newlines.'
+ tests = [
+ ('Name\nInjection', 'to@xample.com'),
+ ('Name', 'to\ninjection@example.com'),
+ ]
+ for email_address in tests:
+ with self.subTest(email_address=email_address):
+ with self.assertRaisesMessage(ValueError, msg):
+ sanitize_address(email_address, encoding='utf-8')
@requires_tz_support