Skip to content

fix: feature-detect getaddresses(strict=) so address parsing works on Python < 3.13#161

Open
apoorvdarshan wants to merge 1 commit into
SpamScope:developfrom
apoorvdarshan:fix/issue-808-getaddresses-strict
Open

fix: feature-detect getaddresses(strict=) so address parsing works on Python < 3.13#161
apoorvdarshan wants to merge 1 commit into
SpamScope:developfrom
apoorvdarshan:fix/issue-808-getaddresses-strict

Conversation

@apoorvdarshan

Copy link
Copy Markdown

Summary

mailparser.utils.get_addresses unconditionally calls
email.utils.getaddresses([raw], strict=True). The strict keyword only
exists on Python 3.13+ (and later 3.9-3.12 security patch releases). Since
mail-parser targets requires-python >=3.9,<3.15, on an earlier patch release
this raises a TypeError and breaks address parsing for every message.

Reported downstream in domainaware/parsedmarc#808 ("getmessages fails with
invalid parameter 'strict'"), where valid messages were consequently moved to
an archived/invalid folder instead of being processed.

Root cause

src/mailparser/utils.py (previously line 137):

parsed = email.utils.getaddresses([raw_header], strict=True)

strict was added to email.utils.getaddresses in CPython 3.13 and backported
only to later 3.9-3.12 security patch releases (e.g. 3.11.10). On an earlier
interpreter such as CPython 3.11.3 the parameter does not exist, so passing it
raises:

TypeError: getaddresses() got an unexpected keyword argument 'strict'

Reproduction

On an interpreter whose email.utils.getaddresses predates strict (simulated
here by patching in the pre-3.13 signature):

import email.utils
from unittest.mock import patch
from mailparser.utils import get_addresses

_orig = email.utils.getaddresses
def legacy_getaddresses(fieldvalues):   # pre-3.13 signature: no `strict`
    return _orig(fieldvalues)

with patch("mailparser.utils.email.utils.getaddresses", new=legacy_getaddresses):
    print(get_addresses("Plain Name <plain@example.com>"))

Actual (before the fix):

TypeError: getaddresses() got an unexpected keyword argument 'strict'

Corrected (after the fix):

[('Plain Name', 'plain@example.com')]

Fix

Feature-detect the strict parameter before passing it, in a small helper
_getaddresses:

if "strict" in inspect.signature(email.utils.getaddresses).parameters:
    return email.utils.getaddresses(fieldvalues, strict=True)
return email.utils.getaddresses(fieldvalues)

The change is localized to get_addresses: it preserves strict, CVE-2023-27043
hardened parsing on interpreters that support it (the existing empty-address
regex fallback is unchanged) and only omits the keyword on older interpreters
where it is unavailable. No behavioral change on Python 3.13+.

Tests

Added tests/test_utils.py::TestUtilsEdgeCases::test_get_addresses_without_strict_parameter,
which patches in a pre-3.13 getaddresses (a real function without strict,
so the feature detection observes the older signature) and asserts the address
is parsed rather than crashing.

  • On unmodified code this test fails with the reported TypeError at
    src/mailparser/utils.py:137.
  • With the fix it passes.
  • Full suite: 211 passed (python -m pytest tests/).
  • ruff check and ruff format --check are clean on the changed files.

Disclosure: prepared with AI assistance; reviewed and verified locally.

… 3.13

email.utils.getaddresses only gained the ``strict`` keyword in Python 3.13
(backported to later 3.9-3.12 security patch releases). mail-parser targets
``requires-python >=3.9,<3.15``, so on an earlier patch release (e.g. CPython
3.11.3) get_addresses raised:

    TypeError: getaddresses() got an unexpected keyword argument 'strict'

which made downstream callers (e.g. parsedmarc) treat valid messages as
invalid. Detect whether ``strict`` is supported via inspect.signature and only
pass it when available, falling back to the default call otherwise.

Adds a regression test that simulates a pre-3.13 getaddresses and asserts the
address is parsed instead of crashing.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant