mirror of
				https://github.com/binwiederhier/ntfy.git
				synced 2025-10-31 13:02:24 +01:00 
			
		
		
		
	Support for base64 encoded emails
This commit is contained in:
		
							parent
							
								
									1edcc239e5
								
							
						
					
					
						commit
						e9b05e8ed7
					
				
					 3 changed files with 113 additions and 19 deletions
				
			
		|  | @ -4,6 +4,10 @@ and the [ntfy Android app](https://github.com/binwiederhier/ntfy-android/release | |||
| 
 | ||||
| ## 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:** | ||||
| 
 | ||||
| * Arabic (thanks to [@ButterflyOfFire](https://hosted.weblate.org/user/ButterflyOfFire/)) | ||||
|  |  | |||
|  | @ -2,6 +2,7 @@ package server | |||
| 
 | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"encoding/base64" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"github.com/emersion/go-smtp" | ||||
|  | @ -204,28 +205,20 @@ func (s *smtpSession) withFailCount(fn func() error) error { | |||
| 
 | ||||
| func readMailBody(msg *mail.Message) (string, error) { | ||||
| 	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")) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	if contentType == "text/plain" { | ||||
| 		return readPlainTextMailBody(msg) | ||||
| 	} else if strings.HasPrefix(contentType, "multipart/") { | ||||
| 	if strings.ToLower(contentType) == "text/plain" { | ||||
| 		return readPlainTextMailBody(msg.Body, msg.Header.Get("Content-Transfer-Encoding")) | ||||
| 	} else if strings.HasPrefix(strings.ToLower(contentType), "multipart/") { | ||||
| 		return readMultipartMailBody(msg, params) | ||||
| 	} | ||||
| 	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) { | ||||
| 	mr := multipart.NewReader(msg.Body, params["boundary"]) | ||||
| 	for { | ||||
|  | @ -236,14 +229,20 @@ func readMultipartMailBody(msg *mail.Message, params map[string]string) (string, | |||
| 		partContentType, _, err := mime.ParseMediaType(part.Header.Get("Content-Type")) | ||||
| 		if err != nil { | ||||
| 			return "", err | ||||
| 		} | ||||
| 		if partContentType != "text/plain" { | ||||
| 		} else if strings.ToLower(partContentType) != "text/plain" { | ||||
| 			continue | ||||
| 		} | ||||
| 		body, err := io.ReadAll(part) | ||||
| 		if err != nil { | ||||
| 			return "", err | ||||
| 		} | ||||
| 		return string(body), nil | ||||
| 		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 { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	return string(body), nil | ||||
| } | ||||
|  |  | |||
|  | @ -348,6 +348,97 @@ what's up | |||
| 	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) | ||||
| 
 | ||||
| func newTestSMTPServer(t *testing.T, handler smtpHandlerFunc) (s *smtp.Server, c net.Conn, conf *Config, scanner *bufio.Scanner) { | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 binwiederhier
						binwiederhier