diff --git a/Lib/email/_header_value_parser.py b/Lib/email/_header_value_parser.py index 7c03fb49b3b5e7..f7ed37022a54c0 100644 --- a/Lib/email/_header_value_parser.py +++ b/Lib/email/_header_value_parser.py @@ -2580,12 +2580,15 @@ def get_parameter(value): return param, value else: if token is not None: - for t in token: + # The charset is parsed as a Value child, but belongs to the + # parameter itself. Retype its extended-attrtext as plain attrtext. + charset = token[0] + for t in charset: if t.token_type == 'extended-attrtext': + t.token_type = 'attrtext' break - t.token_type == 'attrtext' - appendto.append(t) - param.charset = t.value + appendto.append(charset) + param.charset = charset.stripped_value if value[0] != "'": raise errors.HeaderParseError("Expected RFC2231 char/lang encoding " "delimiter, but found {!r}".format(value)) diff --git a/Lib/test/test_email/test__header_value_parser.py b/Lib/test/test_email/test__header_value_parser.py index 9d9fe418ee4d06..d4ccd551299b4b 100644 --- a/Lib/test/test_email/test__header_value_parser.py +++ b/Lib/test/test_email/test__header_value_parser.py @@ -3030,6 +3030,37 @@ def test_parse_message_ids_broken_ang(self): self.assertEqual(message_ids.token_type, 'message-id-list') +class Test_get_parameter(TestParserMixin, TestEmailBase): + + def test_rfc2231_charset_is_retyped_as_attrtext(self): + # gh-150474: the charset of an initial extended parameter is moved + # out of the value and into the parameter itself, and while doing so + # its text is retyped as an attrtext token. + param = self._test_get_x(parser.get_parameter, + "title*=us-ascii'en'This%20is%20a%20test", + "title*=us-ascii'en'This%20is%20a%20test", + "title*=us-ascii'en'This%20is%20a%20test", + [], + '') + self.assertEqual(param.token_type, 'parameter') + self.assertEqual(param[3].token_type, 'attribute') + self.assertEqual(param[3][0].token_type, 'attrtext') + self.assertEqual(param.charset, 'us-ascii') + + def test_rfc2231_charset_strips_cfws(self): + # gh-150474: CFWS around the charset is part of the parse tree, but + # must not end up in the charset itself. + param = self._test_get_x(parser.get_parameter, + "title*=us-ascii 'en'This%20is%20a%20test", + "title*=us-ascii 'en'This%20is%20a%20test", + "title*=us-ascii 'en'This%20is%20a%20test", + [], + '') + self.assertEqual(param[3].token_type, 'attribute') + self.assertEqual(param[3][0].token_type, 'attrtext') + self.assertEqual(param[3][1].token_type, 'cfws') + self.assertEqual(param.charset, 'us-ascii') + @parameterize class Test_parse_mime_parameters(TestParserMixin, TestEmailBase): diff --git a/Misc/NEWS.d/next/Library/2026-09-18-23-41-48.gh-issue-150474.uJ3cL-.rst b/Misc/NEWS.d/next/Library/2026-09-18-23-41-48.gh-issue-150474.uJ3cL-.rst new file mode 100644 index 00000000000000..449933e43a67b8 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-09-18-23-41-48.gh-issue-150474.uJ3cL-.rst @@ -0,0 +1,4 @@ +Fix the parsing of the character set of :rfc:`2231` encoded parameters in the +:mod:`email` header parser. The character set is now correctly located in +the parsed value, so whitespace or a comment following it is no longer +included in the parameter's character set name.