From fce6593cd7851b9b02a0f5c68be3e6dd94eaf9c3 Mon Sep 17 00:00:00 2001 From: Aine Date: Wed, 16 Nov 2022 20:01:30 +0200 Subject: [PATCH] send multipart email with both html and plaintext by default, closes #22 --- bot/command.go | 28 ++++++---------------------- bot/email.go | 2 +- smtp/session.go | 2 +- utils/email.go | 36 ++++++++++++++++++++++++------------ 4 files changed, 32 insertions(+), 36 deletions(-) diff --git a/bot/command.go b/bot/command.go index d953787..74e352b 100644 --- a/bot/command.go +++ b/bot/command.go @@ -17,7 +17,6 @@ const ( commandHelp = "help" commandStop = "stop" commandSend = "send" - commandSendHTML = "send:html" commandDKIM = "dkim" commandCatchAll = botOptionCatchAll commandUsers = botOptionUsers @@ -68,11 +67,6 @@ func (b *Bot) initCommands() commandList { description: "Send email", allowed: b.allowSend, }, - { - key: commandSendHTML, - description: "Send email, converting markdown to HTML", - allowed: b.allowSend, - }, {allowed: b.allowOwner}, // delimiter // options commands { @@ -273,9 +267,7 @@ func (b *Bot) handleCommand(ctx context.Context, evt *event.Event, commandSlice case commandStop: b.runStop(ctx) case commandSend: - b.runSend(ctx, false) - case commandSendHTML: - b.runSend(ctx, true) + b.runSend(ctx) case commandDKIM: b.runDKIM(ctx, commandSlice) case commandUsers: @@ -386,30 +378,27 @@ func (b *Bot) sendHelp(ctx context.Context) { b.SendNotice(ctx, evt.RoomID, msg.String()) } -func (b *Bot) runSend(ctx context.Context, html bool) { +func (b *Bot) runSend(ctx context.Context) { evt := eventFromContext(ctx) if !b.allowSend(evt.Sender, evt.RoomID) { return } - subcommand := "send" - if html { - subcommand = "send:html" - } commandSlice := b.parseCommand(evt.Content.AsMessage().Body, false) to, subject, body, err := utils.ParseSend(commandSlice) if err == utils.ErrInvalidArgs { b.SendNotice(ctx, evt.RoomID, fmt.Sprintf( "Usage:\n"+ "```\n"+ - "%s %s someone@example.com\n"+ + "%s send someone@example.com\n"+ "Subject goes here on a line of its own\n"+ "Email content goes here\n"+ "on as many lines\n"+ "as you want.\n"+ "```", - b.prefix, subcommand)) + b.prefix)) return } + htmlBody := format.RenderMarkdown(body, true, true).FormattedBody cfg, err := b.getRoomSettings(evt.RoomID) if err != nil { @@ -432,11 +421,6 @@ func (b *Bot) runSend(ctx context.Context, html bool) { } } - var htmlBody string - if html { - htmlBody = format.RenderMarkdown(body, true, true).FormattedBody - } - b.lock(evt.RoomID.String()) defer b.unlock(evt.RoomID.String()) @@ -445,7 +429,7 @@ func (b *Bot) runSend(ctx context.Context, html bool) { ID := utils.MessageID(evt.ID, domain) for _, to := range tos { email := utils.NewEmail(ID, "", " "+ID, subject, from, to, body, htmlBody, nil) - data := email.Compose(html, b.getBotSettings().DKIMPrivateKey()) + data := email.Compose(b.getBotSettings().DKIMPrivateKey()) queued, err := b.Sendmail(evt.ID, from, to, data) if queued { b.log.Error("cannot send email: %v", err) diff --git a/bot/email.go b/bot/email.go index e81bfca..0f46527 100644 --- a/bot/email.go +++ b/bot/email.go @@ -179,7 +179,7 @@ func (b *Bot) SendEmailReply(ctx context.Context) { meta.References = meta.References + " " + meta.MessageID b.log.Debug("send email reply: %+v", meta) email := utils.NewEmail(meta.MessageID, meta.InReplyTo, meta.References, meta.Subject, meta.From, meta.To, body, htmlBody, nil) - data := email.Compose(false, b.getBotSettings().DKIMPrivateKey()) + data := email.Compose(b.getBotSettings().DKIMPrivateKey()) queued, err := b.Sendmail(evt.ID, meta.From, meta.To, data) if queued { diff --git a/smtp/session.go b/smtp/session.go index 1e6f28c..3d656d4 100644 --- a/smtp/session.go +++ b/smtp/session.go @@ -156,7 +156,7 @@ func (s *outgoingSession) Data(r io.Reader) error { eml.HTML, files) - return s.sendmail(email.From, email.To, email.Compose(false, s.privkey)) + return s.sendmail(email.From, email.To, email.Compose(s.privkey)) } func (s *outgoingSession) Reset() {} func (s *outgoingSession) Logout() error { return nil } diff --git a/utils/email.go b/utils/email.go index 97f996c..9b3a590 100644 --- a/utils/email.go +++ b/utils/email.go @@ -10,6 +10,7 @@ import ( "time" "github.com/emersion/go-msgauth/dkim" + "gitlab.com/etke.cc/go/secgen" "maunium.net/go/mautrix/event" "maunium.net/go/mautrix/format" "maunium.net/go/mautrix/id" @@ -141,19 +142,18 @@ func (e *Email) Content(threadID id.EventID, options *ContentOptions) *event.Con } // Compose converts the email object to a string (to be used for delivery via SMTP) and possibly DKIM-signs it -func (e *Email) Compose(html bool, privkey string) string { +func (e *Email) Compose(privkey string) string { var data strings.Builder domain := strings.SplitN(e.From, "@", 2)[1] + boundaryName := "postmoogle" + secgen.Password(32) + boundary := "--" + boundaryName data.WriteString("MIME-Version: 1.0") data.WriteString("\r\n") - if html { - data.WriteString("Content-Type: text/html; charset=\"UTF-8\"") - } else { - data.WriteString("Content-Type: text/plain; charset=\"UTF-8\"") - } + data.WriteString("Content-Type: multipart/alternative; boundary=") + data.WriteString(boundaryName) data.WriteString("\r\n") data.WriteString("Content-Transfer-Encoding: 8BIT") @@ -190,14 +190,26 @@ func (e *Email) Compose(html bool, privkey string) string { data.WriteString("Subject: ") data.WriteString(e.Subject) data.WriteString("\r\n") - data.WriteString("\r\n") - if html { - data.WriteString(e.HTML) - } else { - data.WriteString(e.Text) - } + data.WriteString(boundary) + data.WriteString("\r\n") + data.WriteString("Content-Type: text/html; charset=\"UTF-8\"") + data.WriteString("\r\n") + data.WriteString(e.HTML) + data.WriteString("\r\n") + data.WriteString("\r\n") + + data.WriteString(boundary) + data.WriteString("\r\n") + data.WriteString("Content-Type: text/plain; charset=\"UTF-8\"") + data.WriteString("\r\n") + data.WriteString(e.Text) + data.WriteString("\r\n") + data.WriteString("\r\n") + + data.WriteString(boundary) + data.WriteString("--") data.WriteString("\r\n") return e.sign(domain, privkey, data)