From 50b7009603a2418820a2e7f998720720170594dc Mon Sep 17 00:00:00 2001 From: forgejo-backport-action Date: Wed, 1 May 2024 12:19:38 +0000 Subject: [PATCH] [v7.0/forgejo] Add inline attachments to comments and prevent double handling of mails (#3566) **Backport:** https://codeberg.org/forgejo/forgejo/pulls/3504 If incoming email is configured and an email is sent, inline attachments are currently not added to the comment if it has the `Content-Disposition: inline` instead of `Content-Disposition: attachment` as e.g. with Apple Mail. This adds inline attachments (`Content-Disposition: inline`) that *have a filename* as attachment to the comment. Other elements with `Content-Disposition: inline` are not attached as attachment to the comment. In addition, a check has been added to prevent mails from being processed twice. Fixes #3496 Co-authored-by: Beowulf Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/3566 Reviewed-by: Beowulf Co-authored-by: forgejo-backport-action Co-committed-by: forgejo-backport-action --- release-notes/8.0.0/fix/3504.md | 1 + services/mailer/incoming/incoming.go | 18 ++++++++++ services/mailer/incoming/incoming_test.go | 40 +++++++++++++++++++++++ 3 files changed, 59 insertions(+) create mode 100644 release-notes/8.0.0/fix/3504.md diff --git a/release-notes/8.0.0/fix/3504.md b/release-notes/8.0.0/fix/3504.md new file mode 100644 index 0000000000..8ece1557ab --- /dev/null +++ b/release-notes/8.0.0/fix/3504.md @@ -0,0 +1 @@ +Fixed that inline attachments of emails (as they occur for example with Apple Mail) are not attached to comments. diff --git a/services/mailer/incoming/incoming.go b/services/mailer/incoming/incoming.go index eade0cf271..6530b7cc60 100644 --- a/services/mailer/incoming/incoming.go +++ b/services/mailer/incoming/incoming.go @@ -219,6 +219,11 @@ loop: } err := func() error { + if isAlreadyHandled(handledSet, msg) { + log.Debug("Skipping already handled message") + return nil + } + r := msg.GetBody(section) if r == nil { return fmt.Errorf("could not get body from message: %w", err) @@ -277,6 +282,11 @@ loop: return nil } +// isAlreadyHandled tests if the message was already handled +func isAlreadyHandled(handledSet *imap.SeqSet, msg *imap.Message) bool { + return handledSet.Contains(msg.SeqNum) +} + // isAutomaticReply tests if the headers indicate an automatic reply func isAutomaticReply(env *enmime.Envelope) bool { autoSubmitted := env.GetHeader("Auto-Submitted") @@ -367,6 +377,14 @@ func getContentFromMailReader(env *enmime.Envelope) *MailContent { Content: attachment.Content, }) } + for _, inline := range env.Inlines { + if inline.FileName != "" { + attachments = append(attachments, &Attachment{ + Name: inline.FileName, + Content: inline.Content, + }) + } + } return &MailContent{ Content: reply.FromText(env.Text), diff --git a/services/mailer/incoming/incoming_test.go b/services/mailer/incoming/incoming_test.go index 5d84848e3f..f2bb7fc498 100644 --- a/services/mailer/incoming/incoming_test.go +++ b/services/mailer/incoming/incoming_test.go @@ -7,10 +7,24 @@ import ( "strings" "testing" + "github.com/emersion/go-imap" "github.com/jhillyerd/enmime" "github.com/stretchr/testify/assert" ) +func TestNotHandleTwice(t *testing.T) { + handledSet := new(imap.SeqSet) + msg := imap.NewMessage(90, []imap.FetchItem{imap.FetchBody}) + + handled := isAlreadyHandled(handledSet, msg) + assert.Equal(t, false, handled) + + handledSet.AddNum(msg.SeqNum) + + handled = isAlreadyHandled(handledSet, msg) + assert.Equal(t, true, handled) +} + func TestIsAutomaticReply(t *testing.T) { cases := []struct { Headers map[string]string @@ -95,6 +109,32 @@ func TestGetContentFromMailReader(t *testing.T) { assert.Equal(t, "attachment.txt", content.Attachments[0].Name) assert.Equal(t, []byte("attachment content"), content.Attachments[0].Content) + mailString = "Content-Type: multipart/mixed; boundary=message-boundary\r\n" + + "\r\n" + + "--message-boundary\r\n" + + "Content-Type: multipart/alternative; boundary=text-boundary\r\n" + + "\r\n" + + "--text-boundary\r\n" + + "Content-Type: text/plain\r\n" + + "Content-Disposition: inline\r\n" + + "\r\n" + + "mail content\r\n" + + "--text-boundary--\r\n" + + "--message-boundary\r\n" + + "Content-Type: text/plain\r\n" + + "Content-Disposition: inline; filename=attachment.txt\r\n" + + "\r\n" + + "attachment content\r\n" + + "--message-boundary--\r\n" + + env, err = enmime.ReadEnvelope(strings.NewReader(mailString)) + assert.NoError(t, err) + content = getContentFromMailReader(env) + assert.Equal(t, "mail content", content.Content) + assert.Len(t, content.Attachments, 1) + assert.Equal(t, "attachment.txt", content.Attachments[0].Name) + assert.Equal(t, []byte("attachment content"), content.Attachments[0].Content) + mailString = "Content-Type: multipart/mixed; boundary=message-boundary\r\n" + "\r\n" + "--message-boundary\r\n" +