From fa95d8ee982e2fb7460243be4a16e5db8f975402 Mon Sep 17 00:00:00 2001 From: Jiucheng Zang Date: Mon, 26 May 2025 00:53:05 -0400 Subject: [PATCH 1/7] Fix TypeError in email.utils.decode_params() when sorting RFC 2231 continuations on Python 3 and add corresponding test case --- Lib/email/utils.py | 5 +++-- Lib/test/test_email/test_email.py | 10 ++++++++++ .../2025-05-25-23-23-05.gh-issue-134151.13Wwsb.rst | 2 ++ 3 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2025-05-25-23-23-05.gh-issue-134151.13Wwsb.rst diff --git a/Lib/email/utils.py b/Lib/email/utils.py index 7eab74dc0db9df..e89d0cf3fce19b 100644 --- a/Lib/email/utils.py +++ b/Lib/email/utils.py @@ -417,8 +417,9 @@ def decode_params(params): for name, continuations in rfc2231_params.items(): value = [] extended = False - # Sort by number - continuations.sort() + # Sort by number, treating None as greater than any integer. + # In Python 3, None cannot be compared to int, so use a tuple key. + continuations.sort(key=lambda x: (x[0] is None, x[0])) # And now append all values in numerical order, converting # %-encodings for the encoded segments. If any of the # continuation names ends in a *, then the entire string, after diff --git a/Lib/test/test_email/test_email.py b/Lib/test/test_email/test_email.py index 7b14305f997e5d..e4c6c333e244b2 100644 --- a/Lib/test/test_email/test_email.py +++ b/Lib/test/test_email/test_email.py @@ -389,6 +389,16 @@ def test_bad_param(self): msg = email.message_from_string("Content-Type: blarg; baz; boo\n") self.assertEqual(msg.get_param('baz'), '') + def test_continuation_sorting_no_typeerror(self): + msg = email.message_from_string( + "Content-Disposition: attachment; " + "filename*=\"ignored\"; " + "filename*0*=\"utf-8''foo%20\"; " + "filename*1*=\"bar.txt\"\n" + ) + filename = msg.get_filename() + self.assertEqual(filename, 'foo bar.txtignored') + def test_missing_filename(self): msg = email.message_from_string("From: foo\n") self.assertEqual(msg.get_filename(), None) diff --git a/Misc/NEWS.d/next/Library/2025-05-25-23-23-05.gh-issue-134151.13Wwsb.rst b/Misc/NEWS.d/next/Library/2025-05-25-23-23-05.gh-issue-134151.13Wwsb.rst new file mode 100644 index 00000000000000..aa01089ef01050 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-05-25-23-23-05.gh-issue-134151.13Wwsb.rst @@ -0,0 +1,2 @@ +:mod:`email`: Fix TypeError in email.utils.decode_params() when sorting RFC +2231 continuations on Python 3. From 730c3da57569894b122d4c7b46403d7aea137773 Mon Sep 17 00:00:00 2001 From: Jiucheng Zang Date: Mon, 26 May 2025 16:30:45 -0400 Subject: [PATCH 2/7] Fix sorting logic in decode_params to handle None values correctly and update tests for continuation sorting --- Lib/email/utils.py | 10 +++++++--- Lib/test/test_email/test_email.py | 12 ++++++++++-- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/Lib/email/utils.py b/Lib/email/utils.py index e89d0cf3fce19b..b0f2d0ff141a06 100644 --- a/Lib/email/utils.py +++ b/Lib/email/utils.py @@ -417,9 +417,13 @@ def decode_params(params): for name, continuations in rfc2231_params.items(): value = [] extended = False - # Sort by number, treating None as greater than any integer. - # In Python 3, None cannot be compared to int, so use a tuple key. - continuations.sort(key=lambda x: (x[0] is None, x[0])) + # Sort by number, treating None as 0 if there is no 0, ignore it if there is already a 0. + has_zero = any(x[0] == 0 for x in continuations) + if has_zero: + continuations = [x for x in continuations if x[0] is not None] + else: + continuations = [(0 if x[0] is None else x[0], x[1], x[2]) for x in continuations] + continuations.sort(key=lambda x: x[0]) # And now append all values in numerical order, converting # %-encodings for the encoded segments. If any of the # continuation names ends in a *, then the entire string, after diff --git a/Lib/test/test_email/test_email.py b/Lib/test/test_email/test_email.py index e4c6c333e244b2..995d2b342ca795 100644 --- a/Lib/test/test_email/test_email.py +++ b/Lib/test/test_email/test_email.py @@ -389,7 +389,7 @@ def test_bad_param(self): msg = email.message_from_string("Content-Type: blarg; baz; boo\n") self.assertEqual(msg.get_param('baz'), '') - def test_continuation_sorting_no_typeerror(self): + def test_continuation_sorting_part_order(self): msg = email.message_from_string( "Content-Disposition: attachment; " "filename*=\"ignored\"; " @@ -397,7 +397,15 @@ def test_continuation_sorting_no_typeerror(self): "filename*1*=\"bar.txt\"\n" ) filename = msg.get_filename() - self.assertEqual(filename, 'foo bar.txtignored') + self.assertEqual(filename, 'foo bar.txt') + + def test_sorting_no_continuations(self): + msg = email.message_from_string( + "Content-Disposition: attachment; " + "filename*=\"bar.txt\"; " + ) + filename = msg.get_filename() + self.assertEqual(filename, 'bar.txt') def test_missing_filename(self): msg = email.message_from_string("From: foo\n") From 73c5ddb0289112adb1ad5c6eddd6232e7bb1de2e Mon Sep 17 00:00:00 2001 From: Jiucheng Zang Date: Mon, 26 May 2025 16:34:07 -0400 Subject: [PATCH 3/7] Fix lint --- Lib/test/test_email/test_email.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_email/test_email.py b/Lib/test/test_email/test_email.py index 995d2b342ca795..8765d121fd0813 100644 --- a/Lib/test/test_email/test_email.py +++ b/Lib/test/test_email/test_email.py @@ -398,7 +398,7 @@ def test_continuation_sorting_part_order(self): ) filename = msg.get_filename() self.assertEqual(filename, 'foo bar.txt') - + def test_sorting_no_continuations(self): msg = email.message_from_string( "Content-Disposition: attachment; " From 433dea6f6ea26064cf63fd3d697d313c6ada9d68 Mon Sep 17 00:00:00 2001 From: Jiucheng Zang Date: Sat, 7 Jun 2025 16:44:33 -0400 Subject: [PATCH 4/7] Update NEWS entry --- .../next/Library/2025-05-25-23-23-05.gh-issue-134151.13Wwsb.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2025-05-25-23-23-05.gh-issue-134151.13Wwsb.rst b/Misc/NEWS.d/next/Library/2025-05-25-23-23-05.gh-issue-134151.13Wwsb.rst index aa01089ef01050..7c5159c159dafa 100644 --- a/Misc/NEWS.d/next/Library/2025-05-25-23-23-05.gh-issue-134151.13Wwsb.rst +++ b/Misc/NEWS.d/next/Library/2025-05-25-23-23-05.gh-issue-134151.13Wwsb.rst @@ -1,2 +1,2 @@ :mod:`email`: Fix TypeError in email.utils.decode_params() when sorting RFC -2231 continuations on Python 3. +2231 continuations that contain an unnumbered section. \ No newline at end of file From 1a1235654c65fc96cb404c8361e1b31eda4e331b Mon Sep 17 00:00:00 2001 From: Jiucheng Zang Date: Sat, 7 Jun 2025 17:18:17 -0400 Subject: [PATCH 5/7] Fix lint --- .../next/Library/2025-05-25-23-23-05.gh-issue-134151.13Wwsb.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2025-05-25-23-23-05.gh-issue-134151.13Wwsb.rst b/Misc/NEWS.d/next/Library/2025-05-25-23-23-05.gh-issue-134151.13Wwsb.rst index 7c5159c159dafa..f6a5fa319353f8 100644 --- a/Misc/NEWS.d/next/Library/2025-05-25-23-23-05.gh-issue-134151.13Wwsb.rst +++ b/Misc/NEWS.d/next/Library/2025-05-25-23-23-05.gh-issue-134151.13Wwsb.rst @@ -1,2 +1,2 @@ :mod:`email`: Fix TypeError in email.utils.decode_params() when sorting RFC -2231 continuations that contain an unnumbered section. \ No newline at end of file +2231 continuations that contain an unnumbered section. From eb8c0a8238f82145ec9aba07fcd2c1ec0bcaeeca Mon Sep 17 00:00:00 2001 From: Jiucheng Zang Date: Sat, 7 Jun 2025 17:28:43 -0400 Subject: [PATCH 6/7] Addressed Comments --- Lib/email/utils.py | 2 +- .../Library/2025-05-25-23-23-05.gh-issue-134151.13Wwsb.rst | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/email/utils.py b/Lib/email/utils.py index b0f2d0ff141a06..1ae820cf23b60b 100644 --- a/Lib/email/utils.py +++ b/Lib/email/utils.py @@ -422,7 +422,7 @@ def decode_params(params): if has_zero: continuations = [x for x in continuations if x[0] is not None] else: - continuations = [(0 if x[0] is None else x[0], x[1], x[2]) for x in continuations] + continuations = [(x[0] or 0, x[1], x[2]) for x in continuations] continuations.sort(key=lambda x: x[0]) # And now append all values in numerical order, converting # %-encodings for the encoded segments. If any of the diff --git a/Misc/NEWS.d/next/Library/2025-05-25-23-23-05.gh-issue-134151.13Wwsb.rst b/Misc/NEWS.d/next/Library/2025-05-25-23-23-05.gh-issue-134151.13Wwsb.rst index f6a5fa319353f8..ecdde240b4aadc 100644 --- a/Misc/NEWS.d/next/Library/2025-05-25-23-23-05.gh-issue-134151.13Wwsb.rst +++ b/Misc/NEWS.d/next/Library/2025-05-25-23-23-05.gh-issue-134151.13Wwsb.rst @@ -1,2 +1,2 @@ -:mod:`email`: Fix TypeError in email.utils.decode_params() when sorting RFC -2231 continuations that contain an unnumbered section. +:mod:`email`: Fix :exc:`TypeError` in :func:`email.utils.decode_params` +when sorting :rfc:`2231` continuations that contain an unnumbered section. From d9b2e2edcd3fb44d93184bc6aa95e5471bd6b49b Mon Sep 17 00:00:00 2001 From: Jiucheng Zang Date: Sat, 7 Jun 2025 18:01:42 -0400 Subject: [PATCH 7/7] Format --- Lib/email/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Lib/email/utils.py b/Lib/email/utils.py index 1ae820cf23b60b..3de1f0d24a15b0 100644 --- a/Lib/email/utils.py +++ b/Lib/email/utils.py @@ -417,7 +417,8 @@ def decode_params(params): for name, continuations in rfc2231_params.items(): value = [] extended = False - # Sort by number, treating None as 0 if there is no 0, ignore it if there is already a 0. + # Sort by number, treating None as 0 if there is no 0, + # and ignore it if there is already a 0. has_zero = any(x[0] == 0 for x in continuations) if has_zero: continuations = [x for x in continuations if x[0] is not None]