From 5b8520b4e0687a1eda5e96f269a1a3bb08bff98b Mon Sep 17 00:00:00 2001 From: Guillaume Taquet Gasperini Date: Mon, 8 May 2023 10:31:06 +0200 Subject: [PATCH] Add quoted-printable decoding to smtp server Some e-mails are sent using quoted-printable encoding [0], resulting in notifications with weird characters. This commit adds support for this encoding, resulting in the following: **Before** ``` A =3D=3D=3D=3D=3D B =3D=3D=3D=3D=3D C ``` **After** ``` A ===== B ===== C ``` [0] https://www.rfc-editor.org/rfc/rfc2045.html --- server/smtp_server.go | 3 ++ server/smtp_server_test.go | 77 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+) diff --git a/server/smtp_server.go b/server/smtp_server.go index 16d97328..b9fbe6ee 100644 --- a/server/smtp_server.go +++ b/server/smtp_server.go @@ -9,6 +9,7 @@ import ( "io" "mime" "mime/multipart" + "mime/quotedprintable" "net" "net/http" "net/http/httptest" @@ -265,6 +266,8 @@ func readMultipartMailBody(body io.Reader, params map[string]string, depth int) func readPlainTextMailBody(reader io.Reader, transferEncoding string) (string, error) { if strings.ToLower(transferEncoding) == "base64" { reader = base64.NewDecoder(base64.StdEncoding, reader) + } else if strings.ToLower(transferEncoding) == "quoted-printable" { + reader = quotedprintable.NewReader(reader) } body, err := io.ReadAll(reader) if err != nil { diff --git a/server/smtp_server_test.go b/server/smtp_server_test.go index 49085d79..0743aa9e 100644 --- a/server/smtp_server_test.go +++ b/server/smtp_server_test.go @@ -303,6 +303,39 @@ BBBBBBBBBBBBBBBBBBBBBBBBB` writeAndReadUntilLine(t, email, c, scanner, "250 2.0.0 OK: queued") } +func TestSmtpBackend_Plaintext_QuotedPrintable(t *testing.T) { + email := `EHLO example.com +MAIL FROM: phil@example.com +RCPT TO: mytopic@ntfy.sh +DATA +Date: Tue, 28 Dec 2021 00:30:10 +0100 +Message-ID: +Subject: and one more +From: Phil +To: mytopic@ntfy.sh +Content-Type: text/plain; charset="UTF-8" +Content-Transfer-Encoding: quoted-printable + +what's +=C3=A0&=C3=A9"'(-=C3=A8_=C3=A7=C3=A0) +=3D=3D=3D=3D=3D +up +. +` + s, c, conf, scanner := newTestSMTPServer(t, func(w http.ResponseWriter, r *http.Request) { + require.Equal(t, "/mytopic", r.URL.Path) + require.Equal(t, "and one more", r.Header.Get("Title")) + require.Equal(t, `what's +à&é"'(-è_çà) +===== +up`, readAll(t, r.Body)) + }) + conf.SMTPServerAddrPrefix = "" + defer s.Close() + defer c.Close() + writeAndReadUntilLine(t, email, c, scanner, "250 2.0.0 OK: queued") +} + func TestSmtpBackend_Unsupported(t *testing.T) { email := `EHLO example.com MAIL FROM: phil@example.com @@ -390,6 +423,50 @@ L0VOIj4KClRoaXMgaXMgYSB0ZXN0IG1lc3NhZ2UgZnJvbSBUcnVlTkFTIENPUkUuCg== writeAndReadUntilLine(t, email, c, scanner, "250 2.0.0 OK: queued") } + +func TestSmtpBackend_MultipartQuotedPrintable(t *testing.T) { + email := `EHLO example.com +MAIL FROM: phil@example.com +RCPT TO: ntfy-mytopic@ntfy.sh +DATA +MIME-Version: 1.0 +Date: Tue, 28 Dec 2021 00:30:10 +0100 +Message-ID: +Subject: and one more +From: Phil +To: ntfy-mytopic@ntfy.sh +Content-Type: multipart/alternative; boundary="000000000000f3320b05d42915c9" + +--000000000000f3320b05d42915c9 +Content-Type: text/html; charset="UTF-8" + +html, ignore me + +--000000000000f3320b05d42915c9 +Content-Type: text/plain; charset="UTF-8" +Content-Transfer-Encoding: quoted-printable + +what's +=C3=A0&=C3=A9"'(-=C3=A8_=C3=A7=C3=A0) +=3D=3D=3D=3D=3D +up + +--000000000000f3320b05d42915c9-- +. +` + s, c, _, scanner := newTestSMTPServer(t, func(w http.ResponseWriter, r *http.Request) { + require.Equal(t, "/mytopic", r.URL.Path) + require.Equal(t, "and one more", r.Header.Get("Title")) + require.Equal(t, `what's +à&é"'(-è_çà) +===== +up`, readAll(t, r.Body)) + }) + defer s.Close() + defer c.Close() + writeAndReadUntilLine(t, email, c, scanner, "250 2.0.0 OK: queued") +} + func TestSmtpBackend_NestedMultipartBase64(t *testing.T) { email := `EHLO example.com MAIL FROM: test@mydomain.me