From 2c003991bb98025582195d4f6a4aae27875f396d Mon Sep 17 00:00:00 2001 From: Mechiel Lukkien Date: Mon, 5 Aug 2024 12:10:10 +0200 Subject: [PATCH] webmail: put attached files before inline files some emails have text and html versions. the html can have several logo images. and there may be a pdf attached. when gathering attachments to show in webmail, the pdf would come last. it could happen the logo images would get a link to click, and the pdf would be behind the "more ..." button. by putting "multipart/mixed" files before the "multipart/related" in the list, it's more likely that useful files can be clicked immediately, and unimportant logo files are behind the "more"-button. --- webmail/message.go | 36 ++++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/webmail/message.go b/webmail/message.go index 29ed794505..53f65ae1a4 100644 --- a/webmail/message.go +++ b/webmail/message.go @@ -240,18 +240,34 @@ func parsedMessage(log mlog.Log, m store.Message, state *msgState, full, msgitem } pm.Texts = []string{} - pm.attachments = []Attachment{} + + // We track attachments from multipart/mixed differently from other attachments. + // The others are often inline, sometimes just some logo's in HTML alternative + // messages. We want to have our mixed attachments at the start of the list, but + // our descent-first parsing would result in inline messages first in the typical + // message. + var attachmentsMixed []Attachment + var attachmentsOther []Attachment + + addAttachment := func(a Attachment, isMixed bool) { + if isMixed { + attachmentsMixed = append(attachmentsMixed, a) + } else { + attachmentsOther = append(attachmentsOther, a) + } + } // todo: how should we handle messages where a user prefers html, and we want to show it, but it's a DSN that also has textual-only parts? e.g. gmail's dsn where the first part is multipart/related with multipart/alternative, and second part is the regular message/delivery-status. we want to display both the html and the text. - var usePart func(p message.Part, index int, parent *message.Part, path []int) - usePart = func(p message.Part, index int, parent *message.Part, path []int) { + var usePart func(p message.Part, index int, parent *message.Part, path []int, parentMixed bool) + usePart = func(p message.Part, index int, parent *message.Part, path []int, parentMixed bool) { mt := p.MediaType + "/" + p.MediaSubType + newParentMixed := mt == "MULTIPART/MIXED" for i, sp := range p.Parts { if mt == "MULTIPART/SIGNED" && i >= 1 { continue } - usePart(sp, i, &p, append(append([]int{}, path...), i)) + usePart(sp, i, &p, append(append([]int{}, path...), i), newParentMixed) } switch mt { case "TEXT/PLAIN", "/": @@ -269,7 +285,7 @@ func parsedMessage(log mlog.Log, m store.Message, state *msgState, full, msgitem if name == "" { name = tryDecodeParam(log, params["filename"]) } - pm.attachments = append(pm.attachments, Attachment{path, name, p}) + addAttachment(Attachment{path, name, p}, parentMixed) return } } @@ -333,7 +349,7 @@ func parsedMessage(log mlog.Log, m store.Message, state *msgState, full, msgitem return } if parentct == "MULTIPART/REPORT" && index == 2 && (mt == "MESSAGE/GLOBAL" || mt == "TEXT/RFC822") { - pm.attachments = append(pm.attachments, Attachment{path, "original.eml", p}) + addAttachment(Attachment{path, "original.eml", p}, parentMixed) return } @@ -349,11 +365,15 @@ func parsedMessage(log mlog.Log, m store.Message, state *msgState, full, msgitem name = tryDecodeParam(log, params["filename"]) } } - pm.attachments = append(pm.attachments, Attachment{path, name, p}) + addAttachment(Attachment{path, name, p}, parentMixed) } } } - usePart(*state.part, -1, nil, []int{}) + usePart(*state.part, -1, nil, []int{}, false) + + pm.attachments = []Attachment{} + pm.attachments = append(pm.attachments, attachmentsMixed...) + pm.attachments = append(pm.attachments, attachmentsOther...) if rerr == nil { pm.ID = m.ID