From 7ca17581b03ce40c1ecc85e73191f22b8cd541ef Mon Sep 17 00:00:00 2001 From: R David Murray Date: Mon, 13 Apr 2026 15:33:33 -0400 Subject: [PATCH 1/5] Add failing test. --- Lib/test/test_email/test_headerregistry.py | 41 ++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_email/test_headerregistry.py b/Lib/test/test_email/test_headerregistry.py index c9c63951597244..2aaa7d68ca3fe1 100644 --- a/Lib/test/test_email/test_headerregistry.py +++ b/Lib/test/test_email/test_headerregistry.py @@ -1271,12 +1271,12 @@ class TestAddressHeader(TestHeaderBase): 'example.com', None), - } - # XXX: Need many more examples, and in particular some with names in # trailing comments, which aren't currently handled. comments in # general are not handled yet. + } + def example_as_address(self, source, defects, decoded, display_name, addr_spec, username, domain, comment): h = self.make_header('sender', source) @@ -1294,6 +1294,43 @@ def example_as_address(self, source, defects, decoded, display_name, # XXX: we have no comment support yet. #self.assertEqual(a.comment, comment) + example_broken_header_params = { + + 'just_dquote': + ('"', + [errors.InvalidHeaderDefect]*2, + '<>', + '', + '<>', + '', + '', + ), + + } + + def example_broken_header_as_address( + self, + source, + defects, + decoded, + display_name, + addr_spec, + username, + domain, + ): + h = self.make_header('sender', source) + self.assertEqual(h, decoded) + self.assertDefectsEqual(h.defects, defects) + a = h.address + self.assertEqual(str(a), decoded) + self.assertEqual(len(h.groups), 1) + self.assertEqual([a], list(h.groups[0].addresses)) + self.assertEqual([a], list(h.addresses)) + self.assertEqual(a.display_name, display_name) + self.assertEqual(a.addr_spec, addr_spec) + self.assertEqual(a.username, username) + self.assertEqual(a.domain, domain) + def example_as_group(self, source, defects, decoded, display_name, addr_spec, username, domain, comment): source = 'foo: {};'.format(source) From 1b0fbe2c53a7405669152de5b7984afa39a5a847 Mon Sep 17 00:00:00 2001 From: R David Murray Date: Mon, 13 Apr 2026 15:35:55 -0400 Subject: [PATCH 2/5] Correct imperfect fix for variation of this bug. As part of fixing bpo-27931 code was introduced to get_bar_quoted_string that added an empty Terminal if the quoted string was empty. This isn't the best answer in terms of the parse tree, we really want the token list to be empty in that case. But having it be empty would result in local_part raising the index error. Which is the same bug we find if we try to parse an address consisting of a single dquote. So by fixing local_part to not raise in that case, we can have the bare_quoted_string code correctly return an empty token list for the empty string cases (two dquotes or a single dquote as the entire addrespec, at the end of a line). After this commit there will be two test failures instead of just one, and we'll fix both with the fix to local_part. --- Lib/email/_header_value_parser.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Lib/email/_header_value_parser.py b/Lib/email/_header_value_parser.py index 4c5394ab6353ac..eabb39156396d1 100644 --- a/Lib/email/_header_value_parser.py +++ b/Lib/email/_header_value_parser.py @@ -1249,8 +1249,7 @@ def get_bare_quoted_string(value): bare_quoted_string = BareQuotedString() value = value[1:] if value and value[0] == '"': - token, value = get_qcontent(value) - bare_quoted_string.append(token) + return bare_quoted_string, value[1:] while value and value[0] != '"': if value[0] in WSP: token, value = get_fws(value) From 619b19fc22a53862470a9c6e63225ea31a52eae2 Mon Sep 17 00:00:00 2001 From: R David Murray Date: Mon, 13 Apr 2026 15:53:15 -0400 Subject: [PATCH 3/5] Fix the bug. --- Lib/email/_header_value_parser.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/email/_header_value_parser.py b/Lib/email/_header_value_parser.py index eabb39156396d1..a848b79b3cf2c2 100644 --- a/Lib/email/_header_value_parser.py +++ b/Lib/email/_header_value_parser.py @@ -639,11 +639,11 @@ def local_part(self): for tok in self[0] + [DOT]: if tok.token_type == 'cfws': continue - if (last_is_tl and tok.token_type == 'dot' and + if (last_is_tl and tok.token_type == 'dot' and last and last[-1].token_type == 'cfws'): res[-1] = TokenList(last[:-1]) is_tl = isinstance(tok, TokenList) - if (is_tl and last.token_type == 'dot' and + if (is_tl and last.token_type == 'dot' and tok and tok[0].token_type == 'cfws'): res.append(TokenList(tok[1:])) else: From a13dde6fe925233d24a06f1120d78566edf8f289 Mon Sep 17 00:00:00 2001 From: R David Murray Date: Mon, 13 Apr 2026 16:00:10 -0400 Subject: [PATCH 4/5] Add news item. I'm not sure this is worth a news item, though. --- .../Library/2026-04-13-15-59-44.gh-issue-148518.RQdvsu.rst | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2026-04-13-15-59-44.gh-issue-148518.RQdvsu.rst diff --git a/Misc/NEWS.d/next/Library/2026-04-13-15-59-44.gh-issue-148518.RQdvsu.rst b/Misc/NEWS.d/next/Library/2026-04-13-15-59-44.gh-issue-148518.RQdvsu.rst new file mode 100644 index 00000000000000..cdf885d3fb29b5 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-04-13-15-59-44.gh-issue-148518.RQdvsu.rst @@ -0,0 +1,4 @@ +If an email containing an address header that ended in an open double quote +was parsed with a non-`compat32` policy, accessing the `username` attribute +of the mailbox accessed through that header object would result in an +`IndexError`. It now correctly returns an empty string as the result. From f2bb125b6d02adba02522bbbd192d0c2e4277a7d Mon Sep 17 00:00:00 2001 From: R David Murray Date: Mon, 13 Apr 2026 18:28:46 -0400 Subject: [PATCH 5/5] Fix Sphinx syntax in news item. --- .../Library/2026-04-13-15-59-44.gh-issue-148518.RQdvsu.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Misc/NEWS.d/next/Library/2026-04-13-15-59-44.gh-issue-148518.RQdvsu.rst b/Misc/NEWS.d/next/Library/2026-04-13-15-59-44.gh-issue-148518.RQdvsu.rst index cdf885d3fb29b5..994e4ad7446670 100644 --- a/Misc/NEWS.d/next/Library/2026-04-13-15-59-44.gh-issue-148518.RQdvsu.rst +++ b/Misc/NEWS.d/next/Library/2026-04-13-15-59-44.gh-issue-148518.RQdvsu.rst @@ -1,4 +1,4 @@ If an email containing an address header that ended in an open double quote -was parsed with a non-`compat32` policy, accessing the `username` attribute +was parsed with a non-``compat32`` policy, accessing the ``username`` attribute of the mailbox accessed through that header object would result in an -`IndexError`. It now correctly returns an empty string as the result. +``IndexError``. It now correctly returns an empty string as the result.