From 27d37e8bbb6c1b4f4c515c9878e8edb7cebe5380 Mon Sep 17 00:00:00 2001 From: disqualifier Date: Mon, 6 Jul 2026 19:24:57 -0400 Subject: [PATCH] fix: tolerate a malformed encoded-word subject; materialize lengths once decode_header_value's except tuple missed email.errors.HeaderParseError, so a malformed base64 encoded-word subject (=?utf-8?B?A?=) crashed the OTP scan instead of falling back to raw - now caught alongside the other decode errors. retrieve_otp consumed lengths once per fetched message via set(lengths), so a generator was exhausted after the first message and a later message's fallback code was missed - list() it once up front like folders. Signed-off-by: disqualifier --- src/aiomail/extract.py | 3 ++- src/aiomail/retrieve.py | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/aiomail/extract.py b/src/aiomail/extract.py index 2fc7abc..29f7352 100644 --- a/src/aiomail/extract.py +++ b/src/aiomail/extract.py @@ -2,6 +2,7 @@ import email.message import logging import re +from email.errors import HeaderParseError from email.header import decode_header, make_header from typing import Callable, Iterable, Iterator, Optional, Pattern, Sequence, Union @@ -44,7 +45,7 @@ def decode_header_value(raw: str) -> str: return raw try: return str(make_header(decode_header(raw))) - except (UnicodeDecodeError, LookupError, ValueError) as exc: + except (UnicodeDecodeError, LookupError, ValueError, HeaderParseError) as exc: log.debug("header decode failed (%s): %s", raw, exc) return raw diff --git a/src/aiomail/retrieve.py b/src/aiomail/retrieve.py index 8e0167d..ce73eca 100644 --- a/src/aiomail/retrieve.py +++ b/src/aiomail/retrieve.py @@ -75,6 +75,7 @@ async def retrieve_otp( forwarded match on From. set `max_age=None` to disable the freshness check. """ folders = list(folders) if folders is not None else list(DEFAULT_FOLDERS) + lengths = list(lengths) sender_ok = as_predicate(sender) subject_ok = as_predicate(subject) query = _server_query(sender, subject, match_field)