Support for base64 encoded emails
parent
1edcc239e5
commit
e9b05e8ed7
|
@ -4,6 +4,10 @@ and the [ntfy Android app](https://github.com/binwiederhier/ntfy-android/release
|
||||||
|
|
||||||
## ntfy server v2.0.2 (UNRELEASED)
|
## ntfy server v2.0.2 (UNRELEASED)
|
||||||
|
|
||||||
|
**Bug fixes + maintenance:**
|
||||||
|
|
||||||
|
* Support for base64 encoded emails ([#610](https://github.com/binwiederhier/ntfy/issues/610), thanks to [@Robert-litts](https://github.com/Robert-litts))
|
||||||
|
|
||||||
**Additional languages:**
|
**Additional languages:**
|
||||||
|
|
||||||
* Arabic (thanks to [@ButterflyOfFire](https://hosted.weblate.org/user/ButterflyOfFire/))
|
* Arabic (thanks to [@ButterflyOfFire](https://hosted.weblate.org/user/ButterflyOfFire/))
|
||||||
|
|
|
@ -2,6 +2,7 @@ package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/emersion/go-smtp"
|
"github.com/emersion/go-smtp"
|
||||||
|
@ -204,28 +205,20 @@ func (s *smtpSession) withFailCount(fn func() error) error {
|
||||||
|
|
||||||
func readMailBody(msg *mail.Message) (string, error) {
|
func readMailBody(msg *mail.Message) (string, error) {
|
||||||
if msg.Header.Get("Content-Type") == "" {
|
if msg.Header.Get("Content-Type") == "" {
|
||||||
return readPlainTextMailBody(msg)
|
return readPlainTextMailBody(msg.Body, msg.Header.Get("Content-Transfer-Encoding"))
|
||||||
}
|
}
|
||||||
contentType, params, err := mime.ParseMediaType(msg.Header.Get("Content-Type"))
|
contentType, params, err := mime.ParseMediaType(msg.Header.Get("Content-Type"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
if contentType == "text/plain" {
|
if strings.ToLower(contentType) == "text/plain" {
|
||||||
return readPlainTextMailBody(msg)
|
return readPlainTextMailBody(msg.Body, msg.Header.Get("Content-Transfer-Encoding"))
|
||||||
} else if strings.HasPrefix(contentType, "multipart/") {
|
} else if strings.HasPrefix(strings.ToLower(contentType), "multipart/") {
|
||||||
return readMultipartMailBody(msg, params)
|
return readMultipartMailBody(msg, params)
|
||||||
}
|
}
|
||||||
return "", errUnsupportedContentType
|
return "", errUnsupportedContentType
|
||||||
}
|
}
|
||||||
|
|
||||||
func readPlainTextMailBody(msg *mail.Message) (string, error) {
|
|
||||||
body, err := io.ReadAll(msg.Body)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return string(body), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func readMultipartMailBody(msg *mail.Message, params map[string]string) (string, error) {
|
func readMultipartMailBody(msg *mail.Message, params map[string]string) (string, error) {
|
||||||
mr := multipart.NewReader(msg.Body, params["boundary"])
|
mr := multipart.NewReader(msg.Body, params["boundary"])
|
||||||
for {
|
for {
|
||||||
|
@ -236,14 +229,20 @@ func readMultipartMailBody(msg *mail.Message, params map[string]string) (string,
|
||||||
partContentType, _, err := mime.ParseMediaType(part.Header.Get("Content-Type"))
|
partContentType, _, err := mime.ParseMediaType(part.Header.Get("Content-Type"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
} else if strings.ToLower(partContentType) != "text/plain" {
|
||||||
if partContentType != "text/plain" {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
body, err := io.ReadAll(part)
|
return readPlainTextMailBody(part, part.Header.Get("Content-Transfer-Encoding"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func readPlainTextMailBody(reader io.Reader, transferEncoding string) (string, error) {
|
||||||
|
if strings.ToLower(transferEncoding) == "base64" {
|
||||||
|
reader = base64.NewDecoder(base64.StdEncoding, reader)
|
||||||
|
}
|
||||||
|
body, err := io.ReadAll(reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return string(body), nil
|
return string(body), nil
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -348,6 +348,97 @@ what's up
|
||||||
writeAndReadUntilLine(t, email, c, scanner, "451 4.0.0 invalid address")
|
writeAndReadUntilLine(t, email, c, scanner, "451 4.0.0 invalid address")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSmtpBackend_Base64Body(t *testing.T) {
|
||||||
|
email := `EHLO example.com
|
||||||
|
MAIL FROM: test@mydomain.me
|
||||||
|
RCPT TO: ntfy-mytopic@ntfy.sh
|
||||||
|
DATA
|
||||||
|
Content-Type: multipart/mixed; boundary="===============2138658284696597373=="
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Subject: TrueNAS truenas.local: TrueNAS Test Message hostname: truenas.local
|
||||||
|
From: =?utf-8?q?Robbie?= <test@mydomain.me>
|
||||||
|
To: test@mydomain.me
|
||||||
|
Date: Thu, 16 Feb 2023 01:04:00 -0000
|
||||||
|
Message-ID: <truenas-20230216.010400.344514.b'8jfL'@truenas.local>
|
||||||
|
|
||||||
|
This is a multi-part message in MIME format.
|
||||||
|
--===============2138658284696597373==
|
||||||
|
Content-Type: text/plain; charset="utf-8"
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Transfer-Encoding: base64
|
||||||
|
|
||||||
|
VGhpcyBpcyBhIHRlc3QgbWVzc2FnZSBmcm9tIFRydWVOQVMgQ09SRS4=
|
||||||
|
|
||||||
|
--===============2138658284696597373==
|
||||||
|
Content-Type: text/html; charset="utf-8"
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Transfer-Encoding: base64
|
||||||
|
|
||||||
|
PCFET0NUWVBFIEhUTUwgUFVCTElDICItLy9XM0MvL0RURCBIVE1MIDQuMCBUcmFuc2l0aW9uYWwv
|
||||||
|
L0VOIj4KClRoaXMgaXMgYSB0ZXN0IG1lc3NhZ2UgZnJvbSBUcnVlTkFTIENPUkUuCg==
|
||||||
|
|
||||||
|
--===============2138658284696597373==--
|
||||||
|
.
|
||||||
|
`
|
||||||
|
s, c, _, scanner := newTestSMTPServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
require.Equal(t, "/mytopic", r.URL.Path)
|
||||||
|
require.Equal(t, "TrueNAS truenas.local: TrueNAS Test Message hostname: truenas.local", r.Header.Get("Title"))
|
||||||
|
require.Equal(t, "This is a test message from TrueNAS CORE.", 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
|
||||||
|
RCPT TO: ntfy-mytopic@ntfy.sh
|
||||||
|
DATA
|
||||||
|
Content-Type: multipart/mixed; boundary="===============2138658284696597373=="
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Subject: TrueNAS truenas.local: TrueNAS Test Message hostname: truenas.local
|
||||||
|
From: =?utf-8?q?Robbie?= <test@mydomain.me>
|
||||||
|
To: test@mydomain.me
|
||||||
|
Date: Thu, 16 Feb 2023 01:04:00 -0000
|
||||||
|
Message-ID: <truenas-20230216.010400.344514.b'8jfL'@truenas.local>
|
||||||
|
|
||||||
|
This is a multi-part message in MIME format.
|
||||||
|
--===============2138658284696597373==
|
||||||
|
Content-Type: multipart/alternative; boundary="===============2233989480071754745=="
|
||||||
|
MIME-Version: 1.0
|
||||||
|
|
||||||
|
--===============2233989480071754745==
|
||||||
|
Content-Type: text/plain; charset="utf-8"
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Transfer-Encoding: base64
|
||||||
|
|
||||||
|
VGhpcyBpcyBhIHRlc3QgbWVzc2FnZSBmcm9tIFRydWVOQVMgQ09SRS4=
|
||||||
|
|
||||||
|
--===============2233989480071754745==
|
||||||
|
Content-Type: text/html; charset="utf-8"
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Transfer-Encoding: base64
|
||||||
|
|
||||||
|
PCFET0NUWVBFIEhUTUwgUFVCTElDICItLy9XM0MvL0RURCBIVE1MIDQuMCBUcmFuc2l0aW9uYWwv
|
||||||
|
L0VOIj4KClRoaXMgaXMgYSB0ZXN0IG1lc3NhZ2UgZnJvbSBUcnVlTkFTIENPUkUuCg==
|
||||||
|
|
||||||
|
--===============2233989480071754745==--
|
||||||
|
|
||||||
|
--===============2138658284696597373==--
|
||||||
|
.
|
||||||
|
`
|
||||||
|
|
||||||
|
s, c, _, scanner := newTestSMTPServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
t.Fatal("This should not be called")
|
||||||
|
})
|
||||||
|
defer s.Close()
|
||||||
|
defer c.Close()
|
||||||
|
writeAndReadUntilLine(t, email, c, scanner, "451 4.0.0 invalid address")
|
||||||
|
}
|
||||||
|
*/
|
||||||
type smtpHandlerFunc func(http.ResponseWriter, *http.Request)
|
type smtpHandlerFunc func(http.ResponseWriter, *http.Request)
|
||||||
|
|
||||||
func newTestSMTPServer(t *testing.T, handler smtpHandlerFunc) (s *smtp.Server, c net.Conn, conf *Config, scanner *bufio.Scanner) {
|
func newTestSMTPServer(t *testing.T, handler smtpHandlerFunc) (s *smtp.Server, c net.Conn, conf *Config, scanner *bufio.Scanner) {
|
||||||
|
|
Loading…
Reference in New Issue