diff options
author | Florian Apolloner <florian@apolloner.eu> | 2020-07-15 07:30:15 +0200 |
---|---|---|
committer | Mariusz Felisiak <felisiak.mariusz@gmail.com> | 2020-07-20 08:11:30 +0200 |
commit | 1a3835fdf364acb3beebccffbc2aa0c1efe66150 (patch) | |
tree | ab933ad9736c21d6c808c4e993052683dd30b583 | |
parent | f1a6e6c817f3205ea7da6f973f51524ff630dda5 (diff) | |
download | django-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.py | 29 | ||||
-rw-r--r-- | docs/releases/2.2.15.txt | 5 | ||||
-rw-r--r-- | tests/mail/tests.py | 33 |
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 |