From f894d81be81c57e088302adf0106618c3d38f25b Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Thu, 13 Aug 2026 01:24:41 +0800 Subject: [PATCH] =?UTF-8?q?fix(mail):=20=E5=8E=9F=E6=96=87=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E5=A2=9E=E5=8A=A0=20blobId=E2=86=92Blob/get=20?= =?UTF-8?q?=E5=9B=9E=E9=80=80=EF=BC=8C=E5=85=BC=E5=AE=B9=20Stalwart=20?= =?UTF-8?q?=E4=B8=8D=E6=94=AF=E6=8C=81=20raw=20=E5=B1=9E=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/jmap.ts | 42 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/src/utils/jmap.ts b/src/utils/jmap.ts index 60bad85..96d5154 100644 --- a/src/utils/jmap.ts +++ b/src/utils/jmap.ts @@ -165,17 +165,47 @@ export class JmapClient { return '' } - /** 拉取邮件 RFC 5322 原文(含全部头部),通过 Email/get 的 raw 属性(base64) */ + /** 拉取邮件 RFC 5322 原文(含全部头部)。 + * + * 优先用 Email/get 的 raw 属性(RFC 8621,base64);Stalwart 部分版本不支持 + * raw(返回 notFound/空),回退 blobId → Blob/get 取完整消息,兼容 + * data / data:asBytes / data:asText 三种返回格式。 + */ async fetchEmailRaw(emailId: string): Promise { - const payload = await this.call<{ list?: Array<{ raw?: string }> }>('Email/get', { + const payload = await this.call<{ list?: Array<{ raw?: string; blobId?: string }> }>('Email/get', { accountId: this.accountId, ids: [emailId], - properties: ['raw'], + properties: ['raw', 'blobId'], }) - const raw = payload.list?.[0]?.raw - if (raw == null) return '' + const item = payload.list?.[0] + if (!item) return '' + if (item.raw != null) { + try { + return atob(item.raw) + } catch { + /* raw 解码失败,走 blob 回退 */ + } + } + if (!item.blobId) return '' try { - return atob(raw) + const blob = await this.call<{ + list?: Array<{ data?: string; 'data:asText'?: string; 'data:asBytes'?: string }> + }>('Blob/get', { accountId: this.accountId, ids: [item.blobId] }, [ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:blob', + ]) + const b = blob.list?.[0] + if (!b) return '' + if (b['data:asText'] != null) return b['data:asText'] + const b64 = b.data ?? b['data:asBytes'] + if (b64) { + try { + return atob(b64) + } catch { + return '' + } + } + return '' } catch { return '' }