Skip to content

Commit

Permalink
Fix encoding issues
Browse files Browse the repository at this point in the history
  • Loading branch information
Philipp Heckel committed Dec 28, 2021
1 parent 7cfe909 commit 113053a
Show file tree
Hide file tree
Showing 3 changed files with 213 additions and 7 deletions.
2 changes: 1 addition & 1 deletion server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -769,7 +769,7 @@ func (s *Server) runSMTPServer() error {
s.smtpServer.Domain = s.config.SMTPServerDomain
s.smtpServer.ReadTimeout = 10 * time.Second
s.smtpServer.WriteTimeout = 10 * time.Second
s.smtpServer.MaxMessageBytes = 2 * s.config.MessageLimit
s.smtpServer.MaxMessageBytes = 1024 * 1024 // Must be much larger than message size (headers, multipart, etc.)
s.smtpServer.MaxRecipients = 1
s.smtpServer.AllowInsecureAuth = true
return s.smtpServer.ListenAndServe()
Expand Down
60 changes: 54 additions & 6 deletions server/smtp_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,19 @@ import (
"errors"
"github.com/emersion/go-smtp"
"io"
"mime"
"mime/multipart"
"net/mail"
"strings"
"sync"
)

var (
errInvalidDomain = errors.New("invalid domain")
errInvalidAddress = errors.New("invalid address")
errInvalidTopic = errors.New("invalid topic")
errTooManyRecipients = errors.New("too many recipients")
errInvalidDomain = errors.New("invalid domain")
errInvalidAddress = errors.New("invalid address")
errInvalidTopic = errors.New("invalid topic")
errTooManyRecipients = errors.New("too many recipients")
errUnsupportedContentType = errors.New("unsupported content type")
)

// smtpBackend implements SMTP server methods.
Expand Down Expand Up @@ -94,6 +97,7 @@ func (s *smtpSession) Rcpt(to string) error {

func (s *smtpSession) Data(r io.Reader) error {
return s.withFailCount(func() error {
conf := s.backend.config
b, err := io.ReadAll(r) // Protected by MaxMessageBytes
if err != nil {
return err
Expand All @@ -102,13 +106,21 @@ func (s *smtpSession) Data(r io.Reader) error {
if err != nil {
return err
}
body, err := io.ReadAll(io.LimitReader(msg.Body, int64(s.backend.config.MessageLimit)))
body, err := readMailBody(msg)
if err != nil {
return err
}
m := newDefaultMessage(s.topic, string(body))
if len(body) > conf.MessageLimit {
body = body[:conf.MessageLimit]
}
m := newDefaultMessage(s.topic, body)
subject := msg.Header.Get("Subject")
if subject != "" {
dec := mime.WordDecoder{}
subject, err := dec.DecodeHeader(subject)
if err != nil {
return err
}
m.Title = subject
}
if err := s.backend.sub(m); err != nil {
Expand Down Expand Up @@ -140,3 +152,39 @@ func (s *smtpSession) withFailCount(fn func() error) error {
}
return err
}

func readMailBody(msg *mail.Message) (string, error) {
contentType, params, err := mime.ParseMediaType(msg.Header.Get("Content-Type"))
if err != nil {
return "", err
}
if contentType == "text/plain" {
body, err := io.ReadAll(msg.Body)
if err != nil {
return "", err
}
return string(body), nil
}
if strings.HasPrefix(contentType, "multipart/") {
mr := multipart.NewReader(msg.Body, params["boundary"])
for {
part, err := mr.NextPart()
if err != nil { // may be io.EOF
return "", err
}
partContentType, _, err := mime.ParseMediaType(part.Header.Get("Content-Type"))
if err != nil {
return "", err
}
if partContentType != "text/plain" {
continue
}
body, err := io.ReadAll(part)
if err != nil {
return "", err
}
return string(body), nil
}
}
return "", errUnsupportedContentType
}
158 changes: 158 additions & 0 deletions server/smtp_server_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
package server

import (
"github.com/emersion/go-smtp"
"github.com/stretchr/testify/require"
"strings"
"testing"
)

func TestSmtpBackend_Multipart(t *testing.T) {
email := `MIME-Version: 1.0
Date: Tue, 28 Dec 2021 00:30:10 +0100
Message-ID: <CAAvm79YP0C=Rt1N=KWmSUBB87KK2rRChmdzKqF1vCwMEUiVzLQ@mail.gmail.com>
Subject: and one more
From: Phil <[email protected]>
To: [email protected]
Content-Type: multipart/alternative; boundary="000000000000f3320b05d42915c9"
--000000000000f3320b05d42915c9
Content-Type: text/plain; charset="UTF-8"
what's up
--000000000000f3320b05d42915c9
Content-Type: text/html; charset="UTF-8"
<div dir="ltr">what&#39;s up<br clear="all"><div><br></div></div>
--000000000000f3320b05d42915c9--`
_, backend := newTestBackend(t, func(m *message) error {
require.Equal(t, "mytopic", m.Topic)
require.Equal(t, "and one more", m.Title)
require.Equal(t, "what's up\n", m.Message)
return nil
})
session, _ := backend.AnonymousLogin(nil)
require.Nil(t, session.Mail("[email protected]", smtp.MailOptions{}))
require.Nil(t, session.Rcpt("[email protected]"))
require.Nil(t, session.Data(strings.NewReader(email)))
}

func TestSmtpBackend_Plaintext(t *testing.T) {
email := `Date: Tue, 28 Dec 2021 00:30:10 +0100
Message-ID: <CAAvm79YP0C=Rt1N=KWmSUBB87KK2rRChmdzKqF1vCwMEUiVzLQ@mail.gmail.com>
Subject: and one more
From: Phil <[email protected]>
To: [email protected]
Content-Type: text/plain; charset="UTF-8"
what's up
`
conf, backend := newTestBackend(t, func(m *message) error {
require.Equal(t, "mytopic", m.Topic)
require.Equal(t, "and one more", m.Title)
require.Equal(t, "what's up\n", m.Message)
return nil
})
conf.SMTPServerAddrPrefix = ""
session, _ := backend.AnonymousLogin(nil)
require.Nil(t, session.Mail("[email protected]", smtp.MailOptions{}))
require.Nil(t, session.Rcpt("[email protected]"))
require.Nil(t, session.Data(strings.NewReader(email)))
}

func TestSmtpBackend_Plaintext_EncodedSubject(t *testing.T) {
email := `Date: Tue, 28 Dec 2021 00:30:10 +0100
Subject: =?UTF-8?B?VGhyZWUgc2FudGFzIPCfjoXwn46F8J+OhQ==?=
From: Phil <[email protected]>
To: [email protected]
Content-Type: text/plain; charset="UTF-8"
what's up
`
_, backend := newTestBackend(t, func(m *message) error {
require.Equal(t, "Three santas 🎅🎅🎅", m.Title)
return nil
})
session, _ := backend.AnonymousLogin(nil)
require.Nil(t, session.Mail("[email protected]", smtp.MailOptions{}))
require.Nil(t, session.Rcpt("[email protected]"))
require.Nil(t, session.Data(strings.NewReader(email)))
}

func TestSmtpBackend_Plaintext_TooLongTruncate(t *testing.T) {
email := `Date: Tue, 28 Dec 2021 00:30:10 +0100
Message-ID: <CAAvm79YP0C=Rt1N=KWmSUBB87KK2rRChmdzKqF1vCwMEUiVzLQ@mail.gmail.com>
Subject: and one more
From: Phil <[email protected]>
To: [email protected]
Content-Type: text/plain; charset="UTF-8"
you know this is a string.
it's a long string.
it's supposed to be longer than the max message length
which is 512 bytes,
which some people say is too short
but it kinda makes sense when you look at what it looks like one a phone
heck this wasn't even half of it so far.
so i'm gonna fill the rest of this with AAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAa
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
and with BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
that should do it
`
conf, backend := newTestBackend(t, func(m *message) error {
expected := `you know this is a string.
it's a long string.
it's supposed to be longer than the max message length
which is 512 bytes,
which some people say is too short
but it kinda makes sense when you look at what it looks like one a phone
heck this wasn't even half of it so far.
so i'm gonna fill the rest of this with AAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAa
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
and with `
require.Equal(t, expected, m.Message)
return nil
})
conf.SMTPServerAddrPrefix = ""
session, _ := backend.AnonymousLogin(nil)
require.Nil(t, session.Mail("[email protected]", smtp.MailOptions{}))
require.Nil(t, session.Rcpt("[email protected]"))
require.Nil(t, session.Data(strings.NewReader(email)))
}

func TestSmtpBackend_Unsupported(t *testing.T) {
email := `Date: Tue, 28 Dec 2021 00:30:10 +0100
Message-ID: <CAAvm79YP0C=Rt1N=KWmSUBB87KK2rRChmdzKqF1vCwMEUiVzLQ@mail.gmail.com>
Subject: and one more
From: Phil <[email protected]>
To: [email protected]
Content-Type: text/SOMETHINGELSE
what's up
`
conf, backend := newTestBackend(t, func(m *message) error {
return nil
})
conf.SMTPServerAddrPrefix = ""
session, _ := backend.Login(nil, "user", "pass")
require.Nil(t, session.Mail("[email protected]", smtp.MailOptions{}))
require.Nil(t, session.Rcpt("[email protected]"))
require.Equal(t, errUnsupportedContentType, session.Data(strings.NewReader(email)))
}

func newTestBackend(t *testing.T, sub subscriber) (*Config, *smtpBackend) {
conf := newTestConfig(t)
conf.SMTPServerListen = ":25"
conf.SMTPServerDomain = "ntfy.sh"
conf.SMTPServerAddrPrefix = "ntfy-"
backend := newMailBackend(conf, sub)
return conf, backend
}

0 comments on commit 113053a

Please sign in to comment.