https://github.com/python/cpython/commit/ef4d3f3079fbad40cb7f5619a7417e40f7e3c0f9
commit: ef4d3f3079fbad40cb7f5619a7417e40f7e3c0f9
branch: main
author: metsw24-max <[email protected]>
committer: bitdancer <[email protected]>
date: 2026-07-06T16:16:43-04:00
summary:
gh-150479: reject CR and LF in email.utils.formataddr (#150480)
files:
A Misc/NEWS.d/next/Library/2026-05-26-19-30-00.gh-issue-150479.Kq7Lm2.rst
M Doc/library/email.utils.rst
M Lib/email/utils.py
M Lib/test/test_email/test_email.py
diff --git a/Doc/library/email.utils.rst b/Doc/library/email.utils.rst
index e0d2c19a3b0737a..0a96dbfd196a051 100644
--- a/Doc/library/email.utils.rst
+++ b/Doc/library/email.utils.rst
@@ -70,7 +70,7 @@ of the new API.
Add *strict* optional parameter and reject malformed inputs by default.
-.. function:: formataddr(pair, charset='utf-8')
+.. function:: formataddr(pair, charset='utf-8', *, strict=True)
The inverse of :meth:`parseaddr`, this takes a 2-tuple of the form
``(realname,
email_address)`` and returns the string value suitable for a
:mailheader:`To` or
@@ -82,9 +82,16 @@ of the new API.
characters. Can be an instance of :class:`str` or a
:class:`~email.charset.Charset`. Defaults to ``utf-8``.
+ If *strict* is true (the default), raise :exc:`ValueError` for inputs that
+ contain CR or LF, which are not allowed in an email address. Set *strict*
+ to ``False`` to allow non-strict inputs.
+
.. versionchanged:: 3.3
Added the *charset* option.
+ .. versionchanged:: next
+ Added the *strict* parameter.
+
.. function:: getaddresses(fieldvalues, *, strict=True)
diff --git a/Lib/email/utils.py b/Lib/email/utils.py
index d4824dc3601b2dd..7aefc24c15dc767 100644
--- a/Lib/email/utils.py
+++ b/Lib/email/utils.py
@@ -69,7 +69,7 @@ def _sanitize(string):
# Helpers
-def formataddr(pair, charset='utf-8'):
+def formataddr(pair, charset='utf-8', *, strict=True):
"""The inverse of parseaddr(), this takes a 2-tuple of the form
(realname, email_address) and returns the string value suitable
for an RFC 2822 From, To or Cc header.
@@ -81,8 +81,15 @@ def formataddr(pair, charset='utf-8'):
realname in case realname is not ASCII safe. Can be an instance of str or
a Charset-like object which has a header_encode method. Default is
'utf-8'.
+
+ If strict is True (the default), raise ValueError for inputs that
+ contain CR or LF, which are not allowed in an email address.
"""
name, address = pair
+ if strict and ('\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:
diff --git a/Lib/test/test_email/test_email.py
b/Lib/test/test_email/test_email.py
index 19555d87085e176..e40c82bba9af426 100644
--- a/Lib/test/test_email/test_email.py
+++ b/Lib/test/test_email/test_email.py
@@ -3277,6 +3277,28 @@ 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', '[email protected]'),
+ ('Real\nName', '[email protected]'),
+ ('Real Name', '[email protected]\r\nBcc: [email protected]'),
+ ('Real Name', '[email protected]\nSubject: spoofed'),
+ ]:
+ with self.subTest(name=name, addr=addr):
+ self.assertRaises(ValueError, utils.formataddr, (name, addr))
+
+ def test_crlf_in_parts_allowed_when_not_strict(self):
+ # strict=False keeps the old behaviour and passes CR/LF through.
+ self.assertEqual(
+ utils.formataddr(('Real\rName', '[email protected]'), strict=False),
+ 'Real\rName <[email protected]>')
+ self.assertEqual(
+ utils.formataddr(('Real Name', '[email protected]\nfoo'),
strict=False),
+ 'Real Name <[email protected]\nfoo>')
+
def test_name_with_dot(self):
x = 'John X. Doe <[email protected]>'
y = '"John X. Doe" <[email protected]>'
diff --git
a/Misc/NEWS.d/next/Library/2026-05-26-19-30-00.gh-issue-150479.Kq7Lm2.rst
b/Misc/NEWS.d/next/Library/2026-05-26-19-30-00.gh-issue-150479.Kq7Lm2.rst
new file mode 100644
index 000000000000000..5a41273f3501e39
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-05-26-19-30-00.gh-issue-150479.Kq7Lm2.rst
@@ -0,0 +1,4 @@
+:func:`email.utils.formataddr` now raises :exc:`ValueError` when the name or
+address contains a carriage return or line feed, matching
+:class:`email.headerregistry.Address`. This check can be disabled by passing
+``strict=False``.
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]