diff --git a/Lib/email/_header_value_parser.py b/Lib/email/_header_value_parser.py index 922daa2560f006..0fb97a6cb2107f 100644 --- a/Lib/email/_header_value_parser.py +++ b/Lib/email/_header_value_parser.py @@ -2661,6 +2661,24 @@ def _refold_parse_tree(parse_tree, *, policy): if newline or part.startswith_fws(): lines.append(newline + tstr) continue + # Do not strip the quotes from BareQuotedString's by iterating over the + # children. Doing so might break a structured header and this function + # is context unaware. Instead prepare quoted children. + if isinstance(part, BareQuotedString): + subparts = list(part) + quoted_subparts = [] + dquote = ValueTerminal('"', 'ptext') + quoted_subparts.append(dquote) + for subpart in subparts: + quoted_without_quotes = quote_string(subpart)[1:-1] + quoted_terminal = ValueTerminal(quoted_without_quotes, 'ptext') + quoted_subparts.append(quoted_terminal) + quoted_subparts.append(dquote) + if not part.as_ew_allowed: + wrap_as_ew_blocked += 1 + quoted_subparts.append(end_ew_not_allowed) + parts = quoted_subparts + parts + continue if not hasattr(part, 'encode'): # It's not a terminal, try folding the subparts. newparts = list(part) diff --git a/Lib/test/test_email/test__header_value_parser.py b/Lib/test/test_email/test__header_value_parser.py index 676732bb3d0261..6975c900ad7d8b 100644 --- a/Lib/test/test_email/test__header_value_parser.py +++ b/Lib/test/test_email/test__header_value_parser.py @@ -2770,5 +2770,25 @@ def test_long_filename_attachment(self): " filename*1*=_TEST_TES.txt\n", ) + def test_long_display_name(self): + display_name = ( + # An address as first part of display name is a security issue. + 'spoofed@example.com' + + ' ' + # This triggers the BareQuotedString folding recursion. + + 'a'*self.policy.max_line_length + # These two quoted-pairs become the unescaped " \ outside. + + ' ' + + '\\" \\\\' + ) + addr_spec = 'actual.sender@example.com' + address = '"' + display_name + '" <' + addr_spec + '>\n' + self._test(parser.get_address(address)[0], + '"spoofed@example.com\n' + ' ' + 'a'*self.policy.max_line_length + '\n' + ' \\" \\\\" \n', + ) + + if __name__ == '__main__': unittest.main()