summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSrinivas Reddy Thatiparthy (తాటిపర్తి శ్రీనివాస్ రెడ్డి) <thatiparthysreenivas@gmail.com>2025-03-30 12:29:29 (GMT)
committerGitHub <noreply@github.com>2025-03-30 12:29:29 (GMT)
commitc432d0147bdf1a66604e7a3d6a71660ae79b5f45 (patch)
tree6ea2c74ba2ad5eb95074fe4accc91d014c27c7bd
parent55150a79cacbce44f50cea128c511782df0ab277 (diff)
downloadcpython-c432d0147bdf1a66604e7a3d6a71660ae79b5f45.zip
cpython-c432d0147bdf1a66604e7a3d6a71660ae79b5f45.tar.gz
cpython-c432d0147bdf1a66604e7a3d6a71660ae79b5f45.tar.bz2
gh-127794: Validate email header names according to RFC 5322 (#127820)
`email.message.Message` objects now validate header names specified via `__setitem__` or `add_header` according to RFC 5322, §2.2 [1]. In particular, callers should expect a ValueError to be raised for invalid header names. [1]: https://datatracker.ietf.org/doc/html/rfc5322#section-2.2 --------- Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> Co-authored-by: R. David Murray <rdmurray@bitdance.com>
-rw-r--r--Lib/email/_policybase.py10
-rw-r--r--Lib/email/policy.py9
-rw-r--r--Lib/test/test_email/test_email.py25
-rw-r--r--Lib/test/test_email/test_message.py24
-rw-r--r--Misc/NEWS.d/next/Library/2024-12-11-17-44-36.gh-issue-127794.VwmRsp.rst4
5 files changed, 71 insertions, 1 deletions
diff --git a/Lib/email/_policybase.py b/Lib/email/_policybase.py
index 4b63b97..95e79b8 100644
--- a/Lib/email/_policybase.py
+++ b/Lib/email/_policybase.py
@@ -4,6 +4,7 @@ Allows fine grained feature control of how the package parses and emits data.
"""
import abc
+import re
from email import header
from email import charset as _charset
from email.utils import _has_surrogates
@@ -14,6 +15,14 @@ __all__ = [
'compat32',
]
+# validation regex from RFC 5322, equivalent to pattern re.compile("[!-9;-~]+$")
+valid_header_name_re = re.compile("[\041-\071\073-\176]+$")
+
+def validate_header_name(name):
+ # Validate header name according to RFC 5322
+ if not valid_header_name_re.match(name):
+ raise ValueError(
+ f"Header field name contains invalid characters: {name!r}")
class _PolicyBase:
@@ -314,6 +323,7 @@ class Compat32(Policy):
"""+
The name and value are returned unmodified.
"""
+ validate_header_name(name)
return (name, value)
def header_fetch_parse(self, name, value):
diff --git a/Lib/email/policy.py b/Lib/email/policy.py
index 6e109b6..4169150 100644
--- a/Lib/email/policy.py
+++ b/Lib/email/policy.py
@@ -4,7 +4,13 @@ code that adds all the email6 features.
import re
import sys
-from email._policybase import Policy, Compat32, compat32, _extend_docstrings
+from email._policybase import (
+ Compat32,
+ Policy,
+ _extend_docstrings,
+ compat32,
+ validate_header_name
+)
from email.utils import _has_surrogates
from email.headerregistry import HeaderRegistry as HeaderRegistry
from email.contentmanager import raw_data_manager
@@ -138,6 +144,7 @@ class EmailPolicy(Policy):
CR or LF characters.
"""
+ validate_header_name(name)
if hasattr(value, 'name') and value.name.lower() == name.lower():
return (name, value)
if isinstance(value, str) and len(value.splitlines())>1:
diff --git a/Lib/test/test_email/test_email.py b/Lib/test/test_email/test_email.py
index 2deb357..724af3b 100644
--- a/Lib/test/test_email/test_email.py
+++ b/Lib/test/test_email/test_email.py
@@ -728,6 +728,31 @@ class TestMessageAPI(TestEmailBase):
"attachment; filename*=utf-8''Fu%C3%9Fballer%20%5Bfilename%5D.ppt",
msg['Content-Disposition'])
+ def test_invalid_header_names(self):
+ invalid_headers = [
+ ('Invalid Header', 'contains space'),
+ ('Tab\tHeader', 'contains tab'),
+ ('Colon:Header', 'contains colon'),
+ ('', 'Empty name'),
+ (' LeadingSpace', 'starts with space'),
+ ('TrailingSpace ', 'ends with space'),
+ ('Header\x7F', 'Non-ASCII character'),
+ ('Header\x80', 'Extended ASCII'),
+ ]
+ for policy in (email.policy.default, email.policy.compat32):
+ for setter in (Message.__setitem__, Message.add_header):
+ for name, value in invalid_headers:
+ self.do_test_invalid_header_names(
+ policy, setter,name, value)
+
+ def do_test_invalid_header_names(self, policy, setter, name, value):
+ with self.subTest(policy=policy, setter=setter, name=name, value=value):
+ message = Message(policy=policy)
+ pattern = r'(?i)(?=.*invalid)(?=.*header)(?=.*name)'
+ with self.assertRaisesRegex(ValueError, pattern) as cm:
+ setter(message, name, value)
+ self.assertIn(f"{name!r}", str(cm.exception))
+
def test_binary_quopri_payload(self):
for charset in ('latin-1', 'ascii'):
msg = Message()
diff --git a/Lib/test/test_email/test_message.py b/Lib/test/test_email/test_message.py
index 96979db..23c3977 100644
--- a/Lib/test/test_email/test_message.py
+++ b/Lib/test/test_email/test_message.py
@@ -1004,6 +1004,30 @@ class TestEmailMessage(TestEmailMessageBase, TestEmailBase):
parsed_msg = message_from_bytes(m.as_bytes(), policy=policy.default)
self.assertEqual(parsed_msg['Message-ID'], m['Message-ID'])
+ def test_invalid_header_names(self):
+ invalid_headers = [
+ ('Invalid Header', 'contains space'),
+ ('Tab\tHeader', 'contains tab'),
+ ('Colon:Header', 'contains colon'),
+ ('', 'Empty name'),
+ (' LeadingSpace', 'starts with space'),
+ ('TrailingSpace ', 'ends with space'),
+ ('Header\x7F', 'Non-ASCII character'),
+ ('Header\x80', 'Extended ASCII'),
+ ]
+ for email_policy in (policy.default, policy.compat32):
+ for setter in (EmailMessage.__setitem__, EmailMessage.add_header):
+ for name, value in invalid_headers:
+ self.do_test_invalid_header_names(email_policy, setter, name, value)
+
+ def do_test_invalid_header_names(self, policy, setter, name, value):
+ with self.subTest(policy=policy, setter=setter, name=name, value=value):
+ message = EmailMessage(policy=policy)
+ pattern = r'(?i)(?=.*invalid)(?=.*header)(?=.*name)'
+ with self.assertRaisesRegex(ValueError, pattern) as cm:
+ setter(message, name, value)
+ self.assertIn(f"{name!r}", str(cm.exception))
+
def test_get_body_malformed(self):
"""test for bpo-42892"""
msg = textwrap.dedent("""\
diff --git a/Misc/NEWS.d/next/Library/2024-12-11-17-44-36.gh-issue-127794.VwmRsp.rst b/Misc/NEWS.d/next/Library/2024-12-11-17-44-36.gh-issue-127794.VwmRsp.rst
new file mode 100644
index 0000000..b6e39d6
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-12-11-17-44-36.gh-issue-127794.VwmRsp.rst
@@ -0,0 +1,4 @@
+When headers are added to :class:`email.message.Message` objects, either through
+:meth:`email.message.Message.__setitem__` or :meth:`email.message.Message.add_header`,
+the field name is now validated according to :rfc:`RFC 5322, Section 2.2 <5322#section-2.2>`
+and a :exc:`ValueError` is raised if the field name contains any invalid characters.