Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Lib/email/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ def formataddr(pair, charset='utf-8'):
'utf-8'.
"""
name, address = pair
if '\r' in address or '\n' in address or (name and ('\r' in name or '\n' in name)):
raise ValueError("invalid arguments; address parts cannot contain CR or LF")
# The address MUST (per RFC) be ascii, so raise a UnicodeError if it isn't.
address.encode('ascii')
if name:
Expand Down
13 changes: 13 additions & 0 deletions Lib/test/test_email/test_email.py
Original file line number Diff line number Diff line change
Expand Up @@ -3277,6 +3277,19 @@ def test_unicode_address_raises_error(self):
self.assertRaises(UnicodeError, utils.formataddr, (None, addr))
self.assertRaises(UnicodeError, utils.formataddr, ("Name", addr))

def test_crlf_in_parts_raises_error(self):
# formataddr() must reject CR and LF in either part so that the
# returned header value cannot be used to inject extra headers,
# matching email.headerregistry.Address.
for name, addr in [
('Real\rName', 'person@dom.ain'),
('Real\nName', 'person@dom.ain'),
('Real Name', 'person@dom.ain\r\nBcc: victim@dom.ain'),
('Real Name', 'person@dom.ain\nSubject: spoofed'),
]:
with self.subTest(name=name, addr=addr):
self.assertRaises(ValueError, utils.formataddr, (name, addr))

def test_name_with_dot(self):
x = 'John X. Doe <jxd@example.com>'
y = '"John X. Doe" <jxd@example.com>'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
:func:`email.utils.formataddr` now raises :exc:`ValueError` when the name or
address contains a carriage return or line feed, preventing header injection
and matching :class:`email.headerregistry.Address`.
Loading