fix(GODT-3087): Exclude attachment content-disposition part when determining...

This commit is contained in:
Romain Le Jeune 2023-11-03 08:55:01 +00:00
parent 96773f3225
commit 1c385d5c9b
7 changed files with 321 additions and 8 deletions

View File

@ -147,7 +147,7 @@ func parse(p *parser.Parser, allowInvalidAddressLists bool) (Message, error) {
m.PlainBody = Body(plainBody)
m.MIMEBody = MIMEBody(mimeBody)
mimeType, err := determineMIMEType(p)
mimeType, err := determineBodyMIMEType(p)
if err != nil {
return Message{}, errors.Wrap(err, "failed to get mime type")
}
@ -313,7 +313,7 @@ func collectBodyParts(p *parser.Parser, preferredContentType string) (parser.Par
return bestChoice(childParts, preferredContentType), nil
}).
RegisterRule("text/plain", func(p *parser.Part, visit parser.Visit) (interface{}, error) {
disp, _, err := p.Header.ContentDisposition()
disp, _, err := p.ContentDisposition()
if err != nil {
disp = ""
}
@ -325,7 +325,7 @@ func collectBodyParts(p *parser.Parser, preferredContentType string) (parser.Par
return parser.Parts{p}, nil
}).
RegisterRule("text/html", func(p *parser.Part, visit parser.Visit) (interface{}, error) {
disp, _, err := p.Header.ContentDisposition()
disp, _, err := p.ContentDisposition()
if err != nil {
disp = ""
}
@ -405,7 +405,7 @@ func allPartsHaveContentType(parts parser.Parts, contentType string) bool {
return true
}
func determineMIMEType(p *parser.Parser) (string, error) {
func determineBodyMIMEType(p *parser.Parser) (string, error) {
var isHTML bool
w := p.NewWalker().
@ -414,7 +414,7 @@ func determineMIMEType(p *parser.Parser) (string, error) {
return
})
if err := w.Walk(); err != nil {
if err := w.WalkSkipAttachment(); err != nil {
return "", err
}

View File

@ -32,6 +32,10 @@ func (h *handler) matchPart(p *Part) bool {
return h.matchType(p) || h.matchDisp(p)
}
func (h *handler) matchPartSkipAttachment(p *Part) bool {
return !p.isAttachment() && h.matchPart(p)
}
func (h *handler) matchType(p *Part) bool {
if h.typeRegExp == nil {
return false
@ -50,7 +54,7 @@ func (h *handler) matchDisp(p *Part) bool {
return false
}
disp, _, err := p.Header.ContentDisposition()
disp, _, err := p.ContentDisposition()
if err != nil {
disp = ""
}

View File

@ -54,7 +54,7 @@ func (p *Part) ContentType() (string, map[string]string, error) {
}
func (p *Part) ContentDisposition() (string, map[string]string, error) {
return p.Header.ContentDisposition()
return pmmime.ParseMediaType(p.Header.Get("Content-Disposition"))
}
func (p *Part) HasContentID() bool {
@ -209,6 +209,14 @@ func (p *Part) isMultipartMixedOrRelated() bool {
return t == "multipart/mixed" || t == "multipart/related"
}
func (p *Part) isAttachment() bool {
disp, _, err := p.ContentDisposition()
if err != nil {
disp = ""
}
return disp == "attachment"
}
func getContentHeaders(header message.Header) message.Header {
var res message.Header

View File

@ -0,0 +1,86 @@
Content-Type: multipart/mixed; boundary="------------MQ01Z9UM8OaR9z39TvzDfdIq"
Subject: Fwd: Reply to this message, it has various attachments.
References: <something@protonmail.ch>
To: <[user:user2]@[domain]>
From: <[user:user]@[domain]>
In-Reply-To: <something@protonmail.ch>
X-Forwarded-Message-Id: <something@protonmail.ch>
This is a multi-part message in MIME format.
--------------MQ01Z9UM8OaR9z39TvzDfdIq
Content-Type: text/plain; charset=UTF-8; format=flowed
Content-Transfer-Encoding: 7bit
Forwarding a message with various attachments in it!
-------- Forwarded Message --------
Subject: Reply to this message, it has various attachments.
Date: Thu, 26 Oct 2023 10:41:55 +0000
From: Gjorgji Testing <gorgitesting@protonmail.com>
Reply-To: Gjorgji Testing <gorgitesting@protonmail.com>
To: Gjorgji Test v3 <gorgitesting3@protonmail.com>
For real!
*Gjorgji Testing
TesASID <https://www.youtube.com/watch?v=MifXUbrjYr8>
*
--------------MQ01Z9UM8OaR9z39TvzDfdIq
Content-Type: text/html; charset=UTF-8; name="index.html"
Content-Disposition: attachment; filename="index.html"
Content-Transfer-Encoding: base64
IDwhRE9DVFlQRSBodG1sPg0KPGh0bWw+DQo8aGVhZD4NCjx0aXRsZT5QYWdlIFRpdGxlPC90
aXRsZT4NCjwvaGVhZD4NCjxib2R5Pg0KDQo8aDE+TXkgRmlyc3QgSGVhZGluZzwvaDE+DQo8
cD5NeSBmaXJzdCBwYXJhZ3JhcGguPC9wPg0KDQo8L2JvZHk+DQo8L2h0bWw+IA==
--------------MQ01Z9UM8OaR9z39TvzDfdIq
Content-Type: text/xml; charset=UTF-8; name="testxml.xml"
Content-Disposition: attachment; filename="testxml.xml"
Content-Transfer-Encoding: base64
PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPCFET0NUWVBFIHN1aXRl
IFNZU1RFTSAiaHR0cDovL3Rlc3RuZy5vcmcvdGVzdG5nLTEuMC5kdGQiID4KCjxzdWl0ZSBu
YW1lPSJBZmZpbGlhdGUgTmV0d29ya3MiPgoKICAgIDx0ZXN0IG5hbWU9IkFmZmlsaWF0ZSBO
ZXR3b3JrcyIgZW5hYmxlZD0idHJ1ZSI+CiAgICAgICAgPGNsYXNzZXM+CiAgICAgICAgICAg
IDxjbGFzcyBuYW1lPSJjb20uY2xpY2tvdXQuYXBpdGVzdGluZy5hZmZOZXR3b3Jrcy5Bd2lu
VUtUZXN0Ii8+CiAgICAgICAgPC9jbGFzc2VzPgogICAgPC90ZXN0PgoKPC9zdWl0ZT4=
--------------MQ01Z9UM8OaR9z39TvzDfdIq
Content-Type: application/pdf; name="test.pdf"
Content-Disposition: attachment; filename="test.pdf"
Content-Transfer-Encoding: base64
JVBERi0xLjUKJeLjz9MKNyAwIG9iago8PAovVHlwZSAvRm9udERlc2NyaXB0b3IKL0ZvbnRO
MjM0NAolJUVPRgo=
--------------MQ01Z9UM8OaR9z39TvzDfdIq
Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;
name="test.xlsx"
Content-Disposition: attachment; filename="test.xlsx"
Content-Transfer-Encoding: base64
UEsDBBQABgAIAAAAIQBi7p1oXgEAAJAEAAATAAgCW0NvbnRlbnRfVHlwZXNdLnhtbCCiBAIo
UQIAABEAAAAAAAAAAAAAAAAARBcAAGRvY1Byb3BzL2NvcmUueG1sUEsBAi0AFAAGAAgAAAAh
AGFJCRCJAQAAEQMAABAAAAAAAAAAAAAAAAAAvBkAAGRvY1Byb3BzL2FwcC54bWxQSwUGAAAA
AAoACgCAAgAAexwAAAAA
--------------MQ01Z9UM8OaR9z39TvzDfdIq
Content-Type: application/vnd.openxmlformats-officedocument.wordprocessingml.document;
name="test.docx"
Content-Disposition: attachment; filename="test.docx"
Content-Transfer-Encoding: base64
UEsDBBQABgAIAAAAIQDfpNJsWgEAACAFAAATAAgCW0NvbnRlbnRfVHlwZXNdLnhtbCCiBAIo
cHAueG1sUEsBAi0AFAAGAAgAAAAhABA0tG9uAQAA4QIAABEAAAAAAAAAAAAAAAAA2xsAAGRv
Y1Byb3BzL2NvcmUueG1sUEsBAi0AFAAGAAgAAAAhAJ/mlBIqCwAAU3AAAA8AAAAAAAAAAAAA
AAAAgB4AAHdvcmQvc3R5bGVzLnhtbFBLBQYAAAAACwALAMECAADXKQAAAAA=
--------------MQ01Z9UM8OaR9z39TvzDfdIq
Content-Type: text/plain; charset=UTF-8; name="text file.txt"
Content-Disposition: attachment; filename="text file.txt"
Content-Transfer-Encoding: base64
dGV4dCBmaWxl
--------------MQ01Z9UM8OaR9z39TvzDfdIq--

View File

@ -33,9 +33,12 @@ func newWalker(root *Part) *Walker {
}
}
func (w *Walker) Walk() (err error) {
func (w *Walker) Walk() error {
return w.walkOverPart(w.root)
}
func (w *Walker) WalkSkipAttachment() error {
return w.walkOverPartSkipAttachment(w.root)
}
func (w *Walker) walkOverPart(p *Part) error {
if err := w.getHandlerFunc(p)(p); err != nil {
@ -51,6 +54,20 @@ func (w *Walker) walkOverPart(p *Part) error {
return nil
}
func (w *Walker) walkOverPartSkipAttachment(p *Part) error {
if err := w.getHandlerFuncSkipAttachment(p)(p); err != nil {
return err
}
for _, child := range p.children {
if err := w.walkOverPartSkipAttachment(child); err != nil {
return err
}
}
return nil
}
// RegisterDefaultHandler registers a handler that will be called on every part
// that doesn't match a registered content type/disposition handler.
func (w *Walker) RegisterDefaultHandler(fn HandlerFunc) *Walker {
@ -91,3 +108,13 @@ func (w *Walker) getHandlerFunc(p *Part) HandlerFunc {
return w.defaultHandler
}
func (w *Walker) getHandlerFuncSkipAttachment(p *Part) HandlerFunc {
for _, handler := range w.handlers {
if handler.matchPartSkipAttachment(p) {
return handler.fn
}
}
return w.defaultHandler
}

View File

@ -60,6 +60,27 @@ func TestWalkerTypeHandler(t *testing.T) {
}, html)
}
func TestWalkerTypeHandler_excludingAttachment(t *testing.T) {
p := newTestParser(t, "forwarding_html_attachment.eml")
html := [][]byte{}
plain := [][]byte{}
walker := p.NewWalker().
RegisterContentTypeHandler("text/html", func(p *Part) (err error) {
html = append(html, p.Body)
return
}).
RegisterContentTypeHandler("text/plain", func(p *Part) (err error) {
plain = append(plain, p.Body)
return
})
assert.NoError(t, walker.WalkSkipAttachment())
assert.Equal(t, 1, len(plain))
assert.Equal(t, 0, len(html))
}
func TestWalkerDispositionHandler(t *testing.T) {
p := newTestParser(t, "text_html_octet_attachment.eml")

View File

@ -480,3 +480,170 @@ Feature: SMTP sending of plain messages
}
}
"""
Scenario: Forward a message containing various attachments
When SMTP client "1" sends the following message from "[user:user]@[domain]" to "[user:user2]@[domain]":
"""
Content-Type: multipart/mixed; boundary="------------MQ01Z9UM8OaR9z39TvzDfdIq"
Subject: Fwd: Reply to this message, it has various attachments.
References: <something@protonmail.ch>
To: <[user:user2]@[domain]>
From: <[user:user]@[domain]>
In-Reply-To: <something@protonmail.ch>
X-Forwarded-Message-Id: <something@protonmail.ch>
This is a multi-part message in MIME format.
--------------MQ01Z9UM8OaR9z39TvzDfdIq
Content-Type: text/plain; charset=UTF-8; format=flowed
Content-Transfer-Encoding: 7bit
Forwarding a message with various attachments in it!
-------- Forwarded Message --------
Subject: Reply to this message, it has various attachments.
Date: Thu, 26 Oct 2023 10:41:55 +0000
From: Gjorgji Testing <gorgitesting@protonmail.com>
Reply-To: Gjorgji Testing <gorgitesting@protonmail.com>
To: Gjorgji Test v3 <gorgitesting3@protonmail.com>
For real!
*Gjorgji Testing
TesASID <https://www.youtube.com/watch?v=MifXUbrjYr8>
*
--------------MQ01Z9UM8OaR9z39TvzDfdIq
Content-Type: text/html; charset=UTF-8; name="index.html"
Content-Disposition: attachment; filename="index.html"
Content-Transfer-Encoding: base64
IDwhRE9DVFlQRSBodG1sPg0KPGh0bWw+DQo8aGVhZD4NCjx0aXRsZT5QYWdlIFRpdGxlPC90
aXRsZT4NCjwvaGVhZD4NCjxib2R5Pg0KDQo8aDE+TXkgRmlyc3QgSGVhZGluZzwvaDE+DQo8
cD5NeSBmaXJzdCBwYXJhZ3JhcGguPC9wPg0KDQo8L2JvZHk+DQo8L2h0bWw+IA==
--------------MQ01Z9UM8OaR9z39TvzDfdIq
Content-Type: text/xml; charset=UTF-8; name="testxml.xml"
Content-Disposition: attachment; filename="testxml.xml"
Content-Transfer-Encoding: base64
PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPCFET0NUWVBFIHN1aXRl
IFNZU1RFTSAiaHR0cDovL3Rlc3RuZy5vcmcvdGVzdG5nLTEuMC5kdGQiID4KCjxzdWl0ZSBu
YW1lPSJBZmZpbGlhdGUgTmV0d29ya3MiPgoKICAgIDx0ZXN0IG5hbWU9IkFmZmlsaWF0ZSBO
ZXR3b3JrcyIgZW5hYmxlZD0idHJ1ZSI+CiAgICAgICAgPGNsYXNzZXM+CiAgICAgICAgICAg
IDxjbGFzcyBuYW1lPSJjb20uY2xpY2tvdXQuYXBpdGVzdGluZy5hZmZOZXR3b3Jrcy5Bd2lu
VUtUZXN0Ii8+CiAgICAgICAgPC9jbGFzc2VzPgogICAgPC90ZXN0PgoKPC9zdWl0ZT4=
--------------MQ01Z9UM8OaR9z39TvzDfdIq
Content-Type: application/pdf; name="test.pdf"
Content-Disposition: attachment; filename="test.pdf"
Content-Transfer-Encoding: base64
JVBERi0xLjUKJeLjz9MKNyAwIG9iago8PAovVHlwZSAvRm9udERlc2NyaXB0b3IKL0ZvbnRO
MjM0NAolJUVPRgo=
--------------MQ01Z9UM8OaR9z39TvzDfdIq
Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;
name="test.xlsx"
Content-Disposition: attachment; filename="test.xlsx"
Content-Transfer-Encoding: base64
UEsDBBQABgAIAAAAIQBi7p1oXgEAAJAEAAATAAgCW0NvbnRlbnRfVHlwZXNdLnhtbCCiBAIo
UQIAABEAAAAAAAAAAAAAAAAARBcAAGRvY1Byb3BzL2NvcmUueG1sUEsBAi0AFAAGAAgAAAAh
AGFJCRCJAQAAEQMAABAAAAAAAAAAAAAAAAAAvBkAAGRvY1Byb3BzL2FwcC54bWxQSwUGAAAA
AAoACgCAAgAAexwAAAAA
--------------MQ01Z9UM8OaR9z39TvzDfdIq
Content-Type: application/vnd.openxmlformats-officedocument.wordprocessingml.document;
name="test.docx"
Content-Disposition: attachment; filename="test.docx"
Content-Transfer-Encoding: base64
UEsDBBQABgAIAAAAIQDfpNJsWgEAACAFAAATAAgCW0NvbnRlbnRfVHlwZXNdLnhtbCCiBAIo
cHAueG1sUEsBAi0AFAAGAAgAAAAhABA0tG9uAQAA4QIAABEAAAAAAAAAAAAAAAAA2xsAAGRv
Y1Byb3BzL2NvcmUueG1sUEsBAi0AFAAGAAgAAAAhAJ/mlBIqCwAAU3AAAA8AAAAAAAAAAAAA
AAAAgB4AAHdvcmQvc3R5bGVzLnhtbFBLBQYAAAAACwALAMECAADXKQAAAAA=
--------------MQ01Z9UM8OaR9z39TvzDfdIq
Content-Type: text/plain; charset=UTF-8; name="text file.txt"
Content-Disposition: attachment; filename="text file.txt"
Content-Transfer-Encoding: base64
dGV4dCBmaWxl
--------------MQ01Z9UM8OaR9z39TvzDfdIq--
"""
Then it succeeds
When user "[user:user]" connects and authenticates IMAP client "1"
Then IMAP client "1" eventually sees the following messages in "Sent":
| from | to | subject | X-Forwarded-Message-Id |
| [user:user]@[domain] | [user:user2]@[domain] | Fwd: Reply to this message, it has various attachments. | something@protonmail.ch |
And IMAP client "1" eventually sees 1 messages in "Sent"
When the user logs in with username "[user:user2]" and password "password"
And user "[user:user2]" connects and authenticates IMAP client "2"
And user "[user:user2]" finishes syncing
And it succeeds
Then IMAP client "2" eventually sees the following messages in "Inbox":
| from | to | subject | X-Forwarded-Message-Id |
| [user:user]@[domain] | [user:user2]@[domain] | Fwd: Reply to this message, it has various attachments. | something@protonmail.ch |
Then IMAP client "2" eventually sees the following message in "Inbox" with this structure:
"""
{
"from": "[user:user]@[domain]",
"to": "[user:user2]@[domain]",
"subject": "Fwd: Reply to this message, it has various attachments.",
"content": {
"content-type": "multipart/mixed",
"sections":[
{
"content-type": "text/plain",
"content-type-charset": "utf-8",
"transfer-encoding": "quoted-printable",
"body-is": "Forwarding a message with various attachments in it!\r\n\r\n\r\n\r\n-------- Forwarded Message --------\r\nSubject: \tReply to this message, it has various attachments.\r\nDate: \tThu, 26 Oct 2023 10:41:55 +0000\r\nFrom: \tGjorgji Testing <gorgitesting@protonmail.com>\r\nReply-To: \tGjorgji Testing <gorgitesting@protonmail.com>\r\nTo: \tGjorgji Test v3 <gorgitesting3@protonmail.com>\r\n\r\n\r\n\r\n\r\nFor real!\r\n\r\n*Gjorgji Testing\r\nTesASID <https://www.youtube.com/watch?v=3DMifXUbrjYr8>\r\n*"
},
{
"content-type": "text/html",
"content-type-name": "index.html",
"content-disposition": "attachment",
"content-disposition-filename": "index.html",
"transfer-encoding": "base64"
},
{
"content-type": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"content-type-name": "test.docx",
"content-disposition": "attachment",
"content-disposition-filename": "test.docx",
"transfer-encoding": "base64"
},
{
"content-type": "application/pdf",
"content-type-name": "test.pdf",
"content-disposition": "attachment",
"content-disposition-filename": "test.pdf",
"transfer-encoding": "base64"
},
{
"content-type": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"content-type-name": "test.xlsx",
"content-disposition": "attachment",
"content-disposition-filename": "test.xlsx",
"transfer-encoding": "base64"
},
{
"content-type": "text/xml",
"content-type-name": "testxml.xml",
"content-disposition": "attachment",
"content-disposition-filename": "testxml.xml",
"transfer-encoding": "base64"
},
{
"content-type": "text/plain",
"content-type-name": "text file.txt",
"content-disposition": "attachment",
"content-disposition-filename": "text file.txt",
"transfer-encoding": "base64",
"body-is": "dGV4dCBmaWxl"
}
]
}
}
"""