1
0
mirror of https://github.com/django/django.git synced 2025-07-11 21:29:12 +00:00

[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.
This commit is contained in:
Florian Apolloner 2020-07-15 07:30:15 +02:00 committed by Mariusz Felisiak
parent f1a6e6c817
commit 1a3835fdf3
3 changed files with 58 additions and 9 deletions

View File

@ -10,7 +10,9 @@ from email.mime.base import MIMEBase
from email.mime.message import MIMEMessage from email.mime.message import MIMEMessage
from email.mime.multipart import MIMEMultipart from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText 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 io import BytesIO, StringIO
from pathlib import Path from pathlib import Path
@ -103,7 +105,15 @@ def sanitize_address(addr, encoding):
addr = parseaddr(addr) addr = parseaddr(addr)
nm, addr = addr nm, addr = addr
localpart, domain = None, None 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: try:
addr.encode('ascii') addr.encode('ascii')
except UnicodeEncodeError: # IDN or non-ascii in the local part 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 # An `email.headerregistry.Address` object is used since
# email.utils.formataddr() naively encodes the name as ascii (see #25986). # email.utils.formataddr() naively encodes the name as ascii (see #25986).
if localpart and domain: if localpart and domain:
address = Address(nm, username=localpart, domain=domain) address_parts = localpart + domain
return str(address) 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: 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): except (InvalidHeaderDefect, NonASCIILocalPartDefect):
localpart, domain = split_addr(addr, encoding) localpart, domain = split_addr(addr, encoding)
address = Address(nm, username=localpart, domain=domain) address = Address(username=localpart, domain=domain)
return str(address) return formataddr((nm, address.addr_spec))
class MIMEMixin: class MIMEMixin:

View File

@ -4,10 +4,13 @@ Django 2.2.15 release notes
*Expected August 3, 2020* *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 Bugfixes
======== ========
* Allowed setting the ``SameSite`` cookie flag in * Allowed setting the ``SameSite`` cookie flag in
:meth:`.HttpResponse.delete_cookie` (:ticket:`31790`). :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`).

View File

@ -720,7 +720,7 @@ class MailTests(HeadersCheckMixin, SimpleTestCase):
) )
self.assertEqual( self.assertEqual(
sanitize_address(('A name', 'to@example.com'), 'utf-8'), 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. # 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'), sanitize_address(('Tó Example', 'tó@example.com'), 'utf-8'),
'=?utf-8?q?T=C3=B3_Example?= <=?utf-8?b?dMOz?=@example.com>' '=?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 @requires_tz_support