diff --git a/Lib/email/_policybase.py b/Lib/email/_policybase.py index 4b63b97217a835..9558691402d458 100644 --- a/Lib/email/_policybase.py +++ b/Lib/email/_policybase.py @@ -90,6 +90,14 @@ def __add__(self, other): """ return self.clone(**other.__dict__) +def validate_header(name): + # Validate header name according to RFC 5322 + import re + if not re.match(r'^[^\s:]+$', name): + raise ValueError(f"Invalid header field name {name!r}") + # Only allow printable ASCII characters + if any(ord(c) < 33 or ord(c) > 126 for c in name): + raise ValueError(f"Invalid header field name {name!r}") def _append_doc(doc, added_doc): doc = doc.rsplit('\n', 1)[0] @@ -314,6 +322,7 @@ def header_store_parse(self, name, value): """+ The name and value are returned unmodified. """ + validate_header(name) return (name, value) def header_fetch_parse(self, name, value): diff --git a/Lib/email/policy.py b/Lib/email/policy.py index 6e109b65011a44..48e6b2340e2c61 100644 --- a/Lib/email/policy.py +++ b/Lib/email/policy.py @@ -4,7 +4,7 @@ import re import sys -from email._policybase import Policy, Compat32, compat32, _extend_docstrings +from email._policybase import Policy, Compat32, compat32, _extend_docstrings, validate_header from email.utils import _has_surrogates from email.headerregistry import HeaderRegistry as HeaderRegistry from email.contentmanager import raw_data_manager @@ -140,6 +140,7 @@ def header_store_parse(self, name, value): """ if hasattr(value, 'name') and value.name.lower() == name.lower(): return (name, value) + validate_header(name) if isinstance(value, str) and len(value.splitlines())>1: # XXX this error message isn't quite right when we use splitlines # (see issue 22233), but I'm not sure what should happen here. diff --git a/Lib/test/test_email/test_email.py b/Lib/test/test_email/test_email.py index abe9ef2e94409f..ade9d3a2ac28f5 100644 --- a/Lib/test/test_email/test_email.py +++ b/Lib/test/test_email/test_email.py @@ -728,6 +728,29 @@ def test_nonascii_add_header_with_tspecial(self): "attachment; filename*=utf-8''Fu%C3%9Fballer%20%5Bfilename%5D.ppt", msg['Content-Disposition']) + def test_invalid_headers(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'), + ] + for name, value in invalid_headers: + with self.assertRaises(ValueError) as cm: + Message().add_header(name, value) + self.assertIn(f"Invalid header field name {name!r}", str(cm.exception)) + + invalid_headers = [ + ('Header\x7F', 'Non-ASCII character'), + ('Header\x1F', 'control character'), + ] + for name, value in invalid_headers: + with self.assertRaises(ValueError) as cm: + Message().add_header(name, value) + self.assertIn(f"Invalid header field name {name!r}", str(cm.exception)) + def test_binary_quopri_payload(self): for charset in ('latin-1', 'ascii'): msg = Message() 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 00000000000000..c5ed88bd2bf61b --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-12-11-17-44-36.gh-issue-127794.VwmRsp.rst @@ -0,0 +1,2 @@ +The :meth:`email.message.Message.add_header` method now validates header +field names according to :rfc:`5322`.