mirror of
https://github.com/mjl-/mox.git
synced 2025-07-12 17:44: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:
@ -8,10 +8,12 @@
|
||||
package webhook
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/mjl-/mox/message"
|
||||
"github.com/mjl-/mox/mlog"
|
||||
)
|
||||
|
||||
// OutgoingEvent is an activity for an outgoing delivery. Either generated by the
|
||||
@ -135,29 +137,43 @@ type NameAddress struct {
|
||||
}
|
||||
|
||||
type Structure struct {
|
||||
ContentType string // Lower case, e.g. text/plain.
|
||||
ContentTypeParams map[string]string // Lower case keys, original case values, e.g. {"charset": "UTF-8"}.
|
||||
ContentID string // Can be empty. Otherwise, should be a value wrapped in <>'s. For use in HTML, referenced as URI `cid:...`.
|
||||
DecodedSize int64 // Size of content after decoding content-transfer-encoding. For text and HTML parts, this can be larger than the data returned since this size includes \r\n line endings.
|
||||
Parts []Structure // Subparts of a multipart message, possibly recursive.
|
||||
ContentType string // Lower case, e.g. text/plain.
|
||||
ContentTypeParams map[string]string // Lower case keys, original case values, e.g. {"charset": "UTF-8"}.
|
||||
ContentID string // Can be empty. Otherwise, should be a value wrapped in <>'s. For use in HTML, referenced as URI `cid:...`.
|
||||
ContentDisposition string // Lower-case value, e.g. "attachment", "inline" or empty when absent. Without the key/value header parameters.
|
||||
Filename string // Filename for this part, based on "filename" parameter from Content-Disposition, or "name" from Content-Type after decoding.
|
||||
DecodedSize int64 // Size of content after decoding content-transfer-encoding. For text and HTML parts, this can be larger than the data returned since this size includes \r\n line endings.
|
||||
Parts []Structure // Subparts of a multipart message, possibly recursive.
|
||||
}
|
||||
|
||||
// PartStructure returns a Structure for a parsed message part.
|
||||
func PartStructure(p *message.Part) Structure {
|
||||
func PartStructure(log mlog.Log, p *message.Part) (Structure, error) {
|
||||
parts := make([]Structure, len(p.Parts))
|
||||
for i := range p.Parts {
|
||||
parts[i] = PartStructure(&p.Parts[i])
|
||||
var err error
|
||||
parts[i], err = PartStructure(log, &p.Parts[i])
|
||||
if err != nil && !errors.Is(err, message.ErrParamEncoding) {
|
||||
return Structure{}, err
|
||||
}
|
||||
}
|
||||
disp, filename, err := p.DispositionFilename()
|
||||
if err != nil && errors.Is(err, message.ErrParamEncoding) {
|
||||
log.Debugx("parsing disposition/filename", err)
|
||||
} else if err != nil {
|
||||
return Structure{}, err
|
||||
}
|
||||
s := Structure{
|
||||
ContentType: strings.ToLower(p.MediaType + "/" + p.MediaSubType),
|
||||
ContentTypeParams: p.ContentTypeParams,
|
||||
ContentID: p.ContentID,
|
||||
DecodedSize: p.DecodedSize,
|
||||
Parts: parts,
|
||||
ContentType: strings.ToLower(p.MediaType + "/" + p.MediaSubType),
|
||||
ContentTypeParams: p.ContentTypeParams,
|
||||
ContentID: p.ContentID,
|
||||
ContentDisposition: strings.ToLower(disp),
|
||||
Filename: filename,
|
||||
DecodedSize: p.DecodedSize,
|
||||
Parts: parts,
|
||||
}
|
||||
// Replace nil map with empty map, for easier to use JSON.
|
||||
if s.ContentTypeParams == nil {
|
||||
s.ContentTypeParams = map[string]string{}
|
||||
}
|
||||
return s
|
||||
return s, nil
|
||||
}
|
||||
|
Reference in New Issue
Block a user