Skip to content

Commit

Permalink
webapi: implement adding "alternative files" to messages sent with th…
Browse files Browse the repository at this point in the history
…e Send method

with new field "AlternativeFiles" in the JSON body, or with "alternativefile" form file uploads.

can be used if there is a (full) alternative representation (alternative to
text and/or html part), like a calendar item, or PDF file.

for issue #188 by morki
  • Loading branch information
mjl- committed Aug 23, 2024
1 parent 62bd2f4 commit 6c488ea
Show file tree
Hide file tree
Showing 4 changed files with 40 additions and 11 deletions.
8 changes: 4 additions & 4 deletions webapi/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,10 +114,10 @@ func badResponse(hresp *http.Response) error {
// Configure webhooks to receive updates about deliveries.
//
// If the request is a multipart/form-data, uploaded files with the form keys
// "inlinefile" and/or "attachedfile" will be added to the message. If the uploaded
// file has content-type and/or content-id headers, they will be included. If no
// content-type is present in the request, and it can be detected, it is included
// automatically.
// "alternativefile", "inlinefile" and/or "attachedfile" will be added to the
// message. If the uploaded file has content-type and/or content-id headers, they
// will be included. If no content-type is present in the request, and it can be
// detected, it is included automatically.
//
// Example call with a text and html message, with an inline and an attached image:
//
Expand Down
5 changes: 5 additions & 0 deletions webapi/webapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,11 @@ type SendRequest struct {
// Unless a User-Agent or X-Mailer header is present, a User-Agent is added.
Headers [][2]string

// Alternative files are added as (full) alternative representation of the text
// and/or html parts. Alternative files cause a part with content-type
// "multipart/alternative" to be added to the message. Optional.
AlternativeFiles []File

// Inline files are added to the message and should be displayed by mail clients as
// part of the message contents. Inline files cause a part with content-type
// "multipart/related" to be added to the message. Optional.
Expand Down
22 changes: 16 additions & 6 deletions webapisrv/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -759,8 +759,9 @@ func (s server) Send(ctx context.Context, req webapi.SendRequest) (resp webapi.S
xc.Header("User-Agent", "mox/"+moxvar.Version)
}

// Whether we have additional separately inline/attached file(s).
// Whether we have additional separately alternative/inline/attached file(s).
mpf := reqInfo.Request.MultipartForm
formAlternative := mpf != nil && len(mpf.File["alternativefile"]) > 0
formInline := mpf != nil && len(mpf.File["inlinefile"]) > 0
formAttachment := mpf != nil && len(mpf.File["attachedfile"]) > 0

Expand All @@ -770,6 +771,7 @@ func (s server) Send(ctx context.Context, req webapi.SendRequest) (resp webapi.S
// - multipart/alternative (in case we have both text and html bodies)
// - text/plain (optional)
// - text/html (optional)
// - alternative file, ...
// - inline file, ...
// - attached file, ...

Expand Down Expand Up @@ -811,7 +813,7 @@ func (s server) Send(ctx context.Context, req webapi.SendRequest) (resp webapi.S
related = xcreateMultipart("related")
cur = related
}
if m.Text != "" && m.HTML != "" {
if m.Text != "" && m.HTML != "" || len(req.AlternativeFiles) > 0 || formAlternative {
alternative = xcreateMultipart("alternative")
cur = alternative
}
Expand All @@ -827,10 +829,6 @@ func (s server) Send(ctx context.Context, req webapi.SendRequest) (resp webapi.S
_, err := tp.Write([]byte(htmlBody))
xcheckf(err, "write html part")
}
if alternative != nil {
alternative.Close()
alternative = nil
}

xaddFileBase64 := func(ct string, inline bool, filename string, cid string, base64Data string) {
h := textproto.MIMEHeader{}
Expand Down Expand Up @@ -923,6 +921,18 @@ func (s server) Send(ctx context.Context, req webapi.SendRequest) (resp webapi.S
xcheckf(err, "flushing uploaded file")
}

cur = alternative
xaddJSONFiles(req.AlternativeFiles, true)
if mpf != nil {
for _, fh := range mpf.File["alternativefile"] {
xaddFile(fh, true)
}
}
if alternative != nil {
alternative.Close()
alternative = nil
}

cur = related
xaddJSONFiles(req.InlineFiles, true)
if mpf != nil {
Expand Down
16 changes: 15 additions & 1 deletion webapisrv/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,13 @@ func TestServer(t *testing.T) {
},
Extra: map[string]string{"a": "123"},
Headers: [][2]string{{"x-custom", "header"}},
AlternativeFiles: []webapi.File{
{
Name: "x.ics",
ContentType: "text/calendar",
Data: base64.StdEncoding.EncodeToString([]byte("ics data...")),
},
},
InlineFiles: []webapi.File{
{
Name: "x.png",
Expand Down Expand Up @@ -228,8 +235,15 @@ func TestServer(t *testing.T) {
sendReqBuf, err := json.Marshal(fdSendReq)
tcheckf(t, err, "send request")
mp.WriteField("request", string(sendReqBuf))

// One alternative file.
pw, err := mp.CreateFormFile("alternativefile", "test.ics")
tcheckf(t, err, "create alternative ics file")
_, err = fmt.Fprint(pw, "ICS...")
tcheckf(t, err, "write ics")

// Two inline PDFs.
pw, err := mp.CreateFormFile("inlinefile", "test.pdf")
pw, err = mp.CreateFormFile("inlinefile", "test.pdf")
tcheckf(t, err, "create inline pdf file")
_, err = fmt.Fprint(pw, "%PDF-")
tcheckf(t, err, "write pdf")
Expand Down

0 comments on commit 6c488ea

Please sign in to comment.