Skip to content

Commit ef4d3f3

Browse files
authored
gh-150479: reject CR and LF in email.utils.formataddr (#150480)
1 parent 2092192 commit ef4d3f3

4 files changed

Lines changed: 42 additions & 2 deletions

File tree

Doc/library/email.utils.rst

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ of the new API.
7070
Add *strict* optional parameter and reject malformed inputs by default.
7171

7272

73-
.. function:: formataddr(pair, charset='utf-8')
73+
.. function:: formataddr(pair, charset='utf-8', *, strict=True)
7474

7575
The inverse of :meth:`parseaddr`, this takes a 2-tuple of the form ``(realname,
7676
email_address)`` and returns the string value suitable for a :mailheader:`To` or
@@ -82,9 +82,16 @@ of the new API.
8282
characters. Can be an instance of :class:`str` or a
8383
:class:`~email.charset.Charset`. Defaults to ``utf-8``.
8484

85+
If *strict* is true (the default), raise :exc:`ValueError` for inputs that
86+
contain CR or LF, which are not allowed in an email address. Set *strict*
87+
to ``False`` to allow non-strict inputs.
88+
8589
.. versionchanged:: 3.3
8690
Added the *charset* option.
8791

92+
.. versionchanged:: next
93+
Added the *strict* parameter.
94+
8895

8996
.. function:: getaddresses(fieldvalues, *, strict=True)
9097

Lib/email/utils.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ def _sanitize(string):
6969

7070
# Helpers
7171

72-
def formataddr(pair, charset='utf-8'):
72+
def formataddr(pair, charset='utf-8', *, strict=True):
7373
"""The inverse of parseaddr(), this takes a 2-tuple of the form
7474
(realname, email_address) and returns the string value suitable
7575
for an RFC 2822 From, To or Cc header.
@@ -81,8 +81,15 @@ def formataddr(pair, charset='utf-8'):
8181
realname in case realname is not ASCII safe. Can be an instance of str or
8282
a Charset-like object which has a header_encode method. Default is
8383
'utf-8'.
84+
85+
If strict is True (the default), raise ValueError for inputs that
86+
contain CR or LF, which are not allowed in an email address.
8487
"""
8588
name, address = pair
89+
if strict and ('\r' in address or '\n' in address
90+
or (name and ('\r' in name or '\n' in name))):
91+
raise ValueError(
92+
"invalid arguments; address parts cannot contain CR or LF")
8693
# The address MUST (per RFC) be ascii, so raise a UnicodeError if it isn't.
8794
address.encode('ascii')
8895
if name:

Lib/test/test_email/test_email.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3277,6 +3277,28 @@ def test_unicode_address_raises_error(self):
32773277
self.assertRaises(UnicodeError, utils.formataddr, (None, addr))
32783278
self.assertRaises(UnicodeError, utils.formataddr, ("Name", addr))
32793279

3280+
def test_crlf_in_parts_raises_error(self):
3281+
# formataddr() must reject CR and LF in either part so that the
3282+
# returned header value cannot be used to inject extra headers,
3283+
# matching email.headerregistry.Address.
3284+
for name, addr in [
3285+
('Real\rName', 'person@dom.ain'),
3286+
('Real\nName', 'person@dom.ain'),
3287+
('Real Name', 'person@dom.ain\r\nBcc: victim@dom.ain'),
3288+
('Real Name', 'person@dom.ain\nSubject: spoofed'),
3289+
]:
3290+
with self.subTest(name=name, addr=addr):
3291+
self.assertRaises(ValueError, utils.formataddr, (name, addr))
3292+
3293+
def test_crlf_in_parts_allowed_when_not_strict(self):
3294+
# strict=False keeps the old behaviour and passes CR/LF through.
3295+
self.assertEqual(
3296+
utils.formataddr(('Real\rName', 'person@dom.ain'), strict=False),
3297+
'Real\rName <person@dom.ain>')
3298+
self.assertEqual(
3299+
utils.formataddr(('Real Name', 'person@dom.ain\nfoo'), strict=False),
3300+
'Real Name <person@dom.ain\nfoo>')
3301+
32803302
def test_name_with_dot(self):
32813303
x = 'John X. Doe <jxd@example.com>'
32823304
y = '"John X. Doe" <jxd@example.com>'
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
:func:`email.utils.formataddr` now raises :exc:`ValueError` when the name or
2+
address contains a carriage return or line feed, matching
3+
:class:`email.headerregistry.Address`. This check can be disabled by passing
4+
``strict=False``.

0 commit comments

Comments
 (0)