Compare commits

..

No commits in common. "9834c6508d39b985fa8faf9b5d432883ec7aa40c" and "28dc629674d86a0618064503dbbdd02c0baffac8" have entirely different histories.

9 changed files with 2016 additions and 1967 deletions

View file

@ -1094,6 +1094,10 @@ 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 {
@ -1276,6 +1280,12 @@ 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
} }
@ -1292,9 +1302,6 @@ 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
} }
@ -1353,6 +1360,9 @@ 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())
} }
// We can skip the error checking here since netmail.ParseAddressList already performed the if err = addrFunc(addrStrings...); err != nil {
// same address checking that the msg methods do. return fmt.Errorf(`failed to parse %q header: %w`, addrHeader, err)
_ = addrFunc(addrStrings...) }
} }
} }
@ -600,8 +600,6 @@ 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,8 +6,11 @@ package mail
import ( import (
"bytes" "bytes"
"fmt"
"os"
"strings" "strings"
"testing" "testing"
"time"
) )
const ( const (
@ -19,23 +22,6 @@ 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
@ -56,52 +42,6 @@ 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
@ -364,128 +304,6 @@ 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
@ -760,39 +578,6 @@ 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
@ -827,352 +612,8 @@ 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
@ -1180,6 +621,26 @@ 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) {
@ -1548,6 +1009,3 @@ func stringToTempFile(data, name string) (string, string, error) {
} }
return tempDir, filePath, nil return tempDir, filePath, nil
} }
*/

View file

@ -6,160 +6,109 @@ package mail
import "testing" import "testing"
func TestFile(t *testing.T) { // TestFile_SetGetHeader tests the set-/getHeader method of the File object
t.Run("setHeader", func(t *testing.T) { func TestFile_SetGetHeader(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")
contentType, ok := f.Header[HeaderContentType.String()] fi, ok := f.getHeader(HeaderContentType)
if !ok { if !ok {
t.Fatalf("setHeader failed. Expected header %s to be set", HeaderContentType) t.Errorf("getHeader method of File did not return a value")
return
} }
if len(contentType) != 1 { if fi != "text/plain" {
t.Fatalf("setHeader failed. Expected header %s to have one value, got: %d", HeaderContentType, t.Errorf("getHeader returned wrong value. Expected: %s, got: %s", "text/plain", fi)
len(contentType))
} }
if contentType[0] != "text/plain" { fi, ok = f.getHeader(HeaderContentTransferEnc)
t.Fatalf("setHeader failed. Expected header %s to have value %s, got: %s", if ok {
HeaderContentType.String(), "text/plain", contentType[0]) t.Errorf("getHeader method of File did return a value, but wasn't supposed to")
return
} }
}) if fi != "" {
t.Run("getHeader", func(t *testing.T) { t.Errorf("getHeader returned wrong value. Expected: %s, got: %s", "", fi)
f := File{
Name: "testfile.txt",
Header: make(map[string][]string),
} }
f.setHeader(HeaderContentType, "text/plain") }
contentType, ok := f.getHeader(HeaderContentType)
if !ok { // TestFile_WithFileDescription tests the WithFileDescription option
t.Fatalf("setHeader failed. Expected header %s to be set", HeaderContentType) func TestFile_WithFileDescription(t *testing.T) {
}
if contentType != "text/plain" {
t.Fatalf("setHeader failed. Expected header %s to have value %s, got: %s",
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) {
message := NewMsg() m.AttachFile("file.go", WithFileDescription(tt.desc))
message.AttachFile("file.go", WithFileDescription(tt.desc)) al := m.GetAttachments()
attachments := message.GetAttachments() if len(al) <= 0 {
if len(attachments) <= 0 { t.Errorf("AttachFile() failed. Attachment list is empty")
t.Fatalf("failed to retrieve attachments list")
} }
firstAttachment := attachments[0] a := al[0]
if firstAttachment == nil { if a.Desc != tt.desc {
t.Fatalf("failed to retrieve first attachment, got nil") t.Errorf("WithFileDescription() failed. Expected: %s, got: %s", tt.desc, a.Desc)
}
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
id string contentid string
}{ }{
{"Content-ID: test", "test"}, {"File Content-ID: test", "test"},
{"Content-ID: with newline", "test\n"}, {"File Content-ID: empty", ""},
{"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) {
message := NewMsg() m.AttachFile("file.go", WithFileContentID(tt.contentid))
message.AttachFile("file.go", WithFileContentID(tt.id)) al := m.GetAttachments()
attachments := message.GetAttachments() if len(al) <= 0 {
if len(attachments) <= 0 { t.Errorf("AttachFile() failed. Attachment list is empty")
t.Fatalf("failed to retrieve attachments list")
} }
firstAttachment := attachments[0] a := al[0]
if firstAttachment == nil { if a.Header.Get(HeaderContentID.String()) != tt.contentid {
t.Fatalf("failed to retrieve first attachment, got nil") t.Errorf("WithFileContentID() failed. Expected: %s, got: %s", tt.contentid,
} 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
encoding Encoding enc 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) {
message := NewMsg() m.AttachFile("file.go", WithFileEncoding(tt.enc))
message.AttachFile("file.go", WithFileEncoding(tt.encoding)) al := m.GetAttachments()
attachments := message.GetAttachments() if len(al) <= 0 {
if len(attachments) <= 0 { t.Errorf("AttachFile() failed. Attachment list is empty")
t.Fatalf("failed to retrieve attachments list")
} }
firstAttachment := attachments[0] a := al[0]
if firstAttachment == nil { if a.Enc != tt.want {
t.Fatalf("failed to retrieve first attachment, got nil") t.Errorf("WithFileEncoding() failed. Expected: %s, got: %s", tt.enc, a.Enc)
}
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) {
tests := []struct { tests := []struct {
@ -188,6 +137,3 @@ func TestFile_WithFileContentType(t *testing.T) {
}) })
} }
} }
*/

View file

@ -1,8 +0,0 @@
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

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

View file

@ -1,8 +0,0 @@
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".

View file

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