diff --git a/Lib/email/policy.py b/Lib/email/policy.py index 46b7de5bb6d8ae..fc9c6c07985fdb 100644 --- a/Lib/email/policy.py +++ b/Lib/email/policy.py @@ -9,6 +9,7 @@ from email.headerregistry import HeaderRegistry as HeaderRegistry from email.contentmanager import raw_data_manager from email.message import EmailMessage +from email.errors import NonPrintableDefect __all__ = [ 'Compat32', @@ -139,13 +140,22 @@ def header_store_parse(self, name, value): """ if hasattr(value, 'name') and value.name.lower() == name.lower(): - return (name, value) - 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. + header_value = value + else: + header_value = self.header_factory(name, value) + + np = [ + non_printable + for defect in header_value.defects + if isinstance(defect, NonPrintableDefect) + for non_printable in defect.non_printables + ] + + if linesep_splitter.search(str(header_value)) or '\n' in np or '\r' in np: raise ValueError("Header values may not contain linefeed " "or carriage return characters") - return (name, self.header_factory(name, value)) + + return (name, header_value) def header_fetch_parse(self, name, value): """+ diff --git a/Lib/test/test_email/test_policy.py b/Lib/test/test_email/test_policy.py index c6b9c80efe1b54..bf4e71dc717692 100644 --- a/Lib/test/test_email/test_policy.py +++ b/Lib/test/test_email/test_policy.py @@ -398,6 +398,60 @@ def test_header_store_parse_rejects_newlines(self): instance.header_store_parse, 'From', 'spam\negg@foo.py') + self.assertRaises(ValueError, + instance.header_store_parse, + 'Subject', 'te\nst') + + self.assertRaises(ValueError, + instance.header_store_parse, + 'Subject', + 'A 💩 subject=?UTF-8?Q?=0A?=Bcc: injected@example.com') + + self.assertRaises(ValueError, + instance.header_store_parse, + 'Subject', + "Here's an =?UTF-8?Q?embedded_newline=0A?=") + + factory = headerregistry.HeaderRegistry() + h = factory('subject', 'te\nst') + self.assertRaises(ValueError, + instance.header_store_parse, + 'Subject', h) + + h = factory('subject', + "Here's an =?UTF-8?Q?embedded_newline=0A?=") + self.assertRaises(ValueError, + instance.header_store_parse, + 'Subject', h) + + self.assertRaises(ValueError, + instance.header_store_parse, + 'From', + "External Sender =?UTF-8?Q?embedded_newline=0A?=Smuggled-Data: Bad") + + self.assertRaises(ValueError, + instance.header_store_parse, + 'Content-Type', + "text/html; charset=UTF-8=?UTF-8?Q?embedded_newline=0A?=Smuggled-Data: Bad") + + self.assertRaises(ValueError, + instance.header_store_parse, + 'Content-Transfer-Encoding', + "External Sender =?UTF-8?Q?embedded_newline=0A?=Smuggled-Data: Bad") + + self.assertRaises(ValueError, + instance.header_store_parse, + 'Message-Id', + "1212334141-321313=?UTF-8?Q?embedded_newline=0A?=Smuggled-Data: Bad") + + self.assertRaises(ValueError, + instance.header_store_parse, + 'content-disposition', + "inline=?UTF-8?Q?embedded_newline=0A?=Smuggled-Data: Bad") + + # date and mime headers don't have embedded newlines, nor errors + + if __name__ == '__main__': unittest.main() diff --git a/Misc/NEWS.d/next/Library/2024-07-15-21-43-38.gh-issue-121650.A0FkCh.rst b/Misc/NEWS.d/next/Library/2024-07-15-21-43-38.gh-issue-121650.A0FkCh.rst new file mode 100644 index 00000000000000..4a55b181b311ff --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-07-15-21-43-38.gh-issue-121650.A0FkCh.rst @@ -0,0 +1,3 @@ +Fix :mod:`email.policy` to properly disallow newlines and carriage returns +in email headers, both at the end of the header, and encoded entities that are +resolved to newlines and carriage returns.