"""Message-ID and subject normalization (SPEC §7.1, §7.3).""" from __future__ import annotations import hashlib import re # Repeated reply/forward prefixes to strip, case-insensitive (incl. Polish Odp:/PD:). _PREFIX_RE = re.compile(r"^\s*(re|fw|fwd|odp|pd)\s*:\s*", re.IGNORECASE) _WS_RE = re.compile(r"\s+") _ANGLE_RE = re.compile(r"<([^>]*)>") def normalize_message_id(raw: str | None) -> str | None: """Normalize a Message-ID per SPEC §7.1. Strip angle brackets and whitespace, lowercase the domain part only. Return None for obviously invalid ids (no ``@``). """ if raw is None: return None text = raw.strip() m = _ANGLE_RE.search(text) if m: text = m.group(1) text = text.strip().strip("<>").strip() if "@" not in text: return None local, _, domain = text.rpartition("@") if not local or not domain: return None return f"{local}@{domain.lower()}" def normalize_reference_list(raw: str | None) -> list[str]: """Parse a References / In-Reply-To header value into normalized ids, in order.""" if not raw: return [] out: list[str] = [] for token in _ANGLE_RE.findall(raw) or raw.split(): norm = normalize_message_id(token) if norm and norm not in out: out.append(norm) return out def synthesize_message_id(raw_message: bytes) -> str: """Synthesize a non-authoritative Message-ID for a message that lacks one (§7.1).""" digest = hashlib.sha256(raw_message).hexdigest()[:32] return f"{digest}@synthetic.local" def is_synthetic(message_id: str | None) -> bool: """Return True if ``message_id`` is a synthesized (non-authoritative) id.""" return message_id is not None and message_id.endswith("@synthetic.local") def normalize_subject(subject: str) -> str: """Normalize a subject for the §7.3 fallback: strip repeated Re:/Fw:/... prefixes.""" text = subject or "" while True: stripped = _PREFIX_RE.sub("", text, count=1) if stripped == text: break text = stripped return _WS_RE.sub(" ", text).strip()