From a71f609f3bf8621b553c4ae8bd3b3dc013bca6a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20F=C3=B6rtsch?= Date: Sun, 15 Mar 2026 12:30:34 +0100 Subject: [PATCH] decode RFC 2047 subjects/sender names, parse RFC 2822 dates from IMAP envelopes - decode RFC 2047 encoded words in subject and fromName via RFC2047Decoder before storing in MessageRecord (fixes =?utf-8?Q?...?= raw display) - add RFC 2822 date formatters to Queries.parseDate (EEE, dd MMM yyyy HH:mm:ss Z) so IMAP envelope dates parse correctly instead of falling back to distantPast - parseDate now tries ISO 8601, RFC 2822 with day-of-week, RFC 2822 without Co-Authored-By: Claude Opus 4.6 (1M context) --- .../Sources/MailStore/Queries.swift | 25 +++++++++++++++++-- .../Sources/SyncEngine/SyncCoordinator.swift | 7 ++++-- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/Packages/MagnumOpusCore/Sources/MailStore/Queries.swift b/Packages/MagnumOpusCore/Sources/MailStore/Queries.swift index 8c04a52..e4a002e 100644 --- a/Packages/MagnumOpusCore/Sources/MailStore/Queries.swift +++ b/Packages/MagnumOpusCore/Sources/MailStore/Queries.swift @@ -82,8 +82,29 @@ extension MailStore { ISO8601DateFormatter() }() - static func parseDate(_ iso: String) -> Date? { - isoFormatterWithFractional.date(from: iso) ?? isoFormatter.date(from: iso) + nonisolated(unsafe) private static let rfc2822Formatter: DateFormatter = { + let f = DateFormatter() + f.locale = Locale(identifier: "en_US_POSIX") + f.dateFormat = "EEE, dd MMM yyyy HH:mm:ss Z" + return f + }() + + nonisolated(unsafe) private static let rfc2822FormatterNoDow: DateFormatter = { + let f = DateFormatter() + f.locale = Locale(identifier: "en_US_POSIX") + f.dateFormat = "dd MMM yyyy HH:mm:ss Z" + return f + }() + + static func parseDate(_ dateString: String) -> Date? { + let trimmed = dateString.trimmingCharacters(in: .whitespaces) + // Try ISO 8601 first + if let d = isoFormatterWithFractional.date(from: trimmed) { return d } + if let d = isoFormatter.date(from: trimmed) { return d } + // Try RFC 2822 (IMAP envelope date format) + if let d = rfc2822Formatter.date(from: trimmed) { return d } + if let d = rfc2822FormatterNoDow.date(from: trimmed) { return d } + return nil } public static func toMessageSummary(_ record: MessageRecord) -> MessageSummary { diff --git a/Packages/MagnumOpusCore/Sources/SyncEngine/SyncCoordinator.swift b/Packages/MagnumOpusCore/Sources/SyncEngine/SyncCoordinator.swift index d1e931f..d626191 100644 --- a/Packages/MagnumOpusCore/Sources/SyncEngine/SyncCoordinator.swift +++ b/Packages/MagnumOpusCore/Sources/SyncEngine/SyncCoordinator.swift @@ -277,6 +277,9 @@ public final class SyncCoordinator { ) -> MessageRecord { let toJson = encodeAddresses(envelope.to) let ccJson = encodeAddresses(envelope.cc) + // Decode RFC 2047 encoded words in subject and sender name + let decodedSubject = envelope.subject.map { RFC2047Decoder.decode($0) } + let decodedFromName = envelope.from?.name.map { RFC2047Decoder.decode($0) } return MessageRecord( id: UUID().uuidString, accountId: accountId, @@ -285,9 +288,9 @@ public final class SyncCoordinator { messageId: envelope.messageId, inReplyTo: envelope.inReplyTo, refs: envelope.references, - subject: envelope.subject, + subject: decodedSubject, fromAddress: envelope.from?.address, - fromName: envelope.from?.name, + fromName: decodedFromName, toAddresses: toJson, ccAddresses: ccJson, date: envelope.date,