1
0
Fork 0
golang-github-domodwyer-mai.../mime_test.go
Daniel Baumann cb9cbb7a25
Adding upstream version 3.6.2.
Signed-off-by: Daniel Baumann <daniel@debian.org>
2025-05-22 21:13:13 +02:00

657 lines
18 KiB
Go

package mailyak
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"mime"
"mime/multipart"
"net/mail"
"regexp"
"strings"
"testing"
"time"
)
// TestMailYakFromHeader ensures the fromHeader method returns valid headers
func TestMailYakFromHeader(t *testing.T) {
t.Parallel()
tests := []struct {
// Test description.
name string
// Receiver fields.
rfromAddr string
rfromName string
// Expected results.
want string
}{
{
"With name",
"dom@itsallbroken.com",
"Dom",
"From: Dom <dom@itsallbroken.com>\r\n",
},
{
"Without name",
"dom@itsallbroken.com",
"",
"From: dom@itsallbroken.com\r\n",
},
{
"Without either",
"",
"",
"From: \r\n",
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
m := MailYak{
fromAddr: tt.rfromAddr,
fromName: tt.rfromName,
}
if got := m.fromHeader(); got != tt.want {
t.Errorf("%q. MailYak.fromHeader() = %v, want %v", tt.name, got, tt.want)
}
})
}
}
// TestMailYakWriteHeaders ensures the MIME-Version, Date, Reply-To, From, To and
// Subject headers are correctly wrote
func TestMailYakWriteHeaders(t *testing.T) {
t.Parallel()
now := time.Now().Format(time.RFC1123Z)
tests := []struct {
// Test description.
name string
// Receiver fields.
rtoAddrs []string
rccAddrs []string
rbccAddrs []string
rsubject string
rreplyTo string
rwriteBccHeader bool
// Expected results.
wantBuf string
}{
{
"All fields",
[]string{"test@itsallbroken.com"},
[]string{},
[]string{},
"Test",
"help@itsallbroken.com",
true,
"From: Dom <dom@itsallbroken.com>\r\nMIME-Version: 1.0\r\nDate: " + now + "\r\nReply-To: help@itsallbroken.com\r\nSubject: Test\r\nTo: test@itsallbroken.com\r\n",
},
{
"No reply-to",
[]string{"test@itsallbroken.com"},
[]string{},
[]string{},
"",
"",
true,
"From: Dom <dom@itsallbroken.com>\r\nMIME-Version: 1.0\r\nDate: " + now + "\r\nSubject: \r\nTo: test@itsallbroken.com\r\n",
},
{
"Multiple To addresses",
[]string{"test@itsallbroken.com", "repairs@itsallbroken.com"},
[]string{},
[]string{},
"",
"",
true,
"From: Dom <dom@itsallbroken.com>\r\nMIME-Version: 1.0\r\nDate: " + now + "\r\nSubject: \r\nTo: test@itsallbroken.com,repairs@itsallbroken.com\r\n",
},
{
"Single Cc address, Multiple To addresses",
[]string{"test@itsallbroken.com", "repairs@itsallbroken.com"},
[]string{"cc@itsallbroken.com"},
[]string{},
"",
"",
true,
"From: Dom <dom@itsallbroken.com>\r\nMIME-Version: 1.0\r\nDate: " + now + "\r\nSubject: \r\nTo: test@itsallbroken.com,repairs@itsallbroken.com\r\nCC: cc@itsallbroken.com\r\n",
},
{
"Multiple Cc addresses, Multiple To addresses",
[]string{"test@itsallbroken.com", "repairs@itsallbroken.com"},
[]string{"cc1@itsallbroken.com", "cc2@itsallbroken.com"},
[]string{},
"",
"",
true,
"From: Dom <dom@itsallbroken.com>\r\nMIME-Version: 1.0\r\nDate: " + now + "\r\nSubject: \r\nTo: test@itsallbroken.com,repairs@itsallbroken.com\r\nCC: cc1@itsallbroken.com,cc2@itsallbroken.com\r\n",
},
{
"Single Bcc address, Multiple To addresses",
[]string{"test@itsallbroken.com", "repairs@itsallbroken.com"},
[]string{},
[]string{"bcc@itsallbroken.com"},
"",
"",
true,
"From: Dom <dom@itsallbroken.com>\r\nMIME-Version: 1.0\r\nDate: " + now + "\r\nSubject: \r\nTo: test@itsallbroken.com,repairs@itsallbroken.com\r\nBCC: bcc@itsallbroken.com\r\n",
},
{
"Multiple Bcc addresses, Multiple To addresses",
[]string{"test@itsallbroken.com", "repairs@itsallbroken.com"},
[]string{},
[]string{"bcc1@itsallbroken.com", "bcc2@itsallbroken.com"},
"",
"",
true,
"From: Dom <dom@itsallbroken.com>\r\nMIME-Version: 1.0\r\nDate: " + now + "\r\nSubject: \r\nTo: test@itsallbroken.com,repairs@itsallbroken.com\r\nBCC: bcc1@itsallbroken.com,bcc2@itsallbroken.com\r\n",
},
{
"Multiple Bcc addresses, Multiple To addresses",
[]string{"test@itsallbroken.com", "repairs@itsallbroken.com"},
[]string{},
[]string{"bcc1@itsallbroken.com", "bcc2@itsallbroken.com"},
"",
"",
false,
"From: Dom <dom@itsallbroken.com>\r\nMIME-Version: 1.0\r\nDate: " + now + "\r\nSubject: \r\nTo: test@itsallbroken.com,repairs@itsallbroken.com\r\n",
},
{
"All together now",
[]string{"test@itsallbroken.com", "repairs@itsallbroken.com"},
[]string{"cc1@itsallbroken.com", "cc2@itsallbroken.com"},
[]string{"bcc1@itsallbroken.com", "bcc2@itsallbroken.com"},
"",
"",
true,
"From: Dom <dom@itsallbroken.com>\r\nMIME-Version: 1.0\r\nDate: " + now + "\r\nSubject: \r\nTo: test@itsallbroken.com,repairs@itsallbroken.com\r\nCC: cc1@itsallbroken.com,cc2@itsallbroken.com\r\nBCC: bcc1@itsallbroken.com,bcc2@itsallbroken.com\r\n",
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
m := MailYak{
toAddrs: tt.rtoAddrs,
subject: tt.rsubject,
fromAddr: "dom@itsallbroken.com",
fromName: "Dom",
replyTo: tt.rreplyTo,
ccAddrs: tt.rccAddrs,
bccAddrs: tt.rbccAddrs,
writeBccHeader: tt.rwriteBccHeader,
date: now,
}
buf := &bytes.Buffer{}
if err := m.writeHeaders(buf); err != nil {
t.Fatal(err)
}
if gotBuf := buf.String(); gotBuf != tt.wantBuf {
t.Errorf("%q. MailYak.writeHeaders() = %v, want %v", tt.name, gotBuf, tt.wantBuf)
}
})
}
}
// TestMailYakWriteBody ensures the correct MIME parts are wrote for the body
func TestMailYakWriteBody(t *testing.T) {
t.Parallel()
tests := []struct {
// Test description.
name string
// Receiver fields.
rHTML string
rPlain string
// Parameters.
boundary string
// Expected results.
wantW string
wantErr bool
}{
{
"HTML",
"HTML",
"",
"t",
"--t\r\nContent-Transfer-Encoding: quoted-printable\r\nContent-Type: text/html; charset=UTF-8\r\n\r\nHTML\r\n--t--\r\n",
false,
},
{
"Plain text",
"",
"Plain",
"t",
"--t\r\nContent-Transfer-Encoding: quoted-printable\r\nContent-Type: text/plain; charset=UTF-8\r\n\r\nPlain\r\n--t--\r\n",
false,
},
{
"Both",
"HTML",
"Plain",
"t",
"--t\r\nContent-Transfer-Encoding: quoted-printable\r\nContent-Type: text/plain; charset=UTF-8\r\n\r\nPlain\r\n--t\r\nContent-Transfer-Encoding: quoted-printable\r\nContent-Type: text/html; charset=UTF-8\r\n\r\nHTML\r\n--t--\r\n",
false,
},
{
"Both with long lines",
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
"t",
"--t\r\nContent-Transfer-Encoding: quoted-printable\r\nContent-Type: text/plain; charset=UTF-8\r\n\r\nLorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tem=\r\npor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, q=\r\nuis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo cons=\r\nequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillu=\r\nm dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non pr=\r\noident, sunt in culpa qui officia deserunt mollit anim id est laborum.\r\n--t\r\nContent-Transfer-Encoding: quoted-printable\r\nContent-Type: text/html; charset=UTF-8\r\n\r\nLorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tem=\r\npor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, q=\r\nuis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo cons=\r\nequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillu=\r\nm dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non pr=\r\noident, sunt in culpa qui officia deserunt mollit anim id est laborum.\r\n--t--\r\n",
false,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
m := MailYak{}
m.HTML().WriteString(tt.rHTML)
m.Plain().WriteString(tt.rPlain)
w := &bytes.Buffer{}
if err := m.writeBody(w, tt.boundary); (err != nil) != tt.wantErr {
t.Fatalf("%q. MailYak.writeBody() error = %v, wantErr %v", tt.name, err, tt.wantErr)
}
if gotW := w.String(); gotW != tt.wantW {
t.Errorf("%q. MailYak.writeBody() = %v, want %v", tt.name, gotW, tt.wantW)
}
})
}
}
// TestMailYakBuildMime tests all the other mime-related bits combine in a sane way
func TestMailYakBuildMime(t *testing.T) {
t.Parallel()
now := time.Now().Format(time.RFC1123Z)
tests := []struct {
// Test description.
name string
// Receiver fields.
rHTML []byte
rPlain []byte
rtoAddrs []string
rsubject string
rfromAddr string
rfromName string
rreplyTo string
rAttachemnt string
// Expected results.
want string
wantErr bool
}{
{
"Empty",
[]byte{},
[]byte{},
[]string{""},
"",
"",
"",
"",
"",
"From: \r\nMIME-Version: 1.0\r\nDate: " + now + "\r\nSubject: \r\nTo: \r\n\r\n",
false,
},
{
"HTML",
[]byte("HTML"),
[]byte{},
[]string{""},
"",
"",
"",
"",
"",
"From: \r\nMIME-Version: 1.0\r\nDate: " + now + "\r\nSubject: \r\nTo: \r\nContent-Type: multipart/mixed;\r\n\tboundary=\"mixed\"; charset=UTF-8\r\n\r\n--mixed\r\nContent-Type: multipart/alternative;\r\n\tboundary=\"alt\"\r\n\r\n--alt\r\nContent-Transfer-Encoding: quoted-printable\r\nContent-Type: text/html; charset=UTF-8\r\n\r\nHTML\r\n--alt--\r\n\r\n--mixed--\r\n",
false,
},
{
"Plain",
[]byte{},
[]byte("Plain"),
[]string{""},
"",
"",
"",
"",
"",
"From: \r\nMIME-Version: 1.0\r\nDate: " + now + "\r\nSubject: \r\nTo: \r\nContent-Type: multipart/mixed;\r\n\tboundary=\"mixed\"; charset=UTF-8\r\n\r\n--mixed\r\nContent-Type: multipart/alternative;\r\n\tboundary=\"alt\"\r\n\r\n--alt\r\nContent-Transfer-Encoding: quoted-printable\r\nContent-Type: text/plain; charset=UTF-8\r\n\r\nPlain\r\n--alt--\r\n\r\n--mixed--\r\n",
false,
},
{
"Attachemnt",
[]byte{},
[]byte{},
[]string{""},
"",
"",
"",
"",
"attachment",
"From: \r\nMIME-Version: 1.0\r\nDate: " + now + "\r\nSubject: \r\nTo: \r\nContent-Type: multipart/mixed;\r\n\tboundary=\"mixed\"; charset=UTF-8\r\n\r\n--mixed\r\nContent-Disposition: attachment;\n\tfilename=\"testAttachment\"; name=\"testAttachment\"\r\nContent-ID: <testAttachment>\r\nContent-Transfer-Encoding: base64\r\nContent-Type: text/plain; charset=utf-8;\n\tfilename=\"testAttachment\"; name=\"testAttachment\"\r\n\r\nYXR0YWNobWVudA==\r\n--mixed--\r\n",
false,
},
{
"Reply-To",
[]byte{},
[]byte{},
[]string{""},
"",
"",
"",
"reply",
"",
"From: \r\nMIME-Version: 1.0\r\nDate: " + now + "\r\nReply-To: reply\r\nSubject: \r\nTo: \r\n\r\n",
false,
},
{
"From name",
[]byte{},
[]byte{},
[]string{""},
"",
"",
"name",
"",
"",
"From: name <>\r\nMIME-Version: 1.0\r\nDate: " + now + "\r\nSubject: \r\nTo: \r\n\r\n",
false,
},
{
"From name + address",
[]byte{},
[]byte{},
[]string{""},
"",
"addr",
"name",
"",
"",
"From: name <addr>\r\nMIME-Version: 1.0\r\nDate: " + now + "\r\nSubject: \r\nTo: \r\n\r\n",
false,
},
{
"From",
[]byte{},
[]byte{},
[]string{""},
"",
"from",
"",
"",
"",
"From: from\r\nMIME-Version: 1.0\r\nDate: " + now + "\r\nSubject: \r\nTo: \r\n\r\n",
false,
},
{
"Subject",
[]byte{},
[]byte{},
[]string{""},
"subject",
"",
"",
"",
"",
"From: \r\nMIME-Version: 1.0\r\nDate: " + now + "\r\nSubject: subject\r\nTo: \r\n\r\n",
false,
},
{
"To addresses",
[]byte{},
[]byte{},
[]string{"one", "two"},
"",
"",
"",
"",
"",
"From: \r\nMIME-Version: 1.0\r\nDate: " + now + "\r\nSubject: \r\nTo: one,two\r\n\r\n",
false,
},
}
regex := regexp.MustCompile("\r?\n")
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
m := &MailYak{
toAddrs: tt.rtoAddrs,
subject: tt.rsubject,
fromAddr: tt.rfromAddr,
fromName: tt.rfromName,
replyTo: tt.rreplyTo,
trimRegex: regex,
date: now,
}
m.HTML().Write(tt.rHTML)
m.Plain().Write(tt.rPlain)
if tt.rAttachemnt != "" {
m.Attach("testAttachment", strings.NewReader(tt.rAttachemnt))
}
buf := &bytes.Buffer{}
err := m.buildMimeWithBoundaries(buf, "mixed", "alt")
if (err != nil) != tt.wantErr {
t.Fatalf("%q. MailYak.buildMime() error = %v, wantErr %v", tt.name, err, tt.wantErr)
}
if buf.String() != tt.want {
t.Errorf("%q. MailYak.buildMime() = %v, want %v", tt.name, buf.String(), tt.want)
}
})
}
}
// TestMailYakBuildMime_withAttachments ensures attachments are correctly added to the MIME message
func TestMailYakBuildMime_withAttachments(t *testing.T) {
t.Parallel()
tests := []struct {
// Test description.
name string
// Receiver fields.
rHTML []byte
rPlain []byte
rtoAddrs []string
rsubject string
rfromAddr string
rfromName string
rreplyTo string
rattachments []attachment
// Expected results.
wantAttach []string
wantErr bool
}{
{
"No attachment",
[]byte{},
[]byte{},
[]string{""},
"",
"",
"",
"",
[]attachment{},
[]string{},
false,
},
{
"One attachment",
[]byte{},
[]byte{},
[]string{""},
"",
"",
"",
"",
[]attachment{
{"test.txt", strings.NewReader("content"), false, ""},
},
[]string{"Y29udGVudA=="},
false,
},
{
"One inline attachment",
[]byte{},
[]byte{},
[]string{""},
"",
"",
"",
"",
[]attachment{
{"test.txt", strings.NewReader("content"), true, ""},
},
[]string{"Y29udGVudA=="},
false,
},
{
"Two attachments",
[]byte{},
[]byte{},
[]string{""},
"",
"",
"",
"",
[]attachment{
{"test.txt", strings.NewReader("content"), false, ""},
{"another.txt", strings.NewReader("another"), false, ""},
},
[]string{"Y29udGVudA==", "YW5vdGhlcg=="},
false,
},
{
"Two inline attachments",
[]byte{},
[]byte{},
[]string{""},
"",
"",
"",
"",
[]attachment{
{"test.txt", strings.NewReader("content"), true, ""},
{"another.txt", strings.NewReader("another"), true, ""},
},
[]string{"Y29udGVudA==", "YW5vdGhlcg=="},
false,
},
}
regex := regexp.MustCompile("\r?\n")
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
m := &MailYak{
toAddrs: tt.rtoAddrs,
subject: tt.rsubject,
fromAddr: tt.rfromAddr,
fromName: tt.rfromName,
replyTo: tt.rreplyTo,
attachments: tt.rattachments,
trimRegex: regex,
}
m.HTML().Write(tt.rHTML)
m.Plain().Write(tt.rPlain)
buf := &bytes.Buffer{}
err := m.buildMimeWithBoundaries(buf, "mixed", "alt")
if (err != nil) != tt.wantErr {
t.Fatalf("%q. MailYak.buildMime() error = %v, wantErr %v", tt.name, err, tt.wantErr)
}
seen := 0
msg, err := mail.ReadMessage(buf)
if err != nil {
t.Fatalf("%q. MailYak.buildMime() error %v", tt.name, err)
}
contentTypeHeader := msg.Header.Get("Content-Type")
if contentTypeHeader == "" {
if len(tt.rattachments) != 0 {
t.Errorf("%q. MailYak.buildMime() attachments were expected, but Content-Type header wasn't set", tt.name)
}
return
}
mediaType, mediaTypeParams, err := mime.ParseMediaType(contentTypeHeader)
if err != nil {
t.Fatalf("%q. MailYak.buildMime() error %v", tt.name, err)
}
if mediaType != "multipart/mixed" {
t.Fatalf("%q. MailYak.buildMime() Content-Type media type multipart/mixed was expected, but got %q", tt.name, mediaType)
}
mr := multipart.NewReader(msg.Body, mediaTypeParams["boundary"])
// Itterate over the mime parts, look for attachments
for contentTypeHeader != "" {
p, err := mr.NextPart()
if err == io.EOF {
break
}
if err != nil {
t.Errorf("%q. MailYak.buildMime() error = %v, wantErr %v", tt.name, err, tt.wantErr)
}
// Read the attachment data
slurp, err := ioutil.ReadAll(p)
if err != nil {
t.Errorf("%q. MailYak.buildMime() error = %v, wantErr %v", tt.name, err, tt.wantErr)
}
// Skip non-attachments
if p.Header.Get("Content-Disposition") == "" {
continue
}
// Run through our attachments looking for a match
for i, attch := range tt.rattachments {
// Check Disposition header
var disp string
if attch.inline {
disp = "inline; filename=%q; name=%q"
} else {
disp = "attachment; filename=%q; name=%q"
}
if p.Header.Get("Content-Disposition") != fmt.Sprintf(disp, attch.filename, attch.filename) {
continue
}
// Check data
if !bytes.Equal(slurp, []byte(tt.wantAttach[i])) {
fmt.Printf("Part %q: %q\n", p.Header.Get("Content-Disposition"), slurp)
continue
}
seen++
}
}
// Did we see all the expected attachments?
if seen != len(tt.rattachments) {
t.Errorf("%q. MailYak.buildMime() didn't find all attachments in mime body", tt.name)
}
})
}
}