diff --git a/Lib/email/_header_value_parser.py b/Lib/email/_header_value_parser.py index 792072ab9f6128..92bc8f0f797851 100644 --- a/Lib/email/_header_value_parser.py +++ b/Lib/email/_header_value_parser.py @@ -580,6 +580,8 @@ def display_name(self): return res.value if res[0].token_type == 'cfws': res.pop(0) + if len(res) == 0: + return res.value else: if (isinstance(res[0], TokenList) and res[0][0].token_type == 'cfws'): @@ -730,6 +732,8 @@ class Value(TokenList): @property def stripped_value(self): + if not self: + return self.value token = self[0] if token.token_type == 'cfws': token = self[1] @@ -2511,7 +2515,7 @@ def get_parameter(value): param.append(ValueTerminal('*', 'extended-parameter-marker')) value = value[1:] param.extended = True - if value[0] != '=': + if not value or value[0] != '=': raise errors.HeaderParseError("Parameter not followed by '='") param.append(ValueTerminal('=', 'parameter-separator')) value = value[1:] diff --git a/Lib/test/test_email/test__header_value_parser.py b/Lib/test/test_email/test__header_value_parser.py index 9d9fe418ee4d06..5b66441502b768 100644 --- a/Lib/test/test_email/test__header_value_parser.py +++ b/Lib/test/test_email/test__header_value_parser.py @@ -1862,6 +1862,15 @@ def test_get_display_name_for_invalid_address_field(self): ':Foo ', '', '', [errors.InvalidHeaderDefect], ':Foo ') self.assertEqual(display_name.value, '') + def test_get_display_name_comment_only(self): + # gh-151857: a comment-only (CFWS) display name raised IndexError. + display_name = self._test_get_x( + parser.get_display_name, + '(c)', '(c)', ' "" ', + [errors.InvalidHeaderDefect, errors.ObsoleteHeaderDefect], '') + self.assertEqual(display_name.display_name, '') + self.assertEqual(display_name.value, ' "" ') + # get_name_addr def test_get_name_addr_angle_addr_only(self): @@ -3144,6 +3153,45 @@ def mime_parameters_as_value(self, 'r*=\'a\'"', [('r', '"')], [errors.InvalidHeaderDefect]*2), + + # gh-151857: a parameter name ending in the extended marker '*' with + # no value used to raise an uncaught IndexError instead of a defect. + 'extended_marker_no_value': ( + 'name*', + '', + 'name*', + [], + [errors.InvalidHeaderDefect]), + + 'extended_marker_no_value_sectioned': ( + 'name*0*', + '', + 'name*0*', + [], + [errors.InvalidHeaderDefect]), + + 'extended_marker_no_value_after_param': ( + 'x=1; name*', + ' x="1"', + 'x=1; name*', + [('x', '1')], + [errors.InvalidHeaderDefect]), + + # gh-151857: a quoted extended value holding only the RFC 2231 + # delimiters used to raise an uncaught IndexError. + 'extended_quoted_delimiters_only': ( + 'name*="\'"', + ' name', + "name*=''", + [('name', '')], + [errors.InvalidHeaderDefect]), + + 'extended_quoted_charset_delimiters_only': ( + 'name*="us-ascii\'\'"', + ' name', + "name*=us-ascii''", + [('name', '')], + [errors.InvalidHeaderDefect]), } @parameterize diff --git a/Lib/test/test_email/test_headerregistry.py b/Lib/test/test_email/test_headerregistry.py index aa918255d15c37..14c66dfad44453 100644 --- a/Lib/test/test_email/test_headerregistry.py +++ b/Lib/test/test_email/test_headerregistry.py @@ -598,6 +598,17 @@ def content_type_as_value(self, 'text/plain; name="This is even more ***fun*** is it not.pdf"', ), + # gh-151857: used to raise IndexError. + 'rfc2231_double_quoted_delimiter_only': ( + 'text/plain; name*="\'"', + 'text/plain', + 'text', + 'plain', + {'name': ''}, + [errors.InvalidHeaderDefect], + 'text/plain; name', + ), + 'rfc2231_single_quote_inside_double_quotes': ( ('text/plain; charset=us-ascii;' '\ttitle*0*="us-ascii\'en\'This%20is%20really%20";' @@ -1426,6 +1437,15 @@ def test_groups_types(self): self.assertIsInstance(h.groups, tuple) self.assertIsInstance(h.groups[0], Group) + def test_comment_only_group_display_name(self): + # gh-151857: a comment-only group display name raised IndexError. + h = self.make_header('to', '(c):') + self.assertEqual(h.groups[0].display_name, '') + self.assertEqual(h.addresses, ()) + h = self.make_header('cc', '(x): a@b.com;') + self.assertEqual(h.groups[0].display_name, '') + self.assertEqual(h.addresses[0].addr_spec, 'a@b.com') + def test_set_from_Address(self): h = self.make_header('to', Address('me', 'foo', 'example.com')) self.assertEqual(h, 'me ') diff --git a/Misc/NEWS.d/next/Library/2026-06-22-10-00-00.gh-issue-151857.3j9GAn.rst b/Misc/NEWS.d/next/Library/2026-06-22-10-00-00.gh-issue-151857.3j9GAn.rst new file mode 100644 index 00000000000000..9bfe30f9ec8e06 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-06-22-10-00-00.gh-issue-151857.3j9GAn.rst @@ -0,0 +1,5 @@ +Fixed three cases where the :mod:`email` header parser (under +:class:`~email.policy.EmailPolicy`) raised an uncaught :exc:`IndexError` on +malformed input: a MIME parameter name ending with ``*``, an empty quoted +RFC 2231 extended value, and an address header whose display name is only a +comment.