Compare commits

..

17 commits

Author SHA1 Message Date
9834c6508d
Refactor file_test.go to use subtests
Consolidate multiple test functions into a single TestFile function using subtests. This improves test organization and enhances readability. Added a new test case for testing file name attachment.
2024-10-24 17:09:55 +02:00
75e035c783
Add test cases for various EML parsing scenarios
Introduced a series of test cases for validating EML parsing against different edge cases, including invalid headers, content types, and transfer encodings. Ensured both valid and invalid EML strings are covered to thoroughly test the robustness of the parser.
2024-10-24 16:42:11 +02:00
769783f037
Refactor error handling in eml parser
Removed redundant error checking in address parsing as netmail.ParseAddressList already performs necessary checks. Added a default error return for unsupported content disposition types to improve robustness.
2024-10-24 16:42:00 +02:00
9f1e1976fe
Fix assignment in error handling for EML parsing function
Correct the variable assignment in the `if err` statement to ensure proper error handling. This change eliminates a potential bug where the wrong variable might be used.
2024-10-24 14:49:24 +02:00
887e3cd768
Add EML parsing with new tests and examples
Introduce new EML files with valid and invalid examples. Implement tests for EML parsing from readers and files, checking for both successful parsing and expected failures on invalid inputs.
2024-10-24 13:36:03 +02:00
127cfdf2bc
Fix error variable declaration in eml.go
The error variable declaration has been corrected from "if err := parseEML(...)" to "if err = parseEML(...)". This change ensures consistency with the rest of the error handling code in the file.
2024-10-24 13:20:09 +02:00
7ed23bf01b
Remove outdated client test cases
Removed obsolete and redundant client test cases that were no longer relevant. This cleanup improves code maintainability and readability by eliminating excessive, unused test methods.
2024-10-24 12:53:37 +02:00
0310527eb5
Completed client.go tests
We've now covered 96% of all code. Everything else is not testable for us at this point.
2024-10-24 12:25:13 +02:00
1399a3331a
Refactor and extend client email tests
Refactor existing email sending tests by organizing multiple edge cases and adding robust test coverage. This includes adding checks for invalid sender/recipient addresses, handling DSN support, and ensuring proper client server interactions during failures like DATA init, DATA close, and MAIL FROM.
2024-10-24 12:03:56 +02:00
45ebcb95b3
Remove redundant connection check in send function.
The connection check is performed in the c.Reset call just before the c.checkconn call, making this redundant. Removing it simplifies the code and eliminates unnecessary error handling for connection status. This change helps improve code maintainability.
2024-10-24 12:02:57 +02:00
1519522e5d
Reduce sleep duration in client tests
Decreased sleep time from 300ms to 30ms across multiple tests to improve test execution speed. Added a new test `TestClient_sendSingleMsg` to connect and send an email message, ensuring the robustness of the sending functionality.
2024-10-24 10:50:17 +02:00
3bf1992cab
Improve test conciseness and concurrency handling
Simplified repeated message initialization by introducing a helper function `testMessage(t)`. Enhanced existing tests by adding robust concurrency tests and refined the structure of email sending scenarios.
2024-10-24 10:45:05 +02:00
4a8ac76636
Add nil check for smtpClient in checkConn function
Previously, if smtpClient was nil, the checkConn function would not handle that case. This update ensures that an appropriate error is returned when smtpClient is nil, enhancing the robustness of the client connection checks.
2024-10-24 10:44:40 +02:00
5e3ebcc1a6
Remove redundant connection check in auth function
The checkConn call in the auth function was redundant because the connection is already managed appropriately elsewhere. Removing this unnecessary check simplifies the code and avoids potential duplication of error handling.
2024-10-24 10:12:57 +02:00
040289cea4
Remove hardcoded test credentials and add new auth tests.
Replaced hardcoded SMTP credentials with generic placeholders for improved security. Added new test cases to handle unsupported authentication methods and connections without TLS.
2024-10-24 10:12:43 +02:00
2a2176d700
Add tests for various SMTP authentication methods
Implemented new test cases for different SMTP authentication methods including PLAIN, LOGIN, XOAUTH2, and various SCRAM mechanisms. These tests ensure that the client can correctly handle both successful and failing authentication scenarios, as well as unsupported authentication types.
2024-10-24 09:59:31 +02:00
e442419c18
Remove redundant connection check in tls function
The `tls` function in `client.go` no longer checks for an active connection before executing. This simplifies the code since the connection check is either redundant or already handled elsewhere in the flow.
2024-10-24 09:19:08 +02:00
9 changed files with 1929 additions and 1978 deletions

View file

@ -1094,10 +1094,6 @@ func (c *Client) DialAndSendWithContext(ctx context.Context, messages ...*Msg) e
// - An error if the connection check fails, if no supported authentication method is found, // - An error if the connection check fails, if no supported authentication method is found,
// or if the authentication process fails. // or if the authentication process fails.
func (c *Client) auth() error { func (c *Client) auth() error {
if err := c.checkConn(); err != nil {
return fmt.Errorf("failed to authenticate: %w", err)
}
if c.smtpAuth == nil && c.smtpAuthType != SMTPAuthNoAuth { if c.smtpAuth == nil && c.smtpAuthType != SMTPAuthNoAuth {
hasSMTPAuth, smtpAuthType := c.smtpClient.Extension("AUTH") hasSMTPAuth, smtpAuthType := c.smtpClient.Extension("AUTH")
if !hasSMTPAuth { if !hasSMTPAuth {
@ -1280,12 +1276,6 @@ func (c *Client) sendSingleMsg(message *Msg) error {
affectedMsg: message, affectedMsg: message,
} }
} }
if err = c.checkConn(); err != nil {
return &SendError{
Reason: ErrConnCheck, errlist: []error{err}, isTemp: isTempError(err),
affectedMsg: message,
}
}
return nil return nil
} }
@ -1302,6 +1292,9 @@ func (c *Client) sendSingleMsg(message *Msg) error {
// - An error if there is no active connection, if the NOOP command fails, or if extending // - An error if there is no active connection, if the NOOP command fails, or if extending
// the deadline fails; otherwise, returns nil. // the deadline fails; otherwise, returns nil.
func (c *Client) checkConn() error { func (c *Client) checkConn() error {
if c.smtpClient == nil {
return ErrNoActiveConnection
}
if !c.smtpClient.HasConnection() { if !c.smtpClient.HasConnection() {
return ErrNoActiveConnection return ErrNoActiveConnection
} }
@ -1360,9 +1353,6 @@ func (c *Client) setDefaultHelo() error {
// - An error if there is no active connection, if STARTTLS is required but not supported, // - An error if there is no active connection, if STARTTLS is required but not supported,
// or if there are issues during the TLS handshake; otherwise, returns nil. // or if there are issues during the TLS handshake; otherwise, returns nil.
func (c *Client) tls() error { func (c *Client) tls() error {
if !c.smtpClient.HasConnection() {
return ErrNoActiveConnection
}
if !c.useSSL && c.tlspolicy != NoTLS { if !c.useSSL && c.tlspolicy != NoTLS {
hasStartTLS := false hasStartTLS := false
extension, _ := c.smtpClient.Extension("STARTTLS") extension, _ := c.smtpClient.Extension("STARTTLS")

File diff suppressed because it is too large Load diff

12
eml.go
View file

@ -60,7 +60,7 @@ func EMLToMsgFromReader(reader io.Reader) (*Msg, error) {
return msg, fmt.Errorf("failed to parse EML from reader: %w", err) return msg, fmt.Errorf("failed to parse EML from reader: %w", err)
} }
if err := parseEML(parsedMsg, bodybuf, msg); err != nil { if err = parseEML(parsedMsg, bodybuf, msg); err != nil {
return msg, fmt.Errorf("failed to parse EML contents: %w", err) return msg, fmt.Errorf("failed to parse EML contents: %w", err)
} }
@ -93,7 +93,7 @@ func EMLToMsgFromFile(filePath string) (*Msg, error) {
return msg, fmt.Errorf("failed to parse EML file: %w", err) return msg, fmt.Errorf("failed to parse EML file: %w", err)
} }
if err := parseEML(parsedMsg, bodybuf, msg); err != nil { if err = parseEML(parsedMsg, bodybuf, msg); err != nil {
return msg, fmt.Errorf("failed to parse EML contents: %w", err) return msg, fmt.Errorf("failed to parse EML contents: %w", err)
} }
@ -218,9 +218,9 @@ func parseEMLHeaders(mailHeader *netmail.Header, msg *Msg) error {
for _, addr := range parsedAddrs { for _, addr := range parsedAddrs {
addrStrings = append(addrStrings, addr.String()) addrStrings = append(addrStrings, addr.String())
} }
if err = addrFunc(addrStrings...); err != nil { // We can skip the error checking here since netmail.ParseAddressList already performed the
return fmt.Errorf(`failed to parse %q header: %w`, addrHeader, err) // same address checking that the msg methods do.
} _ = addrFunc(addrStrings...)
} }
} }
@ -600,6 +600,8 @@ func parseEMLAttachmentEmbed(contentDisposition []string, multiPart *multipart.P
if err := msg.EmbedReader(filename, dataReader); err != nil { if err := msg.EmbedReader(filename, dataReader); err != nil {
return fmt.Errorf("failed to embed multipart body: %w", err) return fmt.Errorf("failed to embed multipart body: %w", err)
} }
default:
return errors.New("unsupported content disposition type")
} }
return nil return nil
} }

View file

@ -6,11 +6,8 @@ package mail
import ( import (
"bytes" "bytes"
"fmt"
"os"
"strings" "strings"
"testing" "testing"
"time"
) )
const ( const (
@ -22,6 +19,23 @@ Subject: Saying Hello
Date: Fri, 21 Nov 1997 09:55:06 -0600 Date: Fri, 21 Nov 1997 09:55:06 -0600
Message-ID: <1234@local.machine.example> Message-ID: <1234@local.machine.example>
This is a message just to say hello.
So, "Hello".`
exampleMailRFC5322A11InvalidFrom = `From: §§§§§§§§§
To: Mary Smith <mary@example.net>
Subject: Saying Hello
Date: Fri, 21 Nov 1997 09:55:06 -0600
Message-ID: <1234@local.machine.example>
This is a message just to say hello.
So, "Hello".`
exampleMailInvalidHeader = `From: John Doe <jdoe@machine.example>
To: Mary Smith <mary@example.net>
Inva@id*Header; This is a header
Subject: Saying Hello
Date: Fri, 21 Nov 1997 09:55:06 -0600
Message-ID: <1234@local.machine.example>
This is a message just to say hello. This is a message just to say hello.
So, "Hello".` So, "Hello".`
exampleMailPlainNoEnc = `Date: Wed, 01 Nov 2023 00:00:00 +0000 exampleMailPlainNoEnc = `Date: Wed, 01 Nov 2023 00:00:00 +0000
@ -42,6 +56,52 @@ This is a test mail. Please do not reply to this. Also this line is very long so
should be wrapped. should be wrapped.
Thank your for your business!
The go-mail team
--
This is a signature`
exampleMailPlainInvalidCTE = `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: invalid
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`
exampleMailInvalidContentType = `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; $foo; bar; --invalid--
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! Thank your for your business!
The go-mail team The go-mail team
@ -304,6 +364,128 @@ VGhpcyBpcyBhIHNpbXBsZSB0ZXN0IHRleHQgZmlsZSBhdHRhY2htZW50LgoKSXQgCiAgaGFzCiAg
ICAgc2V2ZXJhbAogICAgICAgICAgICBuZXdsaW5lcwoJICAgICAgICAgICAgYW5kCgkgICAgc3Bh ICAgc2V2ZXJhbAogICAgICAgICAgICBuZXdsaW5lcwoJICAgICAgICAgICAgYW5kCgkgICAgc3Bh
Y2VzCiAgICAgaW4KICBpdAouCgpBcyB3ZWxsIGFzIGFuIGVtb2ppOiDwn5mCCg== Y2VzCiAgICAgaW4KICBpdAouCgpBcyB3ZWxsIGFzIGFuIGVtb2ppOiDwn5mCCg==
--45c75ff528359022eb03679fbe91877d75343f2e1f8193e349deffa33ff7--`
exampleMailPlainB64WithAttachmentNoContentType = `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
RGVhciBDdXN0b21lciwKClRoaXMgaXMgYSB0ZXN0IG1haWwuIFBsZWFzZSBkbyBub3QgcmVwbHkg
dG8gdGhpcy4gQWxzbyB0aGlzIGxpbmUgaXMgdmVyeSBsb25nIHNvIGl0CnNob3VsZCBiZSB3cmFw
cGVkLgoKClRoYW5rIHlvdXIgZm9yIHlvdXIgYnVzaW5lc3MhClRoZSBnby1tYWlsIHRlYW0KCi0t
ClRoaXMgaXMgYSBzaWduYXR1cmU=
--45c75ff528359022eb03679fbe91877d75343f2e1f8193e349deffa33ff7
Content-Disposition: attachment; filename="test.attachment"
Content-Transfer-Encoding: base64
VGhpcyBpcyBhIHNpbXBsZSB0ZXN0IHRleHQgZmlsZSBhdHRhY2htZW50LgoKSXQgCiAgaGFzCiAg
ICAgc2V2ZXJhbAogICAgICAgICAgICBuZXdsaW5lcwoJICAgICAgICAgICAgYW5kCgkgICAgc3Bh
Y2VzCiAgICAgaW4KICBpdAouCgpBcyB3ZWxsIGFzIGFuIGVtb2ppOiDwn5mCCg==
--45c75ff528359022eb03679fbe91877d75343f2e1f8193e349deffa33ff7--`
exampleMailPlainB64WithAttachmentBrokenB64 = `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
dG8gdGhpcy4gQWxzbyB0aGl§§§§§@@@@@XMgdmVyeSBsb25nIHNvIGl0CnNob3VsZCBiZSB3cmFw
cGVkLgoKClRoYW5rIHlvdXIgZm9yIHlvdXIgYnVzaW5lc3MhClRoZSBnby1tYWlsIHRlYW0KCi0t
ClRoaXMgaXMgYSBzaWduYXR1cmU=
--45c75ff528359022eb03679fbe91877d75343f2e1f8193e349deffa33ff7
Content-Disposition: attachment; filename="test.attachment"
Content-Transfer-Encoding: base64
Content-Type: application/octet-stream; name="test.attachment"
VGhpcyBpcyBhIHNpbXBsZSB0ZXN0IHRleHQgZmlsZSBhdHRhY2htZW50LgoKSXQgCiAgaGFzCiAg
ICAgc2V2ZXJhbAogICAg§§§§§@@@@@BuZXdsaW5lcwoJICAgICAgICAgICAgYW5kCgkgICAgc3Bh
Y2VzCiAgICAgaW4KICBpdAouCgpBcyB3ZWxsIGFzIGFuIGVtb2ppOiDwn5mCCg==
--45c75ff528359022eb03679fbe91877d75343f2e1f8193e349deffa33ff7--`
exampleMailPlainB64WithAttachmentInvalidCTE = `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: invalid
Content-Type: text/plain; charset=UTF-8
RGVhciBDdXN0b21lciwKClRoaXMgaXMgYSB0ZXN0IG1haWwuIFBsZWFzZSBkbyBub3QgcmVwbHkg
dG8gdGhpcy4gQWxzbyB0aGlzIGxpbmUgaXMgdmVyeSBsb25nIHNvIGl0CnNob3VsZCBiZSB3cmFw
cGVkLgoKClRoYW5rIHlvdXIgZm9yIHlvdXIgYnVzaW5lc3MhClRoZSBnby1tYWlsIHRlYW0KCi0t
ClRoaXMgaXMgYSBzaWduYXR1cmU=
--45c75ff528359022eb03679fbe91877d75343f2e1f8193e349deffa33ff7
Content-Disposition: attachment; filename="test.attachment"
Content-Transfer-Encoding: invalid
Content-Type: application/octet-stream; name="test.attachment"
VGhpcyBpcyBhIHNpbXBsZSB0ZXN0IHRleHQgZmlsZSBhdHRhY2htZW50LgoKSXQgCiAgaGFzCiAg
ICAgc2V2ZXJhbAogICAgICAgICAgICBuZXdsaW5lcwoJICAgICAgICAgICAgYW5kCgkgICAgc3Bh
Y2VzCiAgICAgaW4KICBpdAouCgpBcyB3ZWxsIGFzIGFuIGVtb2ppOiDwn5mCCg==
--45c75ff528359022eb03679fbe91877d75343f2e1f8193e349deffa33ff7--`
exampleMailPlainB64WithAttachmentInvalidContentType = `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; text/plain @ charset=UTF-8; $foo; bar; --invalid--
VGhpcyBpcyBhIHNpbXBsZSB0ZXN0IHRleHQgZmlsZSBhdHRhY2htZW50LgoKSXQgCiAgaGFzCiAg
ICAgc2V2ZXJhbAogICAgICAgICAgICBuZXdsaW5lcwoJICAgICAgICAgICAgYW5kCgkgICAgc3Bh
Y2VzCiAgICAgaW4KICBpdAouCgpBcyB3ZWxsIGFzIGFuIGVtb2ppOiDwn5mCCg==
--45c75ff528359022eb03679fbe91877d75343f2e1f8193e349deffa33ff7--` --45c75ff528359022eb03679fbe91877d75343f2e1f8193e349deffa33ff7--`
exampleMailPlainB64WithAttachmentNoBoundary = `Date: Wed, 01 Nov 2023 00:00:00 +0000 exampleMailPlainB64WithAttachmentNoBoundary = `Date: Wed, 01 Nov 2023 00:00:00 +0000
MIME-Version: 1.0 MIME-Version: 1.0
@ -578,6 +760,39 @@ Content-Disposition: attachment;
filename="testfile.txt" filename="testfile.txt"
VGhpcyBpcyBhIHRlc3QgaW4gQmFzZTY0 VGhpcyBpcyBhIHRlc3QgaW4gQmFzZTY0
--------------26A45336F6C6196BD8BBA2A2--`
exampleMultiPart7BitBase64BrokenB64 = `Date: Wed, 01 Nov 2023 00:00:00 +0000
MIME-Version: 1.0
Message-ID: <1305604950.683004066175.AAAAAAAAaaaaaaaaB@go-mail.dev>
Subject: Example mail // 7bit with base64 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="------------26A45336F6C6196BD8BBA2A2"
This is a multi-part message in MIME format.
--------------26A45336F6C6196BD8BBA2A2
Content-Type: text/plain; charset=US-ASCII; format=flowed
Content-Transfer-Encoding: 7bit
testtest
testtest
testtest
testtest
testtest
testtest
--------------26A45336F6C6196BD8BBA2A2
Content-Type: text/plain; charset=UTF-8;
name="testfile.txt"
Content-Transfer-Encoding: base64
Content-Disposition: attachment;
filename="testfile.txt"
VGh@@@@§§§§hIHRlc3QgaW4gQmFzZTY0
--------------26A45336F6C6196BD8BBA2A2--` --------------26A45336F6C6196BD8BBA2A2--`
exampleMultiPart8BitBase64 = `Date: Wed, 01 Nov 2023 00:00:00 +0000 exampleMultiPart8BitBase64 = `Date: Wed, 01 Nov 2023 00:00:00 +0000
MIME-Version: 1.0 MIME-Version: 1.0
@ -612,8 +827,352 @@ Content-Disposition: attachment;
VGhpcyBpcyBhIHRlc3QgaW4gQmFzZTY0 VGhpcyBpcyBhIHRlc3QgaW4gQmFzZTY0
--------------26A45336F6C6196BD8BBA2A2--` --------------26A45336F6C6196BD8BBA2A2--`
exampleMailWithInlineEmbed = `Date: Wed, 01 Nov 2023 00:00:00 +0000
MIME-Version: 1.0
Message-ID: <1305604950.683004066175.AAAAAAAAaaaaaaaaB@go-mail.dev>
Subject: Example mail with inline embed
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>
Content-Type: multipart/related; boundary="abc123"
--abc123
Content-Type: text/html; charset=UTF-8
Content-Transfer-Encoding: quoted-printable
<html>
<body>
<p>Hello,</p>
<p>This is an example email with an inline image:</p>
<img src="cid:12345@go-mail.dev" alt="Inline Image">
<p>Best regards,<br>The go-mail team</p>
</body>
</html>
--abc123
Content-Type: image/png
Content-Transfer-Encoding: base64
Content-ID: <12345@go-mail.dev>
Content-Disposition: inline; filename="test.png"
iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQIW2NgYGD4DwABBAEAwS2O
UAAAAABJRU5ErkJggg==
--abc123--`
exampleMailWithInlineEmbedWrongDisposition = `Date: Wed, 01 Nov 2023 00:00:00 +0000
MIME-Version: 1.0
Message-ID: <1305604950.683004066175.AAAAAAAAaaaaaaaaB@go-mail.dev>
Subject: Example mail with inline embed
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>
Content-Type: multipart/related; boundary="abc123"
--abc123
Content-Type: text/html; charset=UTF-8
Content-Transfer-Encoding: quoted-printable
<html>
<body>
<p>Hello,</p>
<p>This is an example email with an inline image:</p>
<img src="cid:12345@go-mail.dev" alt="Inline Image">
<p>Best regards,<br>The go-mail team</p>
</body>
</html>
--abc123
Content-Type: image/png
Content-Transfer-Encoding: base64
Content-ID: <12345@go-mail.dev>
Content-Disposition: broken; filename="test.png"
iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQIW2NgYGD4DwABBAEAwS2O
UAAAAABJRU5ErkJggg==
--abc123--`
) )
func TestEMLToMsgFromReader(t *testing.T) {
t.Run("EMLToMsgFromReader via EMLToMsgFromString, check subject and encoding", func(t *testing.T) {
tests := []struct {
name string
emlString string
wantEncoding Encoding
wantSubject string
}{
{
"RFC5322 A1.1 example mail", exampleMailRFC5322A11, EncodingUSASCII,
"Saying Hello"},
{
"Plain text no encoding (7bit)", exampleMailPlain7Bit, EncodingUSASCII,
"Example mail // plain text without encoding",
},
{
"Plain text no encoding", exampleMailPlainNoEnc, NoEncoding,
"Example mail // plain text without encoding",
},
{
"Plain text quoted-printable", exampleMailPlainQP, EncodingQP,
"Example mail // plain text quoted-printable",
},
{
"Plain text base64", exampleMailPlainB64, EncodingB64,
"Example mail // plain text base64",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
parsed, err := EMLToMsgFromString(tt.emlString)
if err != nil {
t.Fatalf("failed to parse EML string: %s", err)
}
if parsed.Encoding() != tt.wantEncoding.String() {
t.Errorf("failed to parse EML string: want encoding %s, got %s", tt.wantEncoding,
parsed.Encoding())
}
gotSubject, ok := parsed.genHeader[HeaderSubject]
if !ok {
t.Fatalf("failed to parse EML string. No subject header found")
}
if len(gotSubject) != 1 {
t.Fatalf("failed to parse EML string, more than one subject header found")
}
if !strings.EqualFold(gotSubject[0], tt.wantSubject) {
t.Errorf("failed to parse EML string: want subject %s, got %s", tt.wantSubject,
gotSubject[0])
}
})
}
})
t.Run("EMLToMsgFromReader fails on reader", func(t *testing.T) {
emlReader := bytes.NewBufferString("invalid")
if _, err := EMLToMsgFromReader(emlReader); err == nil {
t.Errorf("EML parsing with invalid EML string should fail")
}
})
t.Run("EMLToMsgFromReader fails on parseEML", func(t *testing.T) {
emlReader := bytes.NewBufferString(exampleMailRFC5322A11InvalidFrom)
if _, err := EMLToMsgFromReader(emlReader); err == nil {
t.Errorf("EML parsing with invalid EML string should fail")
}
})
t.Run("EMLToMsgFromReader via EMLToMsgFromString on different examples", func(t *testing.T) {
tests := []struct {
name string
emlString string
shouldFail bool
}{
{
name: "Valid RFC 5322 Example",
emlString: exampleMailRFC5322A11,
shouldFail: false,
},
{
name: "Invalid From Header (RFC 5322)",
emlString: exampleMailRFC5322A11InvalidFrom,
shouldFail: true,
},
{
name: "Invalid Header",
emlString: exampleMailInvalidHeader,
shouldFail: true,
},
{
name: "Plain broken Content-Type",
emlString: exampleMailInvalidContentType,
shouldFail: true,
},
{
name: "Plain No Encoding",
emlString: exampleMailPlainNoEnc,
shouldFail: false,
},
{
name: "Plain invalid CTE",
emlString: exampleMailPlainInvalidCTE,
shouldFail: true,
},
{
name: "Plain 7bit",
emlString: exampleMailPlain7Bit,
shouldFail: false,
},
{
name: "Broken Body Base64",
emlString: exampleMailPlainBrokenBody,
shouldFail: true,
},
{
name: "Unknown Content Type",
emlString: exampleMailPlainUnknownContentType,
shouldFail: true,
},
{
name: "Broken Header",
emlString: exampleMailPlainBrokenHeader,
shouldFail: true,
},
{
name: "Broken From Header",
emlString: exampleMailPlainBrokenFrom,
shouldFail: true,
},
{
name: "Broken To Header",
emlString: exampleMailPlainBrokenTo,
shouldFail: true,
},
{
name: "Invalid Date",
emlString: exampleMailPlainNoEncInvalidDate,
shouldFail: true,
},
{
name: "No Date Header",
emlString: exampleMailPlainNoEncNoDate,
shouldFail: false,
},
{
name: "Quoted Printable Encoding",
emlString: exampleMailPlainQP,
shouldFail: false,
},
{
name: "Unsupported Transfer Encoding",
emlString: exampleMailPlainUnsupportedTransferEnc,
shouldFail: true,
},
{
name: "Base64 Encoding",
emlString: exampleMailPlainB64,
shouldFail: false,
},
{
name: "Base64 with Attachment",
emlString: exampleMailPlainB64WithAttachment,
shouldFail: false,
},
{
name: "Base64 with Attachment no content types",
emlString: exampleMailPlainB64WithAttachmentNoContentType,
shouldFail: true,
},
{
name: "Multipart Base64 with Attachment broken Base64",
emlString: exampleMailPlainB64WithAttachmentBrokenB64,
shouldFail: true,
},
{
name: "Base64 with Attachment with invalid content type in attachment",
emlString: exampleMailPlainB64WithAttachmentInvalidContentType,
shouldFail: true,
},
{
name: "Base64 with Attachment with invalid CTE in attachment",
emlString: exampleMailPlainB64WithAttachmentInvalidCTE,
shouldFail: true,
},
{
name: "Base64 with Attachment No Boundary",
emlString: exampleMailPlainB64WithAttachmentNoBoundary,
shouldFail: true,
},
{
name: "Broken Body Base64",
emlString: exampleMailPlainB64BrokenBody,
shouldFail: true,
},
{
name: "Base64 with Embedded Image",
emlString: exampleMailPlainB64WithEmbed,
shouldFail: false,
},
{
name: "Base64 with Embed No Content-ID",
emlString: exampleMailPlainB64WithEmbedNoContentID,
shouldFail: false,
},
{
name: "Multipart Mixed with Attachment, Embed, and Alternative Part",
emlString: exampleMailMultipartMixedAlternativeRelated,
shouldFail: false,
},
{
name: "Multipart 7bit Base64",
emlString: exampleMultiPart7BitBase64,
shouldFail: false,
},
{
name: "Multipart 7bit Base64 with broken Base64",
emlString: exampleMultiPart7BitBase64BrokenB64,
shouldFail: true,
},
{
name: "Multipart 8bit Base64",
emlString: exampleMultiPart8BitBase64,
shouldFail: false,
},
{
name: "Multipart with inline embed",
emlString: exampleMailWithInlineEmbed,
shouldFail: false,
},
{
name: "Multipart with inline embed disposition broken",
emlString: exampleMailWithInlineEmbedWrongDisposition,
shouldFail: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := EMLToMsgFromString(tt.emlString)
if tt.shouldFail && err == nil {
t.Errorf("parsing of EML was supposed to fail, but it did not")
}
if !tt.shouldFail && err != nil {
t.Errorf("parsing of EML failed: %s", err)
}
})
}
})
}
func TestEMLToMsgFromFile(t *testing.T) {
t.Run("EMLToMsgFromFile succeeds", func(t *testing.T) {
parsed, err := EMLToMsgFromFile("testdata/RFC5322-A1-1.eml")
if err != nil {
t.Fatalf("EMLToMsgFromFile failed: %s ", err)
}
if parsed.Encoding() != EncodingUSASCII.String() {
t.Errorf("EMLToMsgFromFile failed: want encoding %s, got %s", EncodingUSASCII,
parsed.Encoding())
}
gotSubject, ok := parsed.genHeader[HeaderSubject]
if !ok {
t.Fatalf("failed to parse EML string. No subject header found")
}
if len(gotSubject) != 1 {
t.Fatalf("failed to parse EML string, more than one subject header found")
}
if !strings.EqualFold(gotSubject[0], "Saying Hello") {
t.Errorf("failed to parse EML string: want subject %s, got %s", "Saying Hello",
gotSubject[0])
}
})
t.Run("EMLToMsgFromFile fails on file not found", func(t *testing.T) {
if _, err := EMLToMsgFromFile("testdata/not-existing.eml"); err == nil {
t.Errorf("EMLToMsgFromFile with invalid file should fail")
}
})
t.Run("EMLToMsgFromFile fails on parseEML", func(t *testing.T) {
if _, err := EMLToMsgFromFile("testdata/RFC5322-A1-1-invalid-from.eml"); err == nil {
t.Errorf("EMLToMsgFromFile with invalid EML message should fail")
}
})
}
/*
func TestEMLToMsgFromString(t *testing.T) { func TestEMLToMsgFromString(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
@ -621,26 +1180,6 @@ func TestEMLToMsgFromString(t *testing.T) {
enc string enc string
sub string sub string
}{ }{
{
"RFC5322 A1.1", exampleMailRFC5322A11, "7bit",
"Saying Hello",
},
{
"Plain text no encoding (7bit)", exampleMailPlain7Bit, "7bit",
"Example mail // plain text without encoding",
},
{
"Plain text no encoding", exampleMailPlainNoEnc, "8bit",
"Example mail // plain text without encoding",
},
{
"Plain text quoted-printable", exampleMailPlainQP, "quoted-printable",
"Example mail // plain text quoted-printable",
},
{
"Plain text base64", exampleMailPlainB64, "base64",
"Example mail // plain text base64",
},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
@ -1009,3 +1548,6 @@ func stringToTempFile(data, name string) (string, string, error) {
} }
return tempDir, filePath, nil return tempDir, filePath, nil
} }
*/

View file

@ -6,108 +6,159 @@ package mail
import "testing" import "testing"
// TestFile_SetGetHeader tests the set-/getHeader method of the File object func TestFile(t *testing.T) {
func TestFile_SetGetHeader(t *testing.T) { t.Run("setHeader", func(t *testing.T) {
f := File{ f := File{
Name: "testfile.txt", Name: "testfile.txt",
Header: make(map[string][]string), Header: make(map[string][]string),
} }
f.setHeader(HeaderContentType, "text/plain") f.setHeader(HeaderContentType, "text/plain")
fi, ok := f.getHeader(HeaderContentType) contentType, ok := f.Header[HeaderContentType.String()]
if !ok { if !ok {
t.Errorf("getHeader method of File did not return a value") t.Fatalf("setHeader failed. Expected header %s to be set", HeaderContentType)
return
} }
if fi != "text/plain" { if len(contentType) != 1 {
t.Errorf("getHeader returned wrong value. Expected: %s, got: %s", "text/plain", fi) t.Fatalf("setHeader failed. Expected header %s to have one value, got: %d", HeaderContentType,
len(contentType))
} }
fi, ok = f.getHeader(HeaderContentTransferEnc) if contentType[0] != "text/plain" {
if ok { t.Fatalf("setHeader failed. Expected header %s to have value %s, got: %s",
t.Errorf("getHeader method of File did return a value, but wasn't supposed to") HeaderContentType.String(), "text/plain", contentType[0])
return
} }
if fi != "" { })
t.Errorf("getHeader returned wrong value. Expected: %s, got: %s", "", fi) t.Run("getHeader", func(t *testing.T) {
f := File{
Name: "testfile.txt",
Header: make(map[string][]string),
} }
f.setHeader(HeaderContentType, "text/plain")
contentType, ok := f.getHeader(HeaderContentType)
if !ok {
t.Fatalf("setHeader failed. Expected header %s to be set", HeaderContentType)
} }
if contentType != "text/plain" {
// TestFile_WithFileDescription tests the WithFileDescription option t.Fatalf("setHeader failed. Expected header %s to have value %s, got: %s",
func TestFile_WithFileDescription(t *testing.T) { HeaderContentType.String(), "text/plain", contentType)
}
})
t.Run("WithFileDescription", func(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
desc string desc string
}{ }{
{"File description: test", "test"}, {"File description: test", "test"},
{"File description: with newline", "test\n"},
{"File description: empty", ""}, {"File description: empty", ""},
} }
for _, tt := range tests { for _, tt := range tests {
m := NewMsg()
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
m.AttachFile("file.go", WithFileDescription(tt.desc)) message := NewMsg()
al := m.GetAttachments() message.AttachFile("file.go", WithFileDescription(tt.desc))
if len(al) <= 0 { attachments := message.GetAttachments()
t.Errorf("AttachFile() failed. Attachment list is empty") if len(attachments) <= 0 {
t.Fatalf("failed to retrieve attachments list")
} }
a := al[0] firstAttachment := attachments[0]
if a.Desc != tt.desc { if firstAttachment == nil {
t.Errorf("WithFileDescription() failed. Expected: %s, got: %s", tt.desc, a.Desc) t.Fatalf("failed to retrieve first attachment, got nil")
}
if firstAttachment.Desc != tt.desc {
t.Errorf("WithFileDescription() failed. Expected: %s, got: %s", tt.desc,
firstAttachment.Desc)
} }
}) })
} }
} })
t.Run("WithFileContentID", func(t *testing.T) {
// TestFile_WithContentID tests the WithFileContentID option
func TestFile_WithContentID(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
contentid string id string
}{ }{
{"File Content-ID: test", "test"}, {"Content-ID: test", "test"},
{"File Content-ID: empty", ""}, {"Content-ID: with newline", "test\n"},
{"Content-ID: empty", ""},
} }
for _, tt := range tests { for _, tt := range tests {
m := NewMsg()
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
m.AttachFile("file.go", WithFileContentID(tt.contentid)) message := NewMsg()
al := m.GetAttachments() message.AttachFile("file.go", WithFileContentID(tt.id))
if len(al) <= 0 { attachments := message.GetAttachments()
t.Errorf("AttachFile() failed. Attachment list is empty") if len(attachments) <= 0 {
t.Fatalf("failed to retrieve attachments list")
} }
a := al[0] firstAttachment := attachments[0]
if a.Header.Get(HeaderContentID.String()) != tt.contentid { if firstAttachment == nil {
t.Errorf("WithFileContentID() failed. Expected: %s, got: %s", tt.contentid, t.Fatalf("failed to retrieve first attachment, got nil")
a.Header.Get(HeaderContentID.String())) }
contentID := firstAttachment.Header.Get(HeaderContentID.String())
if contentID != tt.id {
t.Errorf("WithFileContentID() failed. Expected: %s, got: %s", tt.id,
contentID)
} }
}) })
} }
} })
t.Run("WithFileEncoding", func(t *testing.T) {
// TestFile_WithFileEncoding tests the WithFileEncoding option
func TestFile_WithFileEncoding(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
enc Encoding encoding Encoding
want Encoding want Encoding
}{ }{
{"File encoding: US-ASCII", EncodingUSASCII, EncodingUSASCII},
{"File encoding: 8bit raw", NoEncoding, NoEncoding}, {"File encoding: 8bit raw", NoEncoding, NoEncoding},
{"File encoding: Base64", EncodingB64, EncodingB64}, {"File encoding: Base64", EncodingB64, EncodingB64},
{"File encoding: quoted-printable (not allowed)", EncodingQP, ""}, {"File encoding: quoted-printable (not allowed)", EncodingQP, ""},
} }
for _, tt := range tests { for _, tt := range tests {
m := NewMsg()
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
m.AttachFile("file.go", WithFileEncoding(tt.enc)) message := NewMsg()
al := m.GetAttachments() message.AttachFile("file.go", WithFileEncoding(tt.encoding))
if len(al) <= 0 { attachments := message.GetAttachments()
t.Errorf("AttachFile() failed. Attachment list is empty") if len(attachments) <= 0 {
t.Fatalf("failed to retrieve attachments list")
} }
a := al[0] firstAttachment := attachments[0]
if a.Enc != tt.want { if firstAttachment == nil {
t.Errorf("WithFileEncoding() failed. Expected: %s, got: %s", tt.enc, a.Enc) t.Fatalf("failed to retrieve first attachment, got nil")
}
if firstAttachment.Enc != tt.want {
t.Errorf("WithFileEncoding() failed. Expected: %s, got: %s", tt.want, firstAttachment.Enc)
} }
}) })
} }
})
t.Run("WithFileName", func(t *testing.T) {
tests := []struct {
name string
fileName string
}{
{"File name: test", "test"},
{"File name: with newline", "test\n"},
{"File name: empty", ""},
} }
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
message := NewMsg()
message.AttachFile("file.go", WithFileName(tt.fileName))
attachments := message.GetAttachments()
if len(attachments) <= 0 {
t.Fatalf("failed to retrieve attachments list")
}
firstAttachment := attachments[0]
if firstAttachment == nil {
t.Fatalf("failed to retrieve first attachment, got nil")
}
if firstAttachment.Name != tt.fileName {
t.Errorf("WithFileName() failed. Expected: %s, got: %s", tt.fileName,
firstAttachment.Name)
}
})
}
})
}
/*
// TestFile_WithFileContentType tests the WithFileContentType option // TestFile_WithFileContentType tests the WithFileContentType option
func TestFile_WithFileContentType(t *testing.T) { func TestFile_WithFileContentType(t *testing.T) {
@ -137,3 +188,6 @@ func TestFile_WithFileContentType(t *testing.T) {
}) })
} }
} }
*/

View file

@ -0,0 +1,8 @@
From: §§§§§§§§
To: Mary Smith <mary@example.net>
Subject: Saying Hello
Date: Fri, 21 Nov 1997 09:55:06 -0600
Message-ID: <1234@local.machine.example>
This is a message just to say hello.
So, "Hello".

View file

@ -0,0 +1,3 @@
// SPDX-FileCopyrightText: Copyright (c) 2022-2024 The go-mail Authors
//
// SPDX-License-Identifier: MIT

8
testdata/RFC5322-A1-1.eml vendored Normal file
View file

@ -0,0 +1,8 @@
From: John Doe <jdoe@machine.example>
To: Mary Smith <mary@example.net>
Subject: Saying Hello
Date: Fri, 21 Nov 1997 09:55:06 -0600
Message-ID: <1234@local.machine.example>
This is a message just to say hello.
So, "Hello".

3
testdata/RFC5322-A1-1.eml.license vendored Normal file
View file

@ -0,0 +1,3 @@
// SPDX-FileCopyrightText: Copyright (c) 2022-2024 The go-mail Authors
//
// SPDX-License-Identifier: MIT