mirror of
https://github.com/mjl-/mox.git
synced 2025-07-12 18:24:35 +03:00
add Content-Disposition and Filename to the payload of incoming webhooks
for each message part. The ContentDisposition value is the base value without header key/value parameters. the Filename field is the likely filename of the part. the different email clients encode filenames differently. there is a standard mime mechanism from rfc 2231. and there is the q/b-word-encoding from rfc 2047. instead of letting users of the webhook api deal with those differences, we provide just the parsed filename. for issue #258 by morki, thanks for reporting!
This commit is contained in:
@ -904,7 +904,12 @@ func (w Webmail) MessageSubmit(ctx context.Context, m SubmitMessage) {
|
||||
ap = ap.Parts[xp]
|
||||
}
|
||||
|
||||
filename := tryDecodeParam(log, ap.ContentTypeParams["name"])
|
||||
_, filename, err := ap.DispositionFilename()
|
||||
if err != nil && errors.Is(err, message.ErrParamEncoding) {
|
||||
log.Debugx("parsing disposition/filename", err)
|
||||
} else {
|
||||
xcheckf(ctx, err, "reading disposition")
|
||||
}
|
||||
if filename == "" {
|
||||
filename = "unnamed.bin"
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package webmail
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
@ -275,21 +276,16 @@ func parsedMessage(log mlog.Log, m store.Message, state *msgState, full, msgitem
|
||||
case "TEXT/PLAIN", "/":
|
||||
// Don't include if Content-Disposition attachment.
|
||||
if full || msgitem {
|
||||
// todo: should have this, and perhaps all content-* headers, preparsed in message.Part?
|
||||
h, err := p.Header()
|
||||
log.Check(err, "parsing attachment headers", slog.Int64("msgid", m.ID))
|
||||
cp := h.Get("Content-Disposition")
|
||||
if cp != "" {
|
||||
disp, params, err := mime.ParseMediaType(cp)
|
||||
log.Check(err, "parsing content-disposition", slog.String("cp", cp))
|
||||
if strings.EqualFold(disp, "attachment") {
|
||||
name := tryDecodeParam(log, p.ContentTypeParams["name"])
|
||||
if name == "" {
|
||||
name = tryDecodeParam(log, params["filename"])
|
||||
}
|
||||
addAttachment(Attachment{path, name, p}, parentMixed)
|
||||
return
|
||||
}
|
||||
disp, name, err := p.DispositionFilename()
|
||||
if err != nil && errors.Is(err, message.ErrParamEncoding) {
|
||||
log.Debugx("parsing disposition/filename", err)
|
||||
} else if err != nil {
|
||||
rerr = fmt.Errorf("reading disposition/filename: %v", err)
|
||||
return
|
||||
}
|
||||
if strings.EqualFold(disp, "attachment") {
|
||||
addAttachment(Attachment{path, name, p}, parentMixed)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1829,11 +1829,17 @@ func attachmentTypes(log mlog.Log, m store.Message, state *msgState) (map[Attach
|
||||
mt := strings.ToLower(a.Part.MediaType + "/" + a.Part.MediaSubType)
|
||||
if t, ok := attachmentMimetypes[mt]; ok {
|
||||
types[t] = true
|
||||
} else if ext := filepath.Ext(tryDecodeParam(log, a.Part.ContentTypeParams["name"])); ext != "" {
|
||||
continue
|
||||
}
|
||||
_, filename, err := a.Part.DispositionFilename()
|
||||
if err != nil && errors.Is(err, message.ErrParamEncoding) {
|
||||
log.Debugx("parsing disposition/filename", err)
|
||||
} else if err != nil {
|
||||
return nil, fmt.Errorf("reading disposition/filename: %v", err)
|
||||
}
|
||||
if ext := filepath.Ext(filename); ext != "" {
|
||||
if t, ok := attachmentExtensions[strings.ToLower(ext)]; ok {
|
||||
types[t] = true
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -521,13 +521,11 @@ func handle(apiHandler http.Handler, isForwarded bool, accountPath string, w htt
|
||||
names := map[string]bool{}
|
||||
for _, a := range mi.Attachments {
|
||||
ap := a.Part
|
||||
name := tryDecodeParam(log, ap.ContentTypeParams["name"])
|
||||
if name == "" {
|
||||
// We don't check errors, this is all best-effort.
|
||||
h, _ := ap.Header()
|
||||
disposition := h.Get("Content-Disposition")
|
||||
_, params, _ := mime.ParseMediaType(disposition)
|
||||
name = tryDecodeParam(log, params["filename"])
|
||||
_, name, err := ap.DispositionFilename()
|
||||
if err != nil && errors.Is(err, message.ErrParamEncoding) {
|
||||
log.Debugx("parsing disposition header for filename", err)
|
||||
} else {
|
||||
xcheckf(ctx, err, "reading disposition header")
|
||||
}
|
||||
if name != "" {
|
||||
name = filepath.Base(name)
|
||||
@ -816,13 +814,11 @@ func handle(apiHandler http.Handler, isForwarded bool, accountPath string, w htt
|
||||
h.Set("Content-Type", ct)
|
||||
h.Set("Cache-Control", "no-store, max-age=0")
|
||||
if t[1] == "download" {
|
||||
name := tryDecodeParam(log, ap.ContentTypeParams["name"])
|
||||
if name == "" {
|
||||
// We don't check errors, this is all best-effort.
|
||||
h, _ := ap.Header()
|
||||
disposition := h.Get("Content-Disposition")
|
||||
_, params, _ := mime.ParseMediaType(disposition)
|
||||
name = tryDecodeParam(log, params["filename"])
|
||||
_, name, err := ap.DispositionFilename()
|
||||
if err != nil && errors.Is(err, message.ErrParamEncoding) {
|
||||
log.Debugx("parsing disposition/filename", err)
|
||||
} else {
|
||||
xcheckf(ctx, err, "reading disposition/filename")
|
||||
}
|
||||
if name == "" {
|
||||
name = "attachment.bin"
|
||||
|
Reference in New Issue
Block a user