Skip to content

Decode RFC 2047 display names at the raw .eml boundary and pin the resynced SwiftMail fork - #152

Merged
tabmail-kmyi merged 4 commits into
mainfrom
agent/ios-eml-name-decode
Sep 10, 2026
Merged

tabmail-kmyi merged 4 commits into
mainfrom
agent/ios-eml-name-decode

Conversation

@tabmail-kmyi

@tabmail-kmyi tabmail-kmyi commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

Closes the C1 collision from the 2026-09-09 SwiftMail sync review: upstream #226 keeps parsed addresses in header wire form, so once the fork is re-pinned past it, nested .eml From/To/Cc names would render and index as =?UTF-8?B?…?=.

EmlParsing.parse now decodes From/To/Cc with SwiftMail's public String.decodeMIMEHeader, the same decoder the library applies to the Subject, and passes the Subject through untouched. Addresses, bodies and nested attachment metadata are unchanged.

Also carries the docs-only housekeeping commit and extends companion memory 126 with the root causes of all three collisions and the upstream fix branch status. The library-side bugs (quadratic RFC 2231 continuation parsing, blank/windows-1252 charset filename loss) are fixed by Cocoanetics/SwiftMail#233, carried as the fork's one deviation. Fork main was resynced to upstream 64a9a86 plus that fix, and this PR moves packages.SwiftMail.revision from a2d4a94f to d87295e3 for app and NSE (12 upstream commits pulled in, all classified in the housekeeping review; fork CI and upstream PR checks green on every platform).

Verification: resolved checkout proven at d87295e3 by rev-parse; app, NSE and TabMailTests build; full TabMailTests target (9,727 tests) ran at the code-identical previous fork commit with two WKWebView-hosting suites failing under host load >500 and passing on isolated rerun, nothing else; EML consumer suites (39 tests across 5 suites) pass at the final pin. New tests: parseDecodesEncodedDisplayNames, a literal encoded-word-shaped subject with windows-1252 and ISO-8859-2 names asserted exactly through the rendered marker, and a bounded-work test on an oversized malformed Q-encoded name (64 tests in 6 EML suites pass at the final pin). Red/green against the candidate library reproduced with a consumer probe compiled against the fixed SwiftMail build (pre-fix: every name stays encoded; post-fix: all decoded, addresses retained).

🤖 Generated with Claude Code

https://claude.ai/code/session_0132BGdMBGzmQwA6jkB3rPma

Record accepted upstream contributions, the remaining NIO IMAP release dependency, and migration numbering at resume time. Preserve current dependency pins after the collision check reproduced receive rendering, filename, and parsing-work regressions.

Verified published issue and PR housekeeping, 619 upstream tests and strict lint, and baseline-green/candidate-red consumer probes. No app code, migration, device, or dependency revision changed.

(cherry picked from commit 6dec1536d7843332209acdb22260e9b8dc3f9e35)
Signed-off-by: Kwang Moo Yi <kmyi@tabmail.ai>
SwiftMail #226 changes parsed addresses to header wire form, so a nested
.eml whose From/To/Cc names could not be written literally comes back as
`=?UTF-8?B?...?= <addr>`. `EmlParsing.parse` passed those strings straight
to `EmlMarker`, which escapes HTML but does not decode MIME, so the names
would render and index as encoded-words once the fork is re-pinned past
that commit. The current pin still decodes inside the library, so nothing
is visibly broken today; this closes the gap before the pin advances.

`EmlParsing.parse` now runs subject, from, to and cc through
`RFC5322Parse.decodeRFC2047`, the same decoder `GmailParse` already uses
for subjects. It is a no-op on text without an encoded-word, so the result
is identical under both the current and the candidate SwiftMail revision.
Addresses, bodies and nested attachment metadata are untouched.

Verification: `EmlParsingTests` (9 tests) and the three provider mock
suites that consume EmlParsing (19 tests) pass on the iOS simulator. The
new `parseDecodesEncodedDisplayNames` test asserts decoded From/To/Cc and
subject and that the rendered marker carries no encoded-word. Red/green
against the candidate library was reproduced with the housekeeping
consumer probe compiled against the fixed SwiftMail branch: pre-fix
EmlParsing leaves every name encoded, post-fix decodes all three while
retaining the addresses. Companion memory 126 records the root causes of
all three collisions and the status of the upstream fix branch.

Signed-off-by: Kwang Moo Yi <kmyi@tabmail.ai>
Fork main is now upstream 64a9a86 (#232) plus one carried deviation, the
RFC 2231 continuation and charset fix that is open upstream as
Cocoanetics/SwiftMail#233. This moves packages.SwiftMail.revision from
a2d4a94f to d87295e3, the same commit for the app and the notification
extension, pulling in the twelve upstream commits the 2026-09-09
housekeeping review classified (#220 #222 #221 #224 #225 #226 #230 #229
#228 #227 #231 #232). The three receive-path collisions that review found
are closed: C1 by the previous commit's app-side decode, C2 and R1 by the
carried fork fix.

Verification: the resolved SwiftMail checkout was proven at d87295e3 by
rev-parse before and after building. App, notification extension and
TabMailTests build with only the pre-existing AutoSizingHTMLView
deprecation warning. The full TabMailTests target ran against the
previous, code-identical fork commit 6d8eae13: 9,727 tests, two WKWebView
hosting suites failed under a host load above 500 and passed on an
isolated rerun; no other failures. The EML consumer suites (EmlParsing,
IMAP nested-eml and render, Gmail and Exchange mocks, nested attachments;
39 tests) pass at the final pin. Fork CI is green on all platforms for
d87295e3, as are the upstream PR checks.

Signed-off-by: Kwang Moo Yi <kmyi@tabmail.ai>
…ct alone

Round-1 review of #152 found two defects in the previous commit's decode
pass. First, SwiftMail already decodes the Subject, so decoding it again
turned a literal encoded-word-shaped subject (which TabMail's own
RFC2047.encodeHeaderValue produces by wrapping it) into its decoded form.
Second, the app's minimal RFC5322Parse.decodeRFC2047 knows only UTF-8,
Latin-1, ASCII and UTF-16, so standards-compliant windows-1252 or
ISO-8859-2 names surfaced as raw payload text, and its quoted-printable
loop rescans the remaining string per escape, so one oversized malformed
Q-encoded name made parsing quadratic (3.8 s at 49 KB in an optimized
probe versus 6 ms before).

The Subject is now passed through untouched, and From/To/Cc are decoded
with SwiftMail's public String.decodeMIMEHeader, the decoder that produced
the Subject: full charset table, bounded work. This deletes the app-side
decoder from the path rather than extending it.

Verification on the simulator at the pinned SwiftMail d87295e3: EmlParsing,
EmlRender, IMAP nested-eml, Gmail and Exchange mock, and nested-attachment
suites, 64 tests in 6 suites, pass. Two tests added: a literal
encoded-word-shaped subject with windows-1252 B and Q and ISO-8859-2 Q
names asserted exactly through envelope and rendered marker metadata, and
a 16,384-escape malformed Q name that must parse in under a second with
the decoded name and body intact (measured 14 ms). Both fail on the
previous commit per the reviewer's source-isolated probes.

Signed-off-by: Kwang Moo Yi <kmyi@tabmail.ai>
@tabmail-kmyi
tabmail-kmyi merged commit 35d405e into main Sep 10, 2026
1 check passed
@tabmail-kmyi

Copy link
Copy Markdown
Contributor Author

Follow-up: the SwiftMail fix this pin carries (Cocoanetics/SwiftMail#233) has since gone through three more maintainer review rounds; its head is now 9f1a2b6, ahead of the pinned d87295e3. The added fixes matter on non-Apple platforms and for a blank-charset non-ASCII filename, so this pin stands. The next fork resync must replay the whole PR branch (or upstream's merged version), not the single commit. Tracked in #10; record update in #156.

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.

1 participant