From cfa70b1be646353a51e623d8fce9ec54544cd1ab Mon Sep 17 00:00:00 2001 From: "R. David Murray" Date: Sun, 25 May 2025 18:09:32 -0400 Subject: [PATCH] gh-134152: Fix UnboundLocalError in email._header_value_parser _get_ptext_to_endchars (GH-134233) Fix an UnboundLocalError that can occur when parsing certain delimited constructs in headers (domain literals, quoted strings, comments). After the fix the _get_ptext_to_endchars returns an empty string if there is no content after the opening delimiter. The calling code is responsible for handling the lack of the trailing delimiter, which it already does; this edge case was the header ending immediately after the opening delimiter. (cherry picked from commit a32ea456992fedfc9ce61561c88056de3c18cffd) Co-authored-by: R. David Murray --- Lib/email/_header_value_parser.py | 2 ++ .../test_email/test__header_value_parser.py | 33 +++++++++++++++++++ ...-05-19-10-32-11.gh-issue-134152.INJC2j.rst | 2 ++ 3 files changed, 37 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2025-05-19-10-32-11.gh-issue-134152.INJC2j.rst diff --git a/Lib/email/_header_value_parser.py b/Lib/email/_header_value_parser.py index 9a51b9437333db..f11fa83d45ed2d 100644 --- a/Lib/email/_header_value_parser.py +++ b/Lib/email/_header_value_parser.py @@ -1020,6 +1020,8 @@ def _get_ptext_to_endchars(value, endchars): a flag that is True iff there were any quoted printables decoded. """ + if not value: + return '', '', False fragment, *remainder = _wsp_splitter(value, 1) vchars = [] escape = False diff --git a/Lib/test/test_email/test__header_value_parser.py b/Lib/test/test_email/test__header_value_parser.py index ac12c3b2306f7d..fd4ac2c404ce47 100644 --- a/Lib/test/test_email/test__header_value_parser.py +++ b/Lib/test/test_email/test__header_value_parser.py @@ -463,6 +463,19 @@ def test_get_qp_ctext_non_printables(self): [errors.NonPrintableDefect], ')') self.assertEqual(ptext.defects[0].non_printables[0], '\x00') + def test_get_qp_ctext_close_paren_only(self): + self._test_get_x(parser.get_qp_ctext, + ')', '', ' ', [], ')') + + def test_get_qp_ctext_open_paren_only(self): + self._test_get_x(parser.get_qp_ctext, + '(', '', ' ', [], '(') + + def test_get_qp_ctext_no_end_char(self): + self._test_get_x(parser.get_qp_ctext, + '', '', ' ', [], '') + + # get_qcontent def test_get_qcontent_only(self): @@ -503,6 +516,14 @@ def test_get_qcontent_non_printables(self): [errors.NonPrintableDefect], '"') self.assertEqual(ptext.defects[0].non_printables[0], '\x00') + def test_get_qcontent_empty(self): + self._test_get_x(parser.get_qcontent, + '"', '', '', [], '"') + + def test_get_qcontent_no_end_char(self): + self._test_get_x(parser.get_qcontent, + '', '', '', [], '') + # get_atext def test_get_atext_only(self): @@ -1283,6 +1304,18 @@ def test_get_dtext_open_bracket_mid_word(self): self._test_get_x(parser.get_dtext, 'foo[bar', 'foo', 'foo', [], '[bar') + def test_get_dtext_open_bracket_only(self): + self._test_get_x(parser.get_dtext, + '[', '', '', [], '[') + + def test_get_dtext_close_bracket_only(self): + self._test_get_x(parser.get_dtext, + ']', '', '', [], ']') + + def test_get_dtext_empty(self): + self._test_get_x(parser.get_dtext, + '', '', '', [], '') + # get_domain_literal def test_get_domain_literal_only(self): diff --git a/Misc/NEWS.d/next/Library/2025-05-19-10-32-11.gh-issue-134152.INJC2j.rst b/Misc/NEWS.d/next/Library/2025-05-19-10-32-11.gh-issue-134152.INJC2j.rst new file mode 100644 index 00000000000000..6da3d4147dd960 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-05-19-10-32-11.gh-issue-134152.INJC2j.rst @@ -0,0 +1,2 @@ +Fixed :exc:`UnboundLocalError` that could occur during :mod:`email` header +parsing if an expected trailing delimiter is missing in some contexts.