Compare commits

...

4 commits

Author SHA1 Message Date
db9893f15a
More test coverage
Refactored the way EML files are tested, the errors are now handled more efficiently. Temporary directory and file creation, as well as file writing, have been moved to a helper function named 'stringToTempFile'. Moreover, additional test cases were added to ensure proper parsing failure for various types of email-related errors.
2024-06-24 14:30:28 +02:00
0cec8a28f7
Refactor EML parsing into a single function
The previous separate parsing of EML headers and body parts has been refactored into a single function, parseEML. This change simplifies the operations in the readEML and makes the code cleaner by reducing repetition.
2024-06-24 14:30:07 +02:00
9fa8644a9a
More test coverage
New failing tests have been added to 'eml_test.go' to account for a variety of error situations, such as broken FROM, TO, headers, bodies, and unknown or unsupported content types. Improving the robustness of test coverage helps identify potential issues and ensure the resilience and correctness of the code.
2024-06-24 13:42:28 +02:00
70c9387003
Add error handling for unknown content types in eml.go
The update adds a case to the switch clause in eml.go for properly handling unknown content types. An error will now be returned when the media type of the body to be parsed is not recognized, increasing the robustness of the system.
2024-06-24 13:41:48 +02:00
2 changed files with 312 additions and 22 deletions

27
eml.go
View file

@ -39,11 +39,8 @@ func EMLToMsgFromReader(reader io.Reader) (*Msg, error) {
return msg, fmt.Errorf("failed to parse EML from reader: %w", err)
}
if err = parseEMLHeaders(&parsedMsg.Header, msg); err != nil {
return msg, fmt.Errorf("failed to parse EML headers: %w", err)
}
if err = parseEMLBodyParts(parsedMsg, bodybuf, msg); err != nil {
return msg, fmt.Errorf("failed to parse EML body parts: %w", err)
if err := parseEML(parsedMsg, bodybuf, msg); err != nil {
return msg, fmt.Errorf("failed to parse EML contents: %w", err)
}
return msg, nil
@ -64,17 +61,24 @@ func EMLToMsgFromFile(filePath string) (*Msg, error) {
return msg, fmt.Errorf("failed to parse EML file: %w", err)
}
if err = parseEMLHeaders(&parsedMsg.Header, msg); err != nil {
return msg, fmt.Errorf("failed to parse EML headers: %w", err)
if err := parseEML(parsedMsg, bodybuf, msg); err != nil {
return msg, fmt.Errorf("failed to parse EML contents: %w", err)
}
if err = parseEMLBodyParts(parsedMsg, bodybuf, msg); err != nil {
return msg, fmt.Errorf("failed to parse EML body parts: %w", err)
}
//fmt.Printf("FOO: %+v\n", msg)
return msg, nil
}
// parseEML parses the EML's headers and body and inserts the parsed values into the Msg
func parseEML(parsedMsg *netmail.Message, bodybuf *bytes.Buffer, msg *Msg) error {
if err := parseEMLHeaders(&parsedMsg.Header, msg); err != nil {
return fmt.Errorf("failed to parse EML headers: %w", err)
}
if err := parseEMLBodyParts(parsedMsg, bodybuf, msg); err != nil {
return fmt.Errorf("failed to parse EML body parts: %w", err)
}
return nil
}
// readEML opens an EML file and uses net/mail to parse the header and body
func readEML(filePath string) (*netmail.Message, *bytes.Buffer, error) {
fileHandle, err := os.Open(filePath)
@ -195,6 +199,7 @@ func parseEMLBodyParts(parsedMsg *netmail.Message, bodybuf *bytes.Buffer, msg *M
return fmt.Errorf("failed to parse multipart body: %w", err)
}
default:
return fmt.Errorf("failed to parse body, unknown content type: %s", mediatype)
}
return nil
}

View file

@ -5,6 +5,7 @@
package mail
import (
"bytes"
"fmt"
"os"
"strings"
@ -31,6 +32,115 @@ This is a test mail. Please do not reply to this. Also this line is very long so
should be wrapped.
Thank your for your business!
The go-mail team
--
This is a signature`
exampleMailPlainBrokenBody = `Date: Wed, 01 Nov 2023 00:00:00 +0000
MIME-Version: 1.0
Message-ID: <1305604950.683004066175.AAAAAAAAaaaaaaaaB@go-mail.dev>
Subject: Example mail // plain text without encoding
User-Agent: go-mail v0.4.0 // https://github.com/wneessen/go-mail
X-Mailer: go-mail v0.4.0 // https://github.com/wneessen/go-mail
From: "Toni Tester" <go-mail@go-mail.dev>
To: <go-mail+test@go-mail.dev>
Cc: <go-mail+cc@go-mail.dev>
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: base64
This plain text body should not be parsed as Base64.
`
exampleMailPlainNoContentType = `Date: Wed, 01 Nov 2023 00:00:00 +0000
MIME-Version: 1.0
Message-ID: <1305604950.683004066175.AAAAAAAAaaaaaaaaB@go-mail.dev>
Subject: Example mail // plain text without encoding
User-Agent: go-mail v0.4.0 // https://github.com/wneessen/go-mail
X-Mailer: go-mail v0.4.0 // https://github.com/wneessen/go-mail
From: "Toni Tester" <go-mail@go-mail.dev>
To: <go-mail+test@go-mail.dev>
Cc: <go-mail+cc@go-mail.dev>
This plain text body should not be parsed as Base64.
`
exampleMailPlainUnknownContentType = `Date: Wed, 01 Nov 2023 00:00:00 +0000
MIME-Version: 1.0
Message-ID: <1305604950.683004066175.AAAAAAAAaaaaaaaaB@go-mail.dev>
Subject: Example mail // plain text without encoding
User-Agent: go-mail v0.4.0 // https://github.com/wneessen/go-mail
X-Mailer: go-mail v0.4.0 // https://github.com/wneessen/go-mail
From: "Toni Tester" <go-mail@go-mail.dev>
To: <go-mail+test@go-mail.dev>
Cc: <go-mail+cc@go-mail.dev>
Content-Type: application/unknown; charset=UTF-8
Content-Transfer-Encoding: base64
This plain text body should not be parsed as Base64.
`
exampleMailPlainBrokenHeader = `Date: Wed, 01 Nov 2023 00:00:00 +0000
MIME-Version = 1.0
Message-ID: <1305604950.683004066175.AAAAAAAAaaaaaaaaB@go-mail.dev>
Subject: Example mail // plain text without encoding
User-Agent = go-mail v0.4.0 // https://github.com/wneessen/go-mail
X-Mailer: go-mail v0.4.0 // https://github.com/wneessen/go-mail
From = "Toni Tester" <go-mail@go-mail.dev>
To: <go-mail+test@go-mail.dev>
Cc: <go-mail+cc@go-mail.dev>
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding = 8bit
Dear Customer,
This is a test mail. Please do not reply to this. Also this line is very long so it
should be wrapped.
Thank your for your business!
The go-mail team
--
This is a signature`
exampleMailPlainBrokenFrom = `Date: Wed, 01 Nov 2023 00:00:00 +0000
MIME-Version: 1.0
Message-ID: <1305604950.683004066175.AAAAAAAAaaaaaaaaB@go-mail.dev>
Subject: Example mail // plain text without encoding
User-Agent: go-mail v0.4.0 // https://github.com/wneessen/go-mail
X-Mailer: go-mail v0.4.0 // https://github.com/wneessen/go-mail
From: Toni Tester" go-mail@go-mail.dev>
To: <go-mail+test@go-mail.dev>
Cc: <go-mail+cc@go-mail.dev>
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Dear Customer,
This is a test mail. Please do not reply to this. Also this line is very long so it
should be wrapped.
Thank your for your business!
The go-mail team
--
This is a signature`
exampleMailPlainBrokenTo = `Date: Wed, 01 Nov 2023 00:00:00 +0000
MIME-Version: 1.0
Message-ID: <1305604950.683004066175.AAAAAAAAaaaaaaaaB@go-mail.dev>
Subject: Example mail // plain text without encoding
User-Agent: go-mail v0.4.0 // https://github.com/wneessen/go-mail
X-Mailer: go-mail v0.4.0 // https://github.com/wneessen/go-mail
From: "Toni Tester" <go-mail@go-mail.dev>
To: go-mail+test.go-mail.dev>
Cc: <go-mail+cc@go-mail.dev>
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Dear Customer,
This is a test mail. Please do not reply to this. Also this line is very long so it
should be wrapped.
Thank your for your business!
The go-mail team
@ -99,6 +209,28 @@ This is a test mail. Please do not reply to this. Also this line is very lo=
ng so it
should be wrapped.
Thank your for your business!
The go-mail team
--
This is a signature`
exampleMailPlainUnsupportedTransferEnc = `Date: Wed, 01 Nov 2023 00:00:00 +0000
MIME-Version: 1.0
Message-ID: <1305604950.683004066175.AAAAAAAAaaaaaaaaB@go-mail.dev>
Subject: Example mail // plain text quoted-printable
User-Agent: go-mail v0.4.0 // https://github.com/wneessen/go-mail
X-Mailer: go-mail v0.4.0 // https://github.com/wneessen/go-mail
From: "Toni Tester" <go-mail@go-mail.dev>
To: <go-mail+test@go-mail.dev>
Cc: <go-mail+cc@go-mail.dev>
Content-Type: text/plain; charset=ISO-8859-1
Content-Transfer-Encoding: unknown
Dear Customer,
This is a test mail. Please do not reply to this. Also this line is very long so it should be wrapped.
This is not ==D3
Thank your for your business!
The go-mail team
@ -151,6 +283,37 @@ VGhpcyBpcyBhIHNpbXBsZSB0ZXN0IHRleHQgZmlsZSBhdHRhY2htZW50LgoKSXQgCiAgaGFzCiAg
ICAgc2V2ZXJhbAogICAgICAgICAgICBuZXdsaW5lcwoJICAgICAgICAgICAgYW5kCgkgICAgc3Bh
Y2VzCiAgICAgaW4KICBpdAouCgpBcyB3ZWxsIGFzIGFuIGVtb2ppOiDwn5mCCg==
--45c75ff528359022eb03679fbe91877d75343f2e1f8193e349deffa33ff7--`
exampleMailPlainB64BrokenBody = `Date: Wed, 01 Nov 2023 00:00:00 +0000
MIME-Version: 1.0
Message-ID: <1305604950.683004066175.AAAAAAAAaaaaaaaaB@go-mail.dev>
Subject: Example mail // plain text base64 with attachment
User-Agent: go-mail v0.4.1 // https://github.com/wneessen/go-mail
X-Mailer: go-mail v0.4.1 // https://github.com/wneessen/go-mail
From: "Toni Tester" <go-mail@go-mail.dev>
To: <go-mail+test@go-mail.dev>
Cc: <go-mail+cc@go-mail.dev>
Content-Type: multipart/mixed;
boundary=45c75ff528359022eb03679fbe91877d75343f2e1f8193e349deffa33ff7
--45c75ff528359022eb03679fbe91877d75343f2e1f8193e349deffa33ff7
Content-Transfer-Encoding = base64
Content-Type = text/plain; charset=UTF-8
RGVhciBDdXN0b21lciwKClRoaXMgaXMgYSB0ZXN0IG1haWwuIFBsZWFzZSBkbyBub3QgcmVwbHkg
dG8gdGhpcy4gQWxzbyB0aGlzIGxpbmUgaXMgdmVyeSBsb25nIHNvIGl0CnNob3VsZCBiZSB3cmFw
cGVkLgoKClRoYW5rIHlvdXIgZm9yIHlvdXIgYnVzaW5lc3MhClRoZSBnby1tYWlsIHRlYW0KCi0t
ClRoaXMgaXMgYSBzaWduYXR1cmU=
--45c75ff528359022eb03679fbe91877d75343f2e1f8193e349deffa33ff7
Content-Disposition: attachment; filename="test.attachment"
Content-Transfer-Encoding: base64
Content-Type: application/octet-stream; name="test.attachment"
VGhpcyBpcyBhIHNpbXBsZSB0ZXN0IHRleHQgZmlsZSBhdHRhY2htZW50LgoKSXQgCiAgaGFzCiAg
ICAgc2V2ZXJhbAogICAgICAgICAgICBuZXdsaW5lcwoJICAgICAgICAgICAgYW5kCgkgICAgc3Bh
Y2VzCiAgICAgaW4KICBpdAouCgpBcyB3ZWxsIGFzIGFuIGVtb2ppOiDwn5mCCg==
--45c75ff528359022eb03679fbe91877d75343f2e1f8193e349deffa33ff7--`
exampleMailPlainB64WithEmbed = `Date: Wed, 01 Nov 2023 00:00:00 +0000
MIME-Version: 1.0
@ -240,22 +403,13 @@ func TestEMLToMsgFromFile(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tempDir, err := os.MkdirTemp("", fmt.Sprintf("*-%s", tt.name))
if err != nil {
t.Errorf("failed to create temp dir: %s", err)
return
}
tempDir, tempFile, err := stringToTempFile(tt.eml, tt.name)
defer func() {
if err = os.RemoveAll(tempDir); err != nil {
t.Error("failed to remove temp dir:", err)
}
}()
err = os.WriteFile(fmt.Sprintf("%s/%s.eml", tempDir, tt.name), []byte(tt.eml), 0666)
if err != nil {
t.Error("failed to write mail to temp file:", err)
return
}
msg, err := EMLToMsgFromFile(fmt.Sprintf("%s/%s.eml", tempDir, tt.name))
msg, err := EMLToMsgFromFile(tempFile)
if err != nil {
t.Errorf("failed to parse EML: %s", err)
}
@ -270,6 +424,109 @@ func TestEMLToMsgFromFile(t *testing.T) {
}
}
func TestEMLToMsgFromReaderFailing(t *testing.T) {
mailbuf := bytes.NewBufferString(exampleMailPlainBrokenFrom)
_, err := EMLToMsgFromReader(mailbuf)
if err == nil {
t.Error("EML from Reader with broken FROM was supposed to fail, but didn't")
}
mailbuf.Reset()
mailbuf.WriteString(exampleMailPlainBrokenHeader)
_, err = EMLToMsgFromReader(mailbuf)
if err == nil {
t.Error("EML from Reader with broken header was supposed to fail, but didn't")
}
mailbuf.Reset()
mailbuf.WriteString(exampleMailPlainB64BrokenBody)
_, err = EMLToMsgFromReader(mailbuf)
if err == nil {
t.Error("EML from Reader with broken body was supposed to fail, but didn't")
}
mailbuf.Reset()
mailbuf.WriteString(exampleMailPlainBrokenBody)
_, err = EMLToMsgFromReader(mailbuf)
if err == nil {
t.Error("EML from Reader with broken body was supposed to fail, but didn't")
}
mailbuf.Reset()
mailbuf.WriteString(exampleMailPlainUnknownContentType)
_, err = EMLToMsgFromReader(mailbuf)
if err == nil {
t.Error("EML from Reader with unknown content type was supposed to fail, but didn't")
}
mailbuf.Reset()
mailbuf.WriteString(exampleMailPlainNoContentType)
_, err = EMLToMsgFromReader(mailbuf)
if err == nil {
t.Error("EML from Reader with no content type was supposed to fail, but didn't")
}
mailbuf.Reset()
mailbuf.WriteString(exampleMailPlainUnsupportedTransferEnc)
_, err = EMLToMsgFromReader(mailbuf)
if err == nil {
t.Error("EML from Reader with unsupported Transer-Encoding was supposed to fail, but didn't")
}
}
func TestEMLToMsgFromFileFailing(t *testing.T) {
tempDir, tempFile, err := stringToTempFile(exampleMailPlainBrokenFrom, "testmail")
_, err = EMLToMsgFromFile(tempFile)
if err == nil {
t.Error("EML from Reader with broken FROM was supposed to fail, but didn't")
}
if err = os.RemoveAll(tempDir); err != nil {
t.Error("failed to remove temp dir:", err)
}
tempDir, tempFile, err = stringToTempFile(exampleMailPlainBrokenHeader, "testmail")
_, err = EMLToMsgFromFile(tempFile)
if err == nil {
t.Error("EML from Reader with broken header was supposed to fail, but didn't")
}
if err = os.RemoveAll(tempDir); err != nil {
t.Error("failed to remove temp dir:", err)
}
tempDir, tempFile, err = stringToTempFile(exampleMailPlainB64BrokenBody, "testmail")
_, err = EMLToMsgFromFile(tempFile)
if err == nil {
t.Error("EML from Reader with broken body was supposed to fail, but didn't")
}
if err = os.RemoveAll(tempDir); err != nil {
t.Error("failed to remove temp dir:", err)
}
tempDir, tempFile, err = stringToTempFile(exampleMailPlainBrokenBody, "testmail")
_, err = EMLToMsgFromFile(tempFile)
if err == nil {
t.Error("EML from Reader with broken body was supposed to fail, but didn't")
}
if err = os.RemoveAll(tempDir); err != nil {
t.Error("failed to remove temp dir:", err)
}
tempDir, tempFile, err = stringToTempFile(exampleMailPlainUnknownContentType, "testmail")
_, err = EMLToMsgFromFile(tempFile)
if err == nil {
t.Error("EML from Reader with unknown content type was supposed to fail, but didn't")
}
if err = os.RemoveAll(tempDir); err != nil {
t.Error("failed to remove temp dir:", err)
}
tempDir, tempFile, err = stringToTempFile(exampleMailPlainNoContentType, "testmail")
_, err = EMLToMsgFromFile(tempFile)
if err == nil {
t.Error("EML from Reader with no content type was supposed to fail, but didn't")
}
if err = os.RemoveAll(tempDir); err != nil {
t.Error("failed to remove temp dir:", err)
}
tempDir, tempFile, err = stringToTempFile(exampleMailPlainUnsupportedTransferEnc, "testmail")
_, err = EMLToMsgFromFile(tempFile)
if err == nil {
t.Error("EML from Reader with unsupported Transer-Encoding was supposed to fail, but didn't")
}
if err = os.RemoveAll(tempDir); err != nil {
t.Error("failed to remove temp dir:", err)
}
}
func TestEMLToMsgFromStringBrokenDate(t *testing.T) {
_, err := EMLToMsgFromString(exampleMailPlainNoEncInvalidDate)
if err == nil {
@ -291,6 +548,20 @@ func TestEMLToMsgFromStringBrokenDate(t *testing.T) {
}
}
func TestEMLToMsgFromStringBrokenFrom(t *testing.T) {
_, err := EMLToMsgFromString(exampleMailPlainBrokenFrom)
if err == nil {
t.Error("EML with broken FROM was supposed to fail, but didn't")
}
}
func TestEMLToMsgFromStringBrokenTo(t *testing.T) {
_, err := EMLToMsgFromString(exampleMailPlainBrokenTo)
if err == nil {
t.Error("EML with broken TO was supposed to fail, but didn't")
}
}
func TestEMLToMsgFromStringWithAttachment(t *testing.T) {
wantSubject := "Example mail // plain text base64 with attachment"
msg, err := EMLToMsgFromString(exampleMailPlainB64WithAttachment)
@ -322,3 +593,17 @@ func TestEMLToMsgFromStringWithEmbed(t *testing.T) {
1, len(msg.attachments))
}
}
// stringToTempFile is a helper method that will create a temporary file form a give data string
func stringToTempFile(data, name string) (string, string, error) {
tempDir, err := os.MkdirTemp("", fmt.Sprintf("*-%s", name))
if err != nil {
return tempDir, "", fmt.Errorf("failed to create temp dir: %w", err)
}
filePath := fmt.Sprintf("%s/%s", tempDir, name)
err = os.WriteFile(filePath, []byte(data), 0666)
if err != nil {
return tempDir, "", fmt.Errorf("failed to write data to temp file: %w", err)
}
return tempDir, filePath, nil
}