fix(imap): FETCH BODY/BODYSTRUCTURE 解析失败时服务器 panic 导致客户端只能取到部分邮件
根因:backendutil.FetchBodyStructure 对部分消息返回 nil(典型场景: - message/rfc822 附件为 base64 编码时库内不解码,把编码文本当嵌套 消息头解析报错(转发邮件场景,如 .mail-monitor/forward.py 转发) - multipart 缺少结束边界(截断)时 extended 解析报错) buildIMAPMessage 未处理 nil,go-imap 格式化 FETCH 响应时在 send() 协程 nil 指针解引用 panic,连接中断——Thunderbird 只取到崩溃前已 发送的几封邮件,手机客户端一直卡在"正在获取邮件"。 修复: - BodyStructure 解析失败时降级为 text/plain 单段结构,杜绝 nil - FetchBodySection 返回 nil 时跳过该 section,不再写入 nil literal - 新增集成测试 TestFetchBodyMalformedMIME 覆盖两类畸形 MIME
This commit is contained in:
@@ -461,6 +461,15 @@ func (m *imapMailbox) buildIMAPMessage(dbMsg *db.Message, seqNum uint32, items [
|
||||
if err == nil {
|
||||
imapMsg.BodyStructure, _ = backendutil.FetchBodyStructure(hdr, body, item == imap.FetchBodyStructure)
|
||||
}
|
||||
// 防御:FetchBodyStructure 对部分合法/畸形 MIME 会失败并返回
|
||||
// nil(典型:message/rfc822 附件为 base64 编码时库内不解码
|
||||
// 直接按嵌套消息解析头;或 multipart 边界截断)。BodyStructure
|
||||
// 为 nil 时 go-imap 格式化 FETCH 响应会在 send() 协程 panic
|
||||
// (nil 指针解引用),连接中断导致客户端只收到部分邮件甚至
|
||||
// 一直卡在同步。解析失败时降级为 text/plain 单段结构。
|
||||
if imapMsg.BodyStructure == nil {
|
||||
imapMsg.BodyStructure = fallbackBodyStructure(rawMsg)
|
||||
}
|
||||
default:
|
||||
section, err := imap.ParseBodySectionName(item)
|
||||
if err != nil {
|
||||
@@ -471,13 +480,33 @@ func (m *imapMailbox) buildIMAPMessage(dbMsg *db.Message, seqNum uint32, items [
|
||||
return nil, err
|
||||
}
|
||||
literal, _ := backendutil.FetchBodySection(hdr, body, section)
|
||||
imapMsg.Body[section] = literal
|
||||
if literal != nil {
|
||||
imapMsg.Body[section] = literal
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return imapMsg, nil
|
||||
}
|
||||
|
||||
// fallbackBodyStructure 构造一个 text/plain 单段 BodyStructure,用于
|
||||
// MIME 解析失败的消息(保证 FETCH BODY/BODYSTRUCTURE 不因 nil 崩溃)。
|
||||
func fallbackBodyStructure(raw []byte) *imap.BodyStructure {
|
||||
size := uint32(len(raw))
|
||||
lines := uint32(bytes.Count(raw, []byte{'\n'}))
|
||||
if len(raw) > 0 && raw[len(raw)-1] != '\n' {
|
||||
lines++
|
||||
}
|
||||
return &imap.BodyStructure{
|
||||
MIMEType: "text",
|
||||
MIMESubType: "plain",
|
||||
Params: map[string]string{"charset": "utf-8"},
|
||||
Encoding: "8bit",
|
||||
Size: size,
|
||||
Lines: lines,
|
||||
}
|
||||
}
|
||||
|
||||
func messageRawData(msg *db.Message) []byte {
|
||||
if msg.RawData != "" {
|
||||
return []byte(msg.RawData)
|
||||
|
||||
Reference in New Issue
Block a user