feat: parse inline attachments for incoming mail (#7136)
- Some email clients send inline attachments using the `multipart/related` Mime-Type and enmime collects these in the `Envelope.OtherParts` list; until now only Envelope.Attachments and Envelope.Inline were considered while parsing incoming mail. - As some email clients send attachments without filename, especially in the multipart/related case, this PR implements `constructFilename`, which guesses the filename based on the ContentType. How the issue was disovered: I implemented an otherwise unrelated application written in go that parses emails with enmime just like Forgejo does. I noticed that in a few occasions that system would fail to detect all attachments. Investigating this issue led me to realize the above described issue. After implementing a fix for that application, I looked through the Forgejo email parsing code and discovered the same problem. Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/7136 Reviewed-by: Gusted <gusted@noreply.codeberg.org> Co-authored-by: christopher-besch <mail@chris-besch.com> Co-committed-by: christopher-besch <mail@chris-besch.com>
This commit is contained in:
parent
b2d01fdde6
commit
3de904c963
|
@ -7,8 +7,10 @@ import (
|
|||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"mime"
|
||||
net_mail "net/mail"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
@ -374,25 +376,56 @@ type Attachment struct {
|
|||
// getContentFromMailReader grabs the plain content and the attachments from the mail.
|
||||
// A potential reply/signature gets stripped from the content.
|
||||
func getContentFromMailReader(env *enmime.Envelope) *MailContent {
|
||||
// get attachments
|
||||
attachments := make([]*Attachment, 0, len(env.Attachments))
|
||||
for _, attachment := range env.Attachments {
|
||||
attachments = append(attachments, &Attachment{
|
||||
Name: attachment.FileName,
|
||||
Name: constructFilename(attachment),
|
||||
Content: attachment.Content,
|
||||
})
|
||||
}
|
||||
// get inlines
|
||||
inlineAttachments := make([]*Attachment, 0, len(env.Inlines))
|
||||
for _, inline := range env.Inlines {
|
||||
if inline.FileName != "" && inline.ContentType != "text/plain" {
|
||||
inlineAttachments = append(inlineAttachments, &Attachment{
|
||||
Name: inline.FileName,
|
||||
Name: constructFilename(inline),
|
||||
Content: inline.Content,
|
||||
})
|
||||
}
|
||||
}
|
||||
// get other parts (mostly multipart/related files, these are for example embedded images in an html mail)
|
||||
otherParts := make([]*Attachment, 0, len(env.Inlines))
|
||||
for _, otherPart := range env.OtherParts {
|
||||
otherParts = append(otherParts, &Attachment{
|
||||
Name: constructFilename(otherPart),
|
||||
Content: otherPart.Content,
|
||||
})
|
||||
}
|
||||
|
||||
return &MailContent{
|
||||
Content: reply.FromText(env.Text),
|
||||
Attachments: append(attachments, inlineAttachments...),
|
||||
Attachments: slices.Concat(attachments, inlineAttachments, otherParts),
|
||||
}
|
||||
}
|
||||
|
||||
// constructFilename interprets the mime part as an (inline) attachment and returns its filename
|
||||
// If no filename is given it guesses a sensible filename for it based on the filetype.
|
||||
func constructFilename(part *enmime.Part) string {
|
||||
if strings.TrimSpace(part.FileName) != "" {
|
||||
return part.FileName
|
||||
}
|
||||
|
||||
filenameWOExtension := "unnamed_file"
|
||||
if strings.TrimSpace(part.ContentID) != "" {
|
||||
filenameWOExtension = part.ContentID
|
||||
}
|
||||
|
||||
fileExtension := ".unknown"
|
||||
mimeExtensions, err := mime.ExtensionsByType(part.ContentType)
|
||||
if err == nil && len(mimeExtensions) != 0 {
|
||||
// just use the first one we find
|
||||
fileExtension = mimeExtensions[0]
|
||||
}
|
||||
return filenameWOExtension + fileExtension
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
package incoming
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
|
@ -194,4 +195,191 @@ func TestGetContentFromMailReader(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
assert.Equal(t, "mail content without signature", content.Content)
|
||||
assert.Empty(t, content.Attachments)
|
||||
|
||||
// Some versions of Outlook send inline attachments like this, inside a multipart/related part.
|
||||
// the attached image is from: https://openmoji.org/library/emoji-1F684
|
||||
mailString = "Content-Type: multipart/related; boundary=\"=_related boundary=\"\r\n" +
|
||||
"\r\n" +
|
||||
"This text is for clients unable to decode multipart/related with multipart/alternative.\r\n" +
|
||||
"\r\n" +
|
||||
"--=_related boundary=\r\n" +
|
||||
"Content-Type: multipart/alternative; boundary=\"=_alternative boundary=\"\r\n" +
|
||||
"\r\n" +
|
||||
"\r\n" +
|
||||
"\r\n" +
|
||||
"--=_alternative boundary=\r\n" +
|
||||
"Content-Type: text/plain\r\n" +
|
||||
"\r\n" +
|
||||
"This is the plaintext.\r\n" +
|
||||
"\r\n" +
|
||||
"--=_alternative boundary=\r\n" +
|
||||
"Content-Type: text/html\r\n" +
|
||||
"\r\n" +
|
||||
"<p>This is a mail with multipart/related. Here is an image sent with a filename.</p>\r\n" +
|
||||
"<img src=cid:_1_2845>\r\n" +
|
||||
"\r\n" +
|
||||
"--=_alternative boundary=--\r\n" +
|
||||
"\r\n" +
|
||||
"--=_related boundary=\r\n" +
|
||||
"Content-Transfer-Encoding: base64\r\n" +
|
||||
"Content-Type: image/png;\r\n" +
|
||||
" name=\"image001.png\"\r\n" +
|
||||
"Content-ID: <_1_2845>\r\n" +
|
||||
"\r\n" +
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAEAAAAAiCAYAAADvVd+PAAAFLUlEQVRo3t2ZX0iTXxjHP3u35qvT\r\n" +
|
||||
"6ZzhzKFuzPQq9WKQZS6FvLQf3Wh30ViBQXnViC5+LVKEiC6DjMQgCCy6NChoIKwghhcR1bJ5s5Ei\r\n" +
|
||||
"LmtNs/05XYT7Vercaps/94Xn4uU95znvOc/3+XdehRBCsM1YXl7G6/Xi8Xh49uwZMzMzhEIhFhcX\r\n" +
|
||||
"+fbtW87WbW1tRbVdmxZC8PTpU8bGxrh//z5fv37dcJxGo2HXrl1ZWVOhUPzybDAYUOSbAYlEgjt3\r\n" +
|
||||
"7nD58mVmZ2cBkCSJ1tZWDhw4wP79+2lpaUGv16PX61Gr1Tm3RN7w/Plz0d7eLgABCKPRKJxOp3j/\r\n" +
|
||||
"/r3YLuTlAD5+/ChOnDiR3HhdXZ24e/euiMfjYruRcxe4evUqV65c4fPnz6hUKrq7uzl06NA6v157\r\n" +
|
||||
"19bWlrbueDzOq1evmJ6eJhQKZRww9+3blzsXWFpaEqdOnUpaPV2ZmJjYUveLFy+Ew+EQFRUVGev/\r\n" +
|
||||
"WTQaTW4Y8OjRIxwOB4FAAEmS0Gq1lJWVpZwTjUaZm5vDZrPhdrs3HOP3+3E6nTx48IC1zy4uLqas\r\n" +
|
||||
"rAy1Wr0uym8FnU6X3TT46dMnzp8/z82bNwHQarU0NTVRUlKScl44HMbn8wFQU1Oz7n0sFuP69etc\r\n" +
|
||||
"unSJ5eVllEole/bswWAwbKk7FSRJyl4a/NnqSqWS+vp6jEZjSqskEglmZ2cJBoMIIbBYLExNTWEw\r\n" +
|
||||
"GJJjvF4vDoeD6elpAKqrqzGbzVlJj5Ik/T0D/tTqS0tL+Hw+VlZWUKlUDAwMMDQ0RGlpKQArKyu4\r\n" +
|
||||
"XC6uXbtGLBZDlmUaGxuprKzMajGmyrfVY7EYfr+fDx8+ANDS0sLo6ChWqzU5xu12c/r0aXw+HwqF\r\n" +
|
||||
"gtraWkwmE0qlMutZSpVPq8/NzeH3+4lGo5SUlOByuRgcHESl+u8zLly4wMjICAClpaU0NTUlWZEL\r\n" +
|
||||
"ZBwDfo/wDQ0NKa0ej8dZWFggEAgQiUQA6Onp4caNG5jN5l/GTk1N0dnZmTab8sqA+fl5jh07hsfj\r\n" +
|
||||
"AUCWZXbv3g1AIBBYR/NoNEokEuHLly8kEgkATCYTQ0NDHD9+fFOGrKW2jfRmGxqNJr1CaHJyUuj1\r\n" +
|
||||
"+j8qNiRJEp2dneL27dtidXU15TrhcFhYLJa/Km4ykeLi4tSF0O++Xl9fz5EjR9Dr9SlPtry8nKqq\r\n" +
|
||||
"KsxmM1arFa1Wm7ZVQqEQDx8+5N27dznvTG022+YMmJycFEajUQBClmUxMjIiYrGYKDSwVQ3f3t4u\r\n" +
|
||||
"3rx5k1LJy5cvhd1uF83NzaKoqChvFP5b6e3t/fUAMrX64uKiOHnypFAoFDtm0z9Ll14nVACRSIQz\r\n" +
|
||||
"Z84wPj4OwMGDBxkbG6OxsXFT/7l37x6Dg4PMz89TJEn0VVXwT2U5dUVq1DlOXdmCsrwcVTQapaen\r\n" +
|
||||
"B4/HgyzLDA8Pc+7cOSRJ2nTixYsXGR4eBsBaWsK/xmrqitTsSIyOjgpAmEwm8fbt27QCx969e3ck\r\n" +
|
||||
"5TdyAfr6+gQgxsfH046ct27dEjqdbscfQG9vr1CtNRiZVMR2ux273U4hQOru7gbA5XLh9Xr5H/wn\r\n" +
|
||||
"yS9WV1dFR0dHQfh0ptLR0fGjDgiHw8LpdIqGhoYdm9P/RCwWS3qXok+ePOHs2bO8fv06eZ3c39+X\r\n" +
|
||||
"7AZ3MlK2wzMzMwwMDPD48WMAamtr6e/vo7m5uWBCwKYMWFhYoK2tjWAwiEaj4ejRXmw2W8oCqaAO\r\n" +
|
||||
"wO1209XVhSzLHD5s+3F5UGAwGo2bt8OhUEjU1NQUdBDc8s9QMBhkYmIieVVVaLDZbHwHmmIQk3rD\r\n" +
|
||||
"exgAAAAASUVORK5CYII=\r\n" +
|
||||
"\r\n" +
|
||||
"--=_related boundary=--\r\n" +
|
||||
"\r\n"
|
||||
|
||||
env, err = enmime.ReadEnvelope(strings.NewReader(mailString))
|
||||
require.NoError(t, err)
|
||||
content = getContentFromMailReader(env)
|
||||
assert.Equal(t, "This is the plaintext.", content.Content)
|
||||
assert.Len(t, content.Attachments, 1)
|
||||
assert.Equal(t, "image001.png", content.Attachments[0].Name)
|
||||
expectedAttachment, err := base64.StdEncoding.DecodeString(
|
||||
"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\r\n" +
|
||||
"exgAAAAASUVORK5CYII=\r\n")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, expectedAttachment, content.Attachments[0].Content)
|
||||
|
||||
// HCL Notes inlines attachments like this: without a filename.
|
||||
// the attached image is from: https://openmoji.org/library/emoji-1F684
|
||||
mailString = "Content-Type: multipart/related; boundary=\"=_related boundary=\"\r\n" +
|
||||
"\r\n" +
|
||||
"This text is for clients unable to decode multipart/related with multipart/alternative.\r\n" +
|
||||
"\r\n" +
|
||||
"--=_related boundary=\r\n" +
|
||||
"Content-Type: multipart/alternative; boundary=\"=_alternative boundary=\"\r\n" +
|
||||
"\r\n" +
|
||||
"\r\n" +
|
||||
"\r\n" +
|
||||
"--=_alternative boundary=\r\n" +
|
||||
"Content-Type: text/plain\r\n" +
|
||||
"\r\n" +
|
||||
"This is the plaintext.\r\n" +
|
||||
"\r\n" +
|
||||
"--=_alternative boundary=\r\n" +
|
||||
"Content-Type: text/html\r\n" +
|
||||
"\r\n" +
|
||||
"<p>This is a mail with multipart/related. Here is an image sent without a filename.</p>\r\n" +
|
||||
"<img src=cid:_1_2845>\r\n" +
|
||||
"\r\n" +
|
||||
"--=_alternative boundary=--\r\n" +
|
||||
"\r\n" +
|
||||
"--=_related boundary=\r\n" +
|
||||
"Content-Transfer-Encoding: base64\r\n" +
|
||||
"Content-Type: image/png\r\n" +
|
||||
"Content-ID: <_1_2845>\r\n" +
|
||||
"\r\n" +
|
||||
"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\r\n" +
|
||||
"exgAAAAASUVORK5CYII=\r\n" +
|
||||
"\r\n" +
|
||||
"--=_related boundary=--\r\n" +
|
||||
"\r\n"
|
||||
|
||||
env, err = enmime.ReadEnvelope(strings.NewReader(mailString))
|
||||
require.NoError(t, err)
|
||||
content = getContentFromMailReader(env)
|
||||
assert.Equal(t, "This is the plaintext.", content.Content)
|
||||
assert.Len(t, content.Attachments, 1)
|
||||
assert.Equal(t, "_1_2845.png", content.Attachments[0].Name)
|
||||
expectedAttachment, err = base64.StdEncoding.DecodeString(
|
||||
"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\r\n" +
|
||||
"exgAAAAASUVORK5CYII=\r\n")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, expectedAttachment, content.Attachments[0].Content)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue