fix(email): guard IMAP FETCH response against non-bytes payloads#12498
fix(email): guard IMAP FETCH response against non-bytes payloads#12498Tipiweb wants to merge 1 commit into
Conversation
Some IMAP servers return malformed FETCH responses in which the body slot
is an int, plain bytes, or None instead of the expected (header, body)
tuple. Accessing msg_data[0][1] then either raises IndexError/TypeError
or returns an int, which surfaces later as the opaque error:
[Email] IMAP fetch error: int object has no attribute decode
This commit adds a two-stage guard around the fetch response:
1. try/except around msg_data[0][1] access (IndexError, TypeError)
2. isinstance(raw_email, (bytes, bytearray)) check before parsing
In both branches we log a warning with the offending UID and continue,
so the poll loop keeps processing the rest of the inbox rather than
dying on a single malformed message.
Observed on Raspberry Pi 4 / Debian bookworm talking to an OVH IMAP
server; prior to this patch the same UID would be retried forever
and fill the error log.
|
I'm testing this patch and now instead of an error (#18106) I see a warning. Just sharing in case this is unexpected. I also never got an email response so I may have a case where the build in email messaging doesn't work with @iCloud.com email accounts. |
|
Thanks for testing! The warning is the intended behavior — when iCloud (or any IMAP server) returns a FETCH response we don't recognize, we now skip that specific UID and keep polling the rest, instead of killing the whole poll loop on a cryptic Your log shows UID Re: "never got an email response" — if you were waiting on a reply to UID 17 specifically, that tracks: we skipped it. A fresh message should be picked up normally (different UID, different response shape). If you keep seeing the same skip pattern on every iCloud message, that's a separate iCloud-specific issue worth its own bug report (with a redacted raw IMAP trace from |
What changed and why
Some IMAP servers return malformed
FETCHresponses where the body slot is anint, plainbytes, orNoneinstead of the expected(header, body)tuple. Accessingmsg_data[0][1]then either raisesIndexError/TypeError, or returns an int (indexingbytesyields an int in Python 3), which only surfaces later as the opaque error:The exception kills the inner loop iteration and is caught by the outer
try/exceptat the bottom of_fetch_new_messages, so the entire polling pass returns empty — no messages are dispatched for that tick.Observed in production on a Raspberry Pi 4 / Debian bookworm talking to an OVH IMAP server. The same UID was retried every poll and flooded the error log.
Related
Complements #2794 (which adds a general
try/exceptaround IMAP response parsing) by specifically handling the non-bytes payload case that the bare exception catch would still mask behind a cryptic error message.The fix
Two-stage guard around the fetch response, inside the per-UID loop in
_fetch_new_messages:try/except (IndexError, TypeError)aroundmsg_data[0][1]access.isinstance(raw_email, (bytes, bytearray))check before handing off toemail_lib.message_from_bytes.In both branches we log a
warningwith the offending UID (and the raw response, or the observed payload type) andcontinue, so the poll loop keeps processing the rest of the inbox rather than bailing on a single malformed message.How I tested
Unit-tested the guard logic against 6 shapes observed in the wild:
[(b'flags', b'body')][b')'][None][][(b'flags', b'body'), b')'][(1, 2)]— our caseDeployed on the affected Raspberry Pi — previously the error fired every ~10 min; since the patch the log is clean and legitimate mail is still processed.
Platforms tested