Skip to content

gh-145824: email.utils: replace AttributeError with TypeError in collapse_rfc2231_value for non-string non-3-tuple input - #145825

Open
stefanzetzsche wants to merge 8 commits into
python:mainfrom
stefanzetzsche:fix/email_utils_collapse_non3_tuple
Open

stefanzetzsche wants to merge 8 commits into
python:mainfrom
stefanzetzsche:fix/email_utils_collapse_non3_tuple

Conversation

@stefanzetzsche

@stefanzetzsche stefanzetzsche commented Mar 11, 2026 •

Copy link
Copy Markdown
Contributor

Issue

collapse_rfc2231_value falls through to unquote(value) for tuples whose length is not 3, but unquote expects a string, raising AttributeError.

Reproducer

from email.utils import collapse_rfc2231_value
collapse_rfc2231_value(('a', 'b'))  # AttributeError: 'tuple' object has no attribute 'startswith'

Fix

  1. In Lib/email/utils.py, raise TypeError for values that are neither a string nor a 3-tuple:
if not isinstance(value, tuple) or len(value) != 3:
    if not isinstance(value, str):
        raise TypeError(
            f"expected str or 3-tuple, got {type(value).__name__}"
        )
    return unquote(value)
  1. In Doc/library/email.utils.rst, change "not a tuple" to "not a 3-tuple" to match the code's actual check.

Tests

test_collapse_rfc2231_value_non_3_tuple in TestRFC2231 (Lib/test/test_email/test_email.py) verifies that:

  • Non-string, non-3-tuple values (empty tuple, 1-tuple, 2-tuple, 4-tuple, int, None) raise TypeError
  • A 3-tuple ('us-ascii', 'en', 'hello') decodes correctly
  • A plain quoted string '"hello"' passes through unquote

stefanzetzsche and others added 3 commits February 22, 2026 14:54
Passing a tuple of length != 3 fell through to unquote(value) which
calls .startswith() on the tuple, raising AttributeError. Handle
non-3-tuples explicitly before calling unquote.
@stefanzetzsche
stefanzetzsche marked this pull request as ready for review March 11, 2026 14:42
@stefanzetzsche
stefanzetzsche requested a review from a team as a code owner March 11, 2026 14:42

@picnixz picnixz left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should do anything here. First, it's not a crash but rather a GIGO case (garbage in, garbage out). The documentation says:

For convenience, if the value passed to collapse_rfc2231_value() is not a tuple, it should be a string and it is returned unquoted.

So returning the stringification of the tuple may not necessarily be correct. Instead, a TypeError should be raised if AttributeError is too cryptic IMO.

@bedevere-app

bedevere-app Bot commented Mar 11, 2026

Copy link
Copy Markdown

A Python core developer has requested some changes be made to your pull request before we can consider merging it. If you could please address their requests along with any other requests in other reviews from core developers that would be appreciated.

Once you have made the requested changes, please leave a comment on this pull request containing the phrase I have made the requested changes; please review again. I will then notify any core developers who have left a review that you're ready for them to take another look at this pull request.

Revised per reviewer feedback: instead of silently handling non-3-tuples,
raise TypeError with a clear message. Updated docs to say 'not a 3-tuple'
instead of 'not a tuple' to match the code's actual check.
@stefanzetzsche stefanzetzsche changed the title gh-145824: email.utils: collapse_rfc2231_value crashes on non-3-tuples gh-145824: email.utils: replace AttributeError with TypeError in collapse_rfc2231_value for non-string non-3-tuple input Mar 11, 2026
@stefanzetzsche

stefanzetzsche commented Mar 11, 2026 •

Copy link
Copy Markdown
Contributor Author

Thanks @picnixz, I have made the requested changes; please review again. See also #145824 (comment).

@stefanzetzsche
stefanzetzsche requested a review from picnixz March 11, 2026 17:55
Comment thread Lib/test/test_email/test_email.py Outdated
Comment thread Lib/test/test_email/test_email.py Outdated
Comment thread Lib/test/test_email/test_email.py Outdated
Comment thread Lib/test/test_email/test_email.py Outdated
@stefanzetzsche
stefanzetzsche requested a review from picnixz March 12, 2026 14:11
@github-actions

github-actions Bot commented May 8, 2026

Copy link
Copy Markdown

This PR is stale because it has been open for 30 days with no activity.

@github-actions github-actions Bot added the stale Stale PR or inactive for long period of time. label May 8, 2026
Comment on lines +5792 to +5796
def test_collapse_rfc2231_value_non_3_tuple(self):
for val in [(), ('a',), ('a', 'b'), ('a', 'b', 'c', 'd'), 42, None]:
with self.subTest(val=val):
with self.assertRaises(TypeError):
email.utils.collapse_rfc2231_value(val)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def test_collapse_rfc2231_value_non_3_tuple(self):
for val in [(), ('a',), ('a', 'b'), ('a', 'b', 'c', 'd'), 42, None]:
with self.subTest(val=val):
with self.assertRaises(TypeError):
email.utils.collapse_rfc2231_value(val)
@support.subTests("value", [(), ('a',), ('a', 'b'), ('a', 'b', 'c', 'd'), 42, None])
def test_collapse_rfc2231_value_non_3_tuple(self, value):
self.assertRaises(TypeError, email.utils.collapse_rfc2231_value, value)

and add from test import support at the top of the file if necessary. If subTests is already imported, use it as is.

@picnixz picnixz left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall LGTM except for the small test change.

@github-actions github-actions Bot removed the stale Stale PR or inactive for long period of time. label May 26, 2026
@github-actions

Copy link
Copy Markdown

This PR is stale because it has been open for 90 days with no activity.

@github-actions github-actions Bot added the stale Stale PR or inactive for long period of time. label Aug 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

awaiting changes stale Stale PR or inactive for long period of time.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants