]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-150479: reject CR and LF in email.utils.formataddr (#150480)
authormetsw24-max <metsw24@gmail.com>
Mon, 6 Jul 2026 20:16:43 +0000 (01:46 +0530)
committerGitHub <noreply@github.com>
Mon, 6 Jul 2026 20:16:43 +0000 (16:16 -0400)
Doc/library/email.utils.rst
Lib/email/utils.py
Lib/test/test_email/test_email.py
Misc/NEWS.d/next/Library/2026-05-26-19-30-00.gh-issue-150479.Kq7Lm2.rst [new file with mode: 0644]

index e0d2c19a3b0737af29e99525141e9f50f91df88d..0a96dbfd196a051ac46ba03a04e2f026957233d4 100644 (file)
@@ -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)
 
index d4824dc3601b2dd6f24c46a64d143cfaaa0cf88b..7aefc24c15dc767d784d1b4358d455e1c886e640 100644 (file)
@@ -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:
index 19555d87085e176841ca1f688847c93d4795b505..e40c82bba9af4260d2908d51aecbbed2bfc8a5d9 100644 (file)
@@ -3277,6 +3277,28 @@ class TestMiscellaneous(TestEmailBase):
         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_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', 'person@dom.ain'), strict=False),
+            'Real\rName <person@dom.ain>')
+        self.assertEqual(
+            utils.formataddr(('Real Name', 'person@dom.ain\nfoo'), strict=False),
+            'Real Name <person@dom.ain\nfoo>')
+
     def test_name_with_dot(self):
         x = 'John X. Doe <jxd@example.com>'
         y = '"John X. Doe" <jxd@example.com>'
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 (file)
index 0000000..5a41273
--- /dev/null
@@ -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``.