From 8bc2c3d5a50fabd15e194d6d13c6d4a5772ad9fd Mon Sep 17 00:00:00 2001 From: Apoorv Darshan Date: Thu, 9 Jul 2026 22:22:16 +0530 Subject: [PATCH 1/2] fix(get_addresses): feature-detect getaddresses(strict=) for Python < 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. --- src/mailparser/utils.py | 27 ++++++++++++++++++++++++++- tests/test_utils.py | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/src/mailparser/utils.py b/src/mailparser/utils.py index be6e4d5..9b71315 100644 --- a/src/mailparser/utils.py +++ b/src/mailparser/utils.py @@ -25,6 +25,7 @@ import email.utils import functools import hashlib +import inspect import json import logging import os @@ -52,6 +53,30 @@ log = logging.getLogger(__name__) + +def _getaddresses(fieldvalues: list[str]) -> list[tuple[str, str]]: + """Call ``email.utils.getaddresses`` with strict parsing when available. + + The ``strict`` keyword was added to ``email.utils.getaddresses`` in + Python 3.13 (and backported only to later security patch releases of + 3.9-3.12, e.g. 3.11.10). mail-parser supports ``requires-python + >=3.9,<3.15``, so on an earlier patch release the keyword is absent and + passing it raises ``TypeError: getaddresses() got an unexpected keyword + argument 'strict'``. Feature-detect it before passing ``strict=True`` so + older interpreters keep working (parsedmarc #808). + """ + try: + supports_strict = ( + "strict" in inspect.signature(email.utils.getaddresses).parameters + ) + except (TypeError, ValueError): # pragma: no cover - defensive + supports_strict = False + + if supports_strict: + return email.utils.getaddresses(fieldvalues, strict=True) + return email.utils.getaddresses(fieldvalues) + + # --------------------------------------------------------------------------- # RFC 5322 address parsing — fallback for non-compliant display names # --------------------------------------------------------------------------- @@ -134,7 +159,7 @@ def get_addresses( elif not isinstance(raw_header, str): raw_header = str(raw_header) - parsed = email.utils.getaddresses([raw_header], strict=True) + parsed = _getaddresses([raw_header]) # If every result from the strict parser has an empty address — while the # raw header is non-empty — fall back to regex extraction so that the diff --git a/tests/test_utils.py b/tests/test_utils.py index 734900d..0aecb27 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -771,6 +771,43 @@ def test_get_addresses_fallback_regex_no_matches(self): result = get_addresses("not an email address at all") self.assertEqual(result, [("", "")]) + def test_get_addresses_without_strict_parameter(self): + """ + Regression for parsedmarc #808: get_addresses must not pass the + ``strict`` keyword unconditionally to email.utils.getaddresses. + + The ``strict`` parameter was added to ``email.utils.getaddresses`` in + Python 3.13 (and backported only to later security patch releases of + 3.9-3.12). mail-parser targets ``requires-python >=3.9,<3.15``, so on + an earlier patch release (e.g. CPython 3.11.3) the call raised:: + + TypeError: getaddresses() got an unexpected keyword argument 'strict' + + Here we simulate a pre-3.13 ``getaddresses`` (no ``strict`` parameter) + and assert that get_addresses parses the address instead of crashing. + """ + + import email.utils + + real_getaddresses = email.utils.getaddresses + + def legacy_getaddresses(fieldvalues): + """Mimic the pre-3.13 signature that lacks ``strict``. + + Patched in as a real function (not a Mock) so that the feature + detection in get_addresses observes a signature without + ``strict`` and does not attempt to pass the keyword. + """ + return real_getaddresses(fieldvalues) + + with patch( + "mailparser.utils.email.utils.getaddresses", + new=legacy_getaddresses, + ): + result = get_addresses("Plain Name ") + + self.assertEqual(result, [("Plain Name", "plain@example.com")]) + def test_parse_received_sendgrid_date(self): """parse_received extracts SendGrid non-standard date (utils.py:389-390)""" received = ( From d972e641ae41be1c4f28a0cfe0250c2e1218bdf3 Mon Sep 17 00:00:00 2001 From: Fedele Mantuano Date: Wed, 29 Jul 2026 23:29:54 +0200 Subject: [PATCH 2/2] fix(get_addresses): implement feature detection for strict parsing in getaddresses --- src/mailparser/utils.py | 37 +++++++++++++++++++++---------------- tests/test_utils.py | 38 ++++++++++++++++++++++++-------------- 2 files changed, 45 insertions(+), 30 deletions(-) diff --git a/src/mailparser/utils.py b/src/mailparser/utils.py index 9b71315..1bb03ac 100644 --- a/src/mailparser/utils.py +++ b/src/mailparser/utils.py @@ -54,25 +54,30 @@ log = logging.getLogger(__name__) -def _getaddresses(fieldvalues: list[str]) -> list[tuple[str, str]]: - """Call ``email.utils.getaddresses`` with strict parsing when available. +# The ``strict`` keyword was added to ``email.utils.getaddresses`` in Python +# 3.13 (and backported only to later security patch releases of 3.9-3.12, +# e.g. 3.11.10). mail-parser supports ``requires-python >=3.9,<3.15``, so on +# an earlier patch release the keyword is absent and passing it raises +# ``TypeError: getaddresses() got an unexpected keyword argument 'strict'`` +# (parsedmarc #808). The signature is fixed for the running interpreter, so +# detect support once at import time. +_GETADDRESSES_SUPPORTS_STRICT = ( + "strict" in inspect.signature(email.utils.getaddresses).parameters +) + - The ``strict`` keyword was added to ``email.utils.getaddresses`` in - Python 3.13 (and backported only to later security patch releases of - 3.9-3.12, e.g. 3.11.10). mail-parser supports ``requires-python - >=3.9,<3.15``, so on an earlier patch release the keyword is absent and - passing it raises ``TypeError: getaddresses() got an unexpected keyword - argument 'strict'``. Feature-detect it before passing ``strict=True`` so - older interpreters keep working (parsedmarc #808). +def _getaddresses(fieldvalues: list[str]) -> list[tuple[str, str]]: """ - try: - supports_strict = ( - "strict" in inspect.signature(email.utils.getaddresses).parameters - ) - except (TypeError, ValueError): # pragma: no cover - defensive - supports_strict = False + Call ``email.utils.getaddresses`` with strict parsing when available. + + Args: + fieldvalues (list[str]): raw address header values to parse. - if supports_strict: + Returns: + list[tuple[str, str]]: list of ``(display_name, email_addr)`` tuples, + as returned by ``email.utils.getaddresses``. + """ + if _GETADDRESSES_SUPPORTS_STRICT: return email.utils.getaddresses(fieldvalues, strict=True) return email.utils.getaddresses(fieldvalues) diff --git a/tests/test_utils.py b/tests/test_utils.py index 0aecb27..e56283e 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -17,6 +17,8 @@ """ import base64 +import email.utils +import inspect import os import tempfile import unittest @@ -24,6 +26,7 @@ from mailparser.exceptions import MailParserOSError, MailParserReceivedParsingError from mailparser.utils import ( + _GETADDRESSES_SUPPORTS_STRICT, decode_header_part, find_between, get_addresses, @@ -783,31 +786,38 @@ def test_get_addresses_without_strict_parameter(self): TypeError: getaddresses() got an unexpected keyword argument 'strict' - Here we simulate a pre-3.13 ``getaddresses`` (no ``strict`` parameter) - and assert that get_addresses parses the address instead of crashing. + Here we simulate a pre-3.13 interpreter: the feature-detection flag + is forced off and ``getaddresses`` is replaced by a function whose + signature lacks ``strict``, so passing the keyword would raise the + reported TypeError. The address must still be parsed. """ - - import email.utils - real_getaddresses = email.utils.getaddresses def legacy_getaddresses(fieldvalues): - """Mimic the pre-3.13 signature that lacks ``strict``. - - Patched in as a real function (not a Mock) so that the feature - detection in get_addresses observes a signature without - ``strict`` and does not attempt to pass the keyword. - """ + """Mimic the pre-3.13 signature that lacks ``strict``.""" return real_getaddresses(fieldvalues) - with patch( - "mailparser.utils.email.utils.getaddresses", - new=legacy_getaddresses, + with ( + patch("mailparser.utils._GETADDRESSES_SUPPORTS_STRICT", False), + patch( + "mailparser.utils.email.utils.getaddresses", + new=legacy_getaddresses, + ), ): result = get_addresses("Plain Name ") self.assertEqual(result, [("Plain Name", "plain@example.com")]) + def test_get_addresses_strict_detection_matches_interpreter(self): + """ + The feature-detection flag must reflect the running interpreter, so + that ``strict=True`` is still used wherever it is available. + """ + self.assertEqual( + _GETADDRESSES_SUPPORTS_STRICT, + "strict" in inspect.signature(email.utils.getaddresses).parameters, + ) + def test_parse_received_sendgrid_date(self): """parse_received extracts SendGrid non-standard date (utils.py:389-390)""" received = (