Fix encoding issues
This commit is contained in:
		
							parent
							
								
									7cfe909644
								
							
						
					
					
						commit
						113053a9e3
					
				
					 3 changed files with 213 additions and 7 deletions
				
			
		|  | @ -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() | ||||
|  |  | |||
|  | @ -5,6 +5,8 @@ import ( | |||
| 	"errors" | ||||
| 	"github.com/emersion/go-smtp" | ||||
| 	"io" | ||||
| 	"mime" | ||||
| 	"mime/multipart" | ||||
| 	"net/mail" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
|  | @ -15,6 +17,7 @@ var ( | |||
| 	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. | ||||
|  | @ -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 | ||||
|  | @ -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 { | ||||
|  | @ -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
									
								
								server/smtp_server_test.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										158
									
								
								server/smtp_server_test.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 <phil@example.com> | ||||
| To: ntfy-mytopic@ntfy.sh | ||||
| 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'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("phil@example.com", smtp.MailOptions{})) | ||||
| 	require.Nil(t, session.Rcpt("ntfy-mytopic@ntfy.sh")) | ||||
| 	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 <phil@example.com> | ||||
| To: mytopic@ntfy.sh | ||||
| 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("phil@example.com", smtp.MailOptions{})) | ||||
| 	require.Nil(t, session.Rcpt("mytopic@ntfy.sh")) | ||||
| 	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 <phil@example.com> | ||||
| To: ntfy-mytopic@ntfy.sh | ||||
| 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("phil@example.com", smtp.MailOptions{})) | ||||
| 	require.Nil(t, session.Rcpt("ntfy-mytopic@ntfy.sh")) | ||||
| 	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 <phil@example.com> | ||||
| To: mytopic@ntfy.sh | ||||
| 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("phil@example.com", smtp.MailOptions{})) | ||||
| 	require.Nil(t, session.Rcpt("mytopic@ntfy.sh")) | ||||
| 	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 <phil@example.com> | ||||
| To: mytopic@ntfy.sh | ||||
| 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("phil@example.com", smtp.MailOptions{})) | ||||
| 	require.Nil(t, session.Rcpt("mytopic@ntfy.sh")) | ||||
| 	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 | ||||
| } | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue