diff --git a/internal/user/smtp.go b/internal/user/smtp.go index f5f6d2b8..7351c2ca 100644 --- a/internal/user/smtp.go +++ b/internal/user/smtp.go @@ -438,6 +438,10 @@ func (user *User) createAttachments( } } + // Exclude name from params since this is already provided using Filename. + delete(att.MIMEParams, "name") + delete(att.MIMEParams, "filename") + attachment, err := client.UploadAttachment(ctx, addrKR, proton.CreateAttachmentReq{ Filename: att.Name, MessageID: draftID, diff --git a/pkg/message/parser_test.go b/pkg/message/parser_test.go index 5bbce163..de28aed0 100644 --- a/pkg/message/parser_test.go +++ b/pkg/message/parser_test.go @@ -673,6 +673,40 @@ func TestParsePanic(t *testing.T) { require.Error(t, err) } +func TestParseTextPlainWithPdfttachmentCyrillic(t *testing.T) { + f := getFileReader("text_plain_pdf_attachment_cyrillic.eml") + + m, err := Parse(f) + require.NoError(t, err) + + assert.Equal(t, `"Sender" `, m.Sender.String()) + assert.Equal(t, `"Receiver" `, m.ToList[0].String()) + + assert.Equal(t, "Shake that body", string(m.RichBody)) + assert.Equal(t, "Shake that body", string(m.PlainBody)) + + require.Len(t, m.Attachments, 1) + require.Equal(t, "application/pdf", m.Attachments[0].MIMEType) + assert.Equal(t, "АБВГДЃЕЖЗЅИЈКЛЉМНЊОПРСТЌУФХЧЏЗШ.pdf", m.Attachments[0].Name) +} + +func TestParseTextPlainWithDocxAttachmentCyrillic(t *testing.T) { + f := getFileReader("text_plain_docx_attachment_cyrillic.eml") + + m, err := Parse(f) + require.NoError(t, err) + + assert.Equal(t, `"Sender" `, m.Sender.String()) + assert.Equal(t, `"Receiver" `, m.ToList[0].String()) + + assert.Equal(t, "Shake that body", string(m.RichBody)) + assert.Equal(t, "Shake that body", string(m.PlainBody)) + + require.Len(t, m.Attachments, 1) + require.Equal(t, "application/vnd.openxmlformats-officedocument.wordprocessingml.document", m.Attachments[0].MIMEType) + assert.Equal(t, "АБВГДЃЕЖЗЅИЈКЛЉМНЊОПРСТЌУФХЧЏЗШ.docx", m.Attachments[0].Name) +} + func getFileReader(filename string) io.Reader { f, err := os.Open(filepath.Join("testdata", filename)) if err != nil { diff --git a/pkg/message/testdata/text_plain_docx_attachment_cyrillic.eml b/pkg/message/testdata/text_plain_docx_attachment_cyrillic.eml new file mode 100644 index 00000000..b81cfd1e --- /dev/null +++ b/pkg/message/testdata/text_plain_docx_attachment_cyrillic.eml @@ -0,0 +1,24 @@ +Content-Type: multipart/mixed; boundary="------------nq8WTMHkJcymWO6pWfby0uY3" +To: "Receiver" +From: "Sender" +Subject: Test with cyrillic attachment + +--------------nq8WTMHkJcymWO6pWfby0uY3 +Content-Type: text/plain; charset=UTF-8; format=flowed +Content-Transfer-Encoding: 7bit + +Shake that body +--------------nq8WTMHkJcymWO6pWfby0uY3 +Content-Type: application/vnd.openxmlformats-officedocument.wordprocessingml.document; + name="=?UTF-8?B?0JDQkdCS0JPQlNCD0JXQltCX0IXQmNCI0JrQm9CJ0JzQndCK0J7Qn9Cg?= + =?UTF-8?B?0KHQotCM0KPQpNCl0KfQj9CX0KguZG9jeA==?=" +Content-Disposition: attachment; + filename*0*=UTF-8''%D0%90%D0%91%D0%92%D0%93%D0%94%D0%83%D0%95%D0%96%D0%97; + filename*1*=%D0%85%D0%98%D0%88%D0%9A%D0%9B%D0%89%D0%9C%D0%9D%D0%8A%D0%9E; + filename*2*=%D0%9F%D0%A0%D0%A1%D0%A2%D0%8C%D0%A3%D0%A4%D0%A5%D0%A7%D0%8F; + filename*3*=%D0%97%D0%A8%2E%64%6F%63%78 +Content-Transfer-Encoding: base64 + +0JDQkdCS0JPQlNCD0JXQltCX0IXQmNCI0JrQm9CJ0JzQndCK0J7Qn9Cg0KHQotCM0KPQpNCl0KfQj9CX0Kg= + +--------------nq8WTMHkJcymWO6pWfby0uY3-- diff --git a/pkg/message/testdata/text_plain_pdf_attachment_cyrillic.eml b/pkg/message/testdata/text_plain_pdf_attachment_cyrillic.eml new file mode 100644 index 00000000..e83c92b6 --- /dev/null +++ b/pkg/message/testdata/text_plain_pdf_attachment_cyrillic.eml @@ -0,0 +1,25 @@ +Content-Type: multipart/mixed; boundary="------------bYzsV6z0EdKTbltmCDZgIM15" +To: "Receiver" +From: "Sender" +Subject: Test with cyrillic attachment + +--------------bYzsV6z0EdKTbltmCDZgIM15 +Content-Transfer-Encoding: quoted-printable +Content-Type: text/plain; charset=utf-8 + +Shake that body +--------------bYzsV6z0EdKTbltmCDZgIM15 +Content-Type: application/pdf; + name="=?UTF-8?B?0JDQkdCS0JPQlNCD0JXQltCX0IXQmNCI0JrQm9CJ0JzQndCK0J7Qn9Cg?= + =?UTF-8?B?0KHQotCM0KPQpNCl0KfQj9CX0KgucGRm?=" +Content-Disposition: attachment; + filename*0*=UTF-8''%D0%90%D0%91%D0%92%D0%93%D0%94%D0%83%D0%95%D0%96%D0%97; + filename*1*=%D0%85%D0%98%D0%88%D0%9A%D0%9B%D0%89%D0%9C%D0%9D%D0%8A%D0%9E; + filename*2*=%D0%9F%D0%A0%D0%A1%D0%A2%D0%8C%D0%A3%D0%A4%D0%A5%D0%A7%D0%8F; + filename*3*=%D0%97%D0%A8%2E%70%64%66 +Content-Transfer-Encoding: base64 + +0JDQkdCS0JPQlNCD0JXQltCX0IXQmNCI0JrQm9CJ0JzQndCK0J7Qn9Cg0KHQotCM0KPQpNCl0KfQj9CX0Kg= + + +--------------bYzsV6z0EdKTbltmCDZgIM15-- diff --git a/tests/bdd_test.go b/tests/bdd_test.go index 92a73bed..1dadb870 100644 --- a/tests/bdd_test.go +++ b/tests/bdd_test.go @@ -106,6 +106,7 @@ func TestFeatures(testingT *testing.T) { ctx.Step(`^the user agent is "([^"]*)"$`, s.theUserAgentIs) ctx.Step(`^the header in the "([^"]*)" request to "([^"]*)" has "([^"]*)" set to "([^"]*)"$`, s.theHeaderInTheRequestToHasSetTo) ctx.Step(`^the body in the "([^"]*)" request to "([^"]*)" is:$`, s.theBodyInTheRequestToIs) + ctx.Step(`^the body in the "([^"]*)" response to "([^"]*)" is:$`, s.theBodyInTheResponseToIs) ctx.Step(`^the API requires bridge version at least "([^"]*)"$`, s.theAPIRequiresBridgeVersion) ctx.Step(`^the network port (\d+) is busy$`, s.networkPortIsBusy) ctx.Step(`^the network port range (\d+)-(\d+) is busy$`, s.networkPortRangeIsBusy) diff --git a/tests/environment_test.go b/tests/environment_test.go index da78767d..d80a7861 100644 --- a/tests/environment_test.go +++ b/tests/environment_test.go @@ -110,3 +110,27 @@ func (s *scenario) theBodyInTheRequestToIs(method, path string, value *godog.Doc return nil } + +func (s *scenario) theBodyInTheResponseToIs(method, path string, value *godog.DocString) error { + // We have to exclude HTTP-Overrides to avoid race condition with the creating and sending of the draft message. + call, err := s.t.getLastCallExcludingHTTPOverride(method, path) + if err != nil { + return err + } + + var body, want map[string]any + + if err := json.Unmarshal(call.ResponseBody, &body); err != nil { + return err + } + + if err := json.Unmarshal([]byte(value.Content), &want); err != nil { + return err + } + + if !IsSub(body, want) { + return fmt.Errorf("have body %v, want %v", body, want) + } + + return nil +} diff --git a/tests/features/smtp/send/attachment.feature b/tests/features/smtp/send/attachment.feature new file mode 100644 index 00000000..d8151a3c --- /dev/null +++ b/tests/features/smtp/send/attachment.feature @@ -0,0 +1,143 @@ +Feature: SMTP sending with attachment + Background: + Given there exists an account with username "[user:user1]" and password "password" + And there exists an account with username "[user:user2]" and password "password" + Then it succeeds + When bridge starts + And the user logs in with username "[user:user1]" and password "password" + And user "[user:user1]" finishes syncing + Then it succeeds + When user "[user:user1]" connects and authenticates SMTP client "1" + And user "[user:user1]" connects and authenticates IMAP client "1" + Then it succeeds + + @long-black + Scenario: Sending with cyrillic PDF attachment + When SMTP client "1" sends the following message from "[user:user1]@[domain]" to "[user:user2]@[domain]": + """ + Content-Type: multipart/mixed; boundary="------------bYzsV6z0EdKTbltmCDZgIM15" + From: Bridge Test <[user:user1]@[domain]> + To: Internal Bridge <[user:user2]@[domain]> + Subject: Test with cyrillic attachment + + --------------bYzsV6z0EdKTbltmCDZgIM15 + Content-Transfer-Encoding: quoted-printable + Content-Type: text/plain; charset=utf-8 + + Shake that body + --------------bYzsV6z0EdKTbltmCDZgIM15 + Content-Type: application/pdf; + name="=?UTF-8?B?0JDQkdCS0JPQlNCD0JXQltCX0IXQmNCI0JrQm9CJ0JzQndCK0J7Qn9Cg?= + =?UTF-8?B?0KHQotCM0KPQpNCl0KfQj9CX0KgucGRm?=" + Content-Disposition: attachment; + filename*0*=UTF-8''%D0%90%D0%91%D0%92%D0%93%D0%94%D0%83%D0%95%D0%96%D0%97; + filename*1*=%D0%85%D0%98%D0%88%D0%9A%D0%9B%D0%89%D0%9C%D0%9D%D0%8A%D0%9E; + filename*2*=%D0%9F%D0%A0%D0%A1%D0%A2%D0%8C%D0%A3%D0%A4%D0%A5%D0%A7%D0%8F; + filename*3*=%D0%97%D0%A8%2E%70%64%66 + Content-Transfer-Encoding: base64 + + 0JDQkdCS0JPQlNCD0JXQltCX0IXQmNCI0JrQm9CJ0JzQndCK0J7Qn9Cg0KHQotCM0KPQpNCl0KfQj9CX0Kg= + + --------------bYzsV6z0EdKTbltmCDZgIM15-- + + """ + Then it succeeds + Then IMAP client "1" eventually sees the following messages in "Sent": + | from | to | subject | + | [user:user1]@[domain] | [user:user2]@[domain] | Test with cyrillic attachment | + And the body in the "POST" request to "/mail/v4/messages" is: + """ + { + "Message": { + "Subject": "Test with cyrillic attachment", + "Sender": { + "Name": "Bridge Test" + }, + "ToList": [ + { + "Address": "[user:user2]@[domain]", + "Name": "Internal Bridge" + } + ], + "CCList": [], + "BCCList": [], + "MIMEType": "text/plain" + } + } + """ + And the body in the "POST" response to "/mail/v4/attachments" is: + """ + { + "Attachment":{ + "Name": "АБВГДЃЕЖЗЅИЈКЛЉМНЊОПРСТЌУФХЧЏЗШ.pdf", + "MIMEType": "application/pdf", + "Disposition": "attachment" + } + } + """ + + + @long-black + Scenario: Sending with cyrillic docx attachment + When SMTP client "1" sends the following message from "[user:user1]@[domain]" to "[user:user2]@[domain]": + """ + Content-Type: multipart/mixed; boundary="------------9xfXriG1c1v5iJlMiIMCaIWP" + From: Bridge Test <[user:user1]@[domain]> + To: Internal Bridge <[user:user2]@[domain]> + Subject: Test with cyrillic attachment + + --------------9xfXriG1c1v5iJlMiIMCaIWP + Content-Type: text/plain; charset=UTF-8; format=flowed + Content-Transfer-Encoding: 7bit + + Shake that body + --------------9xfXriG1c1v5iJlMiIMCaIWP + Content-Type: application/vnd.openxmlformats-officedocument.wordprocessingml.document; + name="=?UTF-8?B?0JDQkdCS0JPQlNCD0JXQltCX0IXQmNCI0JrQm9CJ0JzQndCK0J7Qn9Cg?= + =?UTF-8?B?0KHQotCM0KPQpNCl0KfQj9CX0KguZG9jeA==?=" + Content-Disposition: attachment; + filename*0*=UTF-8''%D0%90%D0%91%D0%92%D0%93%D0%94%D0%83%D0%95%D0%96%D0%97; + filename*1*=%D0%85%D0%98%D0%88%D0%9A%D0%9B%D0%89%D0%9C%D0%9D%D0%8A%D0%9E; + filename*2*=%D0%9F%D0%A0%D0%A1%D0%A2%D0%8C%D0%A3%D0%A4%D0%A5%D0%A7%D0%8F; + filename*3*=%D0%97%D0%A8%2E%64%6F%63%78 + Content-Transfer-Encoding: base64 + + 0JDQkdCS0JPQlNCD0JXQltCX0IXQmNCI0JrQm9CJ0JzQndCK0J7Qn9Cg0KHQotCM0KPQpNCl0KfQj9CX0Kg= + + --------------9xfXriG1c1v5iJlMiIMCaIWP-- + + """ + Then it succeeds + Then IMAP client "1" eventually sees the following messages in "Sent": + | from | to | subject | + | [user:user1]@[domain] | [user:user2]@[domain] | Test with cyrillic attachment | + And the body in the "POST" request to "/mail/v4/messages" is: + """ + { + "Message": { + "Subject": "Test with cyrillic attachment", + "Sender": { + "Name": "Bridge Test" + }, + "ToList": [ + { + "Address": "[user:user2]@[domain]", + "Name": "Internal Bridge" + } + ], + "CCList": [], + "BCCList": [], + "MIMEType": "text/plain" + } + } + """ + And the body in the "POST" response to "/mail/v4/attachments" is: + """ + { + "Attachment":{ + "Name": "АБВГДЃЕЖЗЅИЈКЛЉМНЊОПРСТЌУФХЧЏЗШ.docx", + "MIMEType": "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + "Disposition": "attachment" + } + } + """ \ No newline at end of file