Skip to content

Fix mailbox selection and expose forensic address recovery defects - #176

Merged
fedelemantuano merged 5 commits into
masterfrom
bugfix/issue_#175
Sep 30, 2026
Merged

fedelemantuano merged 5 commits into
masterfrom
bugfix/issue_#175

Conversation

@fedelemantuano

Copy link
Copy Markdown
Contributor

Fix mailbox selection when malformed address headers contain a trusted-looking display name before an angle-address. For From: billing@trusted.example < billing@vendor.example >, the parser now returns billing@vendor.example as the mailbox, preserves billing@trusted.example as display-name evidence, and reports invalid-display-name instead of silently selecting the display text.

Closes #175

Changes relative to master

  • Replace the lenient regex fallback with a structural address scanner that respects quotes, escapes, nested comments, groups, folding whitespace, domain literals, and obsolete source routes. Complete, unambiguous angle-addresses take precedence; display names and comments are never searched for substitute mailboxes.
  • Expose address_header_defects with the reason, raw item, recovery status, candidates, header name, and occurrence. Include diagnostics in full/partial mail and JSON output, set has_defects and AddressHeaderDefect, and protect the computed metadata from colliding wire headers.
  • Retain ambiguous or incomplete items as diagnostic evidence without selecting an uncertain mailbox. Parse list members independently, inspect every repeated header occurrence, and preserve first-occurrence semantics for address properties. Cache results once per parse.
  • Add the issue's 432-header regression matrix and coverage for public outputs, quoted names, comments, malformed boundaries, repeated headers, metadata collisions, obsolete routes, and hostile-input performance.
  • Document recovery behavior and its limits in the README. Consolidate Claude and Codex guidance in AGENTS.md, imported by CLAUDE.md, and require RFC 5322 checks and evidence-preserving recovery for future code changes.
  • Refresh dependency versions and artifacts in uv.lock; project dependency declarations and the stdlib-only core remain unchanged.

Expected behavior

mail = mailparser.parse_from_string(
    "From: billing@trusted.example < billing@vendor.example >\r\n\r\n"
)
assert mail.from_ == [("billing@trusted.example", "billing@vendor.example")]
assert mail.has_defects is True
assert mail.address_header_defects[0]["reason"] == "invalid-display-name"

RFC 5322 sections 3.2.3, 3.2.5, and 3.4 distinguish display names from mailboxes; section 4 covers obsolete syntax accepted by receivers. Whitespace around the mailbox inside angle brackets is permitted; the unquoted @ in this example's display name is the defect. structural-recovery can also indicate a limitation of the standard-library parser and is not, by itself, proof of noncompliance. This recovery policy is not a complete RFC validator or a guarantee of how all mail clients interpret malformed messages.

Validation

  • Full suite on Python 3.13.2: 775 passed, 5 subtests passed.
  • All pre-commit hooks passed, including Ruff, Markdown checks, and Pyright (rerun with cache access after a sandbox restriction).
  • Executed the documented recovery example and verified its complete diagnostic output.
  • Local validation covers Python 3.13.2; the repository's Python 3.9–3.15 CI matrix will run on this PR.

The ReDoS hardening in 5aab122 excluded whitespace inside angle addresses, causing the fallback to select an email-like display name instead of the mailbox (issue #175).

Parse structural boundaries with quote, escape, comment and group awareness. Preserve recovery and ambiguity evidence in address_header_defects while retaining first-occurrence address semantics.

Validated with 775 tests on Python 3.13 and 3.14, the 432-header regression matrix, 20,000 malformed-input probes, scaling checks and pre-commit.
@coveralls

Copy link
Copy Markdown

Coverage Status

coverage: 99.818% (-0.2%) from 100.0% — bugfix/issue_#175 into master

@fedelemantuano
fedelemantuano merged commit 451880b into master Sep 30, 2026
8 checks passed
@fedelemantuano
fedelemantuano deleted the bugfix/issue_#175 branch September 30, 2026 21:45
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.

2 participants