[3.1.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 96a3ea39ef
from master
This commit is contained in:
parent
474f65406f
commit
bfe404deb9
|
@ -10,7 +10,7 @@ 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
|
from email.utils import formataddr, formatdate, getaddresses, make_msgid
|
||||||
from io import BytesIO, StringIO
|
from io import BytesIO, StringIO
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
@ -96,16 +96,24 @@ def sanitize_address(addr, encoding):
|
||||||
nm, address = addr
|
nm, address = addr
|
||||||
localpart, domain = address.rsplit('@', 1)
|
localpart, domain = address.rsplit('@', 1)
|
||||||
|
|
||||||
nm = Header(nm, encoding).encode()
|
address_parts = nm + localpart + domain
|
||||||
|
if '\n' in address_parts or '\r' in address_parts:
|
||||||
|
raise ValueError('Invalid address; address parts cannot contain newlines.')
|
||||||
|
|
||||||
# Avoid UTF-8 encode, if it's possible.
|
# 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:
|
||||||
localpart.encode('ascii')
|
localpart.encode('ascii')
|
||||||
except UnicodeEncodeError:
|
except UnicodeEncodeError:
|
||||||
localpart = Header(localpart, encoding).encode()
|
localpart = Header(localpart, encoding).encode()
|
||||||
domain = punycode(domain)
|
domain = punycode(domain)
|
||||||
|
|
||||||
parsed_address = Address(nm, username=localpart, domain=domain)
|
parsed_address = Address(username=localpart, domain=domain)
|
||||||
return str(parsed_address)
|
return formataddr((nm, parsed_address.addr_spec))
|
||||||
|
|
||||||
|
|
||||||
class MIMEMixin:
|
class MIMEMixin:
|
||||||
|
|
|
@ -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`).
|
||||||
|
|
|
@ -11,3 +11,6 @@ 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`).
|
||||||
|
|
|
@ -730,14 +730,14 @@ class MailTests(HeadersCheckMixin, SimpleTestCase):
|
||||||
(
|
(
|
||||||
('A name', 'to@example.com'),
|
('A name', 'to@example.com'),
|
||||||
'utf-8',
|
'utf-8',
|
||||||
'=?utf-8?q?A_name?= <to@example.com>',
|
'A name <to@example.com>',
|
||||||
),
|
),
|
||||||
('localpartonly', 'ascii', 'localpartonly'),
|
('localpartonly', 'ascii', 'localpartonly'),
|
||||||
# ASCII addresses with display names.
|
# ASCII addresses with display names.
|
||||||
('A name <to@example.com>', 'ascii', 'A name <to@example.com>'),
|
('A name <to@example.com>', 'ascii', 'A name <to@example.com>'),
|
||||||
('A name <to@example.com>', 'utf-8', '=?utf-8?q?A_name?= <to@example.com>'),
|
('A name <to@example.com>', 'utf-8', 'A name <to@example.com>'),
|
||||||
('"A name" <to@example.com>', 'ascii', 'A name <to@example.com>'),
|
('"A name" <to@example.com>', 'ascii', 'A name <to@example.com>'),
|
||||||
('"A name" <to@example.com>', 'utf-8', '=?utf-8?q?A_name?= <to@example.com>'),
|
('"A name" <to@example.com>', 'utf-8', 'A name <to@example.com>'),
|
||||||
# Unicode addresses (supported per RFC-6532).
|
# Unicode addresses (supported per RFC-6532).
|
||||||
('tó@example.com', 'utf-8', '=?utf-8?b?dMOz?=@example.com'),
|
('tó@example.com', 'utf-8', '=?utf-8?b?dMOz?=@example.com'),
|
||||||
('to@éxample.com', 'utf-8', 'to@xn--xample-9ua.com'),
|
('to@éxample.com', 'utf-8', 'to@xn--xample-9ua.com'),
|
||||||
|
@ -756,20 +756,45 @@ class MailTests(HeadersCheckMixin, SimpleTestCase):
|
||||||
(
|
(
|
||||||
'To Example <to@éxample.com>',
|
'To Example <to@éxample.com>',
|
||||||
'utf-8',
|
'utf-8',
|
||||||
'=?utf-8?q?To_Example?= <to@xn--xample-9ua.com>',
|
'To Example <to@xn--xample-9ua.com>',
|
||||||
),
|
),
|
||||||
# Addresses with two @ signs.
|
# Addresses with two @ signs.
|
||||||
('"to@other.com"@example.com', 'utf-8', r'"to@other.com"@example.com'),
|
('"to@other.com"@example.com', 'utf-8', r'"to@other.com"@example.com'),
|
||||||
(
|
(
|
||||||
'"to@other.com" <to@example.com>',
|
'"to@other.com" <to@example.com>',
|
||||||
'utf-8',
|
'utf-8',
|
||||||
'=?utf-8?q?to=40other=2Ecom?= <to@example.com>',
|
'"to@other.com" <to@example.com>',
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
('To Example', 'to@other.com@example.com'),
|
('To Example', 'to@other.com@example.com'),
|
||||||
'utf-8',
|
'utf-8',
|
||||||
'=?utf-8?q?To_Example?= <"to@other.com"@example.com>',
|
'To Example <"to@other.com"@example.com>',
|
||||||
),
|
),
|
||||||
|
# Addresses with long unicode display names.
|
||||||
|
(
|
||||||
|
'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>',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
('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.
|
||||||
|
(
|
||||||
|
('To Example very long' * 4, 'to@exampl€.com'),
|
||||||
|
'utf-8',
|
||||||
|
'To Example very longTo Example very longTo Example very longT'
|
||||||
|
'o Example very\n'
|
||||||
|
' long <to@xn--exampl-nc1c.com>'
|
||||||
|
)
|
||||||
):
|
):
|
||||||
with self.subTest(email_address=email_address, encoding=encoding):
|
with self.subTest(email_address=email_address, encoding=encoding):
|
||||||
self.assertEqual(sanitize_address(email_address, encoding), expected_result)
|
self.assertEqual(sanitize_address(email_address, encoding), expected_result)
|
||||||
|
@ -789,6 +814,19 @@ class MailTests(HeadersCheckMixin, SimpleTestCase):
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
sanitize_address(email_address, encoding='utf-8')
|
sanitize_address(email_address, encoding='utf-8')
|
||||||
|
|
||||||
|
def test_sanitize_address_header_injection(self):
|
||||||
|
msg = 'Invalid address; address parts cannot contain newlines.'
|
||||||
|
tests = [
|
||||||
|
'Name\nInjection <to@example.com>',
|
||||||
|
('Name\nInjection', 'to@xample.com'),
|
||||||
|
'Name <to\ninjection@example.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
|
||||||
class MailTimeZoneTests(SimpleTestCase):
|
class MailTimeZoneTests(SimpleTestCase):
|
||||||
|
|
Loading…
Reference in New Issue