Merge branch 'main' into feature/107_provide-more-ways-for-middleware-to-interact-with-mail-parts

This commit is contained in:
Winni Neessen 2023-01-31 20:47:36 +01:00
commit d33f0e8004
Signed by: wneessen
GPG key ID: 5F3AF39B820C119D
2 changed files with 173 additions and 5 deletions

54
msg.go
View file

@ -746,11 +746,22 @@ func (m *Msg) AttachFile(n string, o ...FileOption) {
} }
// AttachReader adds an attachment File via io.Reader to the Msg // AttachReader adds an attachment File via io.Reader to the Msg
//
// CAVEAT: For AttachReader to work it has to read all data of the io.Reader
// into memory first, so it can seek through it. Using larger amounts of
// data on the io.Reader should be avoided. For such, it is recommeded to
// either use AttachFile or AttachReadSeeker instead
func (m *Msg) AttachReader(n string, r io.Reader, o ...FileOption) { func (m *Msg) AttachReader(n string, r io.Reader, o ...FileOption) {
f := fileFromReader(n, r) f := fileFromReader(n, r)
m.attachments = m.appendFile(m.attachments, f, o...) m.attachments = m.appendFile(m.attachments, f, o...)
} }
// AttachReadSeeker adds an attachment File via io.ReadSeeker to the Msg
func (m *Msg) AttachReadSeeker(n string, r io.ReadSeeker, o ...FileOption) {
f := fileFromReadSeeker(n, r)
m.attachments = m.appendFile(m.attachments, f, o...)
}
// AttachHTMLTemplate adds the output of a html/template.Template pointer as File attachment to the Msg // AttachHTMLTemplate adds the output of a html/template.Template pointer as File attachment to the Msg
func (m *Msg) AttachHTMLTemplate(n string, t *ht.Template, d interface{}, o ...FileOption) error { func (m *Msg) AttachHTMLTemplate(n string, t *ht.Template, d interface{}, o ...FileOption) error {
f, err := fileFromHTMLTemplate(n, t, d) f, err := fileFromHTMLTemplate(n, t, d)
@ -794,11 +805,22 @@ func (m *Msg) EmbedFile(n string, o ...FileOption) {
} }
// EmbedReader adds an embedded File from an io.Reader to the Msg // EmbedReader adds an embedded File from an io.Reader to the Msg
//
// CAVEAT: For AttachReader to work it has to read all data of the io.Reader
// into memory first, so it can seek through it. Using larger amounts of
// data on the io.Reader should be avoided. For such, it is recommeded to
// either use AttachFile or AttachReadSeeker instead
func (m *Msg) EmbedReader(n string, r io.Reader, o ...FileOption) { func (m *Msg) EmbedReader(n string, r io.Reader, o ...FileOption) {
f := fileFromReader(n, r) f := fileFromReader(n, r)
m.embeds = m.appendFile(m.embeds, f, o...) m.embeds = m.appendFile(m.embeds, f, o...)
} }
// EmbedReadSeeker adds an embedded File from an io.ReadSeeker to the Msg
func (m *Msg) EmbedReadSeeker(n string, r io.ReadSeeker, o ...FileOption) {
f := fileFromReadSeeker(n, r)
m.embeds = m.appendFile(m.embeds, f, o...)
}
// EmbedHTMLTemplate adds the output of a html/template.Template pointer as embedded File to the Msg // EmbedHTMLTemplate adds the output of a html/template.Template pointer as embedded File to the Msg
func (m *Msg) EmbedHTMLTemplate(n string, t *ht.Template, d interface{}, o ...FileOption) error { func (m *Msg) EmbedHTMLTemplate(n string, t *ht.Template, d interface{}, o ...FileOption) error {
f, err := fileFromHTMLTemplate(n, t, d) f, err := fileFromHTMLTemplate(n, t, d)
@ -1151,15 +1173,37 @@ func fileFromFS(n string) *File {
// fileFromReader returns a File pointer from a given io.Reader // fileFromReader returns a File pointer from a given io.Reader
func fileFromReader(n string, r io.Reader) *File { func fileFromReader(n string, r io.Reader) *File {
d, err := io.ReadAll(r)
if err != nil {
return &File{}
}
br := bytes.NewReader(d)
return &File{ return &File{
Name: filepath.Base(n), Name: n,
Header: make(map[string][]string), Header: make(map[string][]string),
Writer: func(w io.Writer) (int64, error) { Writer: func(w io.Writer) (int64, error) {
nb, err := io.Copy(w, r) rb, cerr := io.Copy(w, br)
if err != nil { if cerr != nil {
return nb, err return rb, cerr
} }
return nb, nil _, cerr = br.Seek(0, io.SeekStart)
return rb, cerr
},
}
}
// fileFromReadSeeker returns a File pointer from a given io.ReadSeeker
func fileFromReadSeeker(n string, r io.ReadSeeker) *File {
return &File{
Name: n,
Header: make(map[string][]string),
Writer: func(w io.Writer) (int64, error) {
rb, err := io.Copy(w, r)
if err != nil {
return rb, err
}
_, err = r.Seek(0, io.SeekStart)
return rb, err
}, },
} }
} }

View file

@ -2667,3 +2667,127 @@ func TestMsg_GetBccString(t *testing.T) {
`"Toni Cc" <bcc@example.com>"`, fh[0]) `"Toni Cc" <bcc@example.com>"`, fh[0])
} }
} }
// TestMsg_AttachEmbedReader_consecutive tests the Msg.AttachReader and Msg.EmbedReader
// methods with consecutive calls to Msg.WriteTo to make sure the attachments are not
// lost (see Github issue #110)
func TestMsg_AttachEmbedReader_consecutive(t *testing.T) {
ts1 := "This is a test string"
ts2 := "Another test string"
m := NewMsg()
m.AttachReader("attachment.txt", bytes.NewBufferString(ts1))
m.EmbedReader("embeded.txt", bytes.NewBufferString(ts2))
obuf1 := &bytes.Buffer{}
obuf2 := &bytes.Buffer{}
_, err := m.WriteTo(obuf1)
if err != nil {
t.Errorf("WriteTo to first output buffer failed: %s", err)
}
_, err = m.WriteTo(obuf2)
if err != nil {
t.Errorf("WriteTo to second output buffer failed: %s", err)
}
if !strings.Contains(obuf1.String(), "VGhpcyBpcyBhIHRlc3Qgc3RyaW5n") {
t.Errorf("Expected file attachment string not found in first output buffer")
}
if !strings.Contains(obuf2.String(), "VGhpcyBpcyBhIHRlc3Qgc3RyaW5n") {
t.Errorf("Expected file attachment string not found in second output buffer")
}
if !strings.Contains(obuf1.String(), "QW5vdGhlciB0ZXN0IHN0cmluZw==") {
t.Errorf("Expected embeded file string not found in first output buffer")
}
if !strings.Contains(obuf2.String(), "QW5vdGhlciB0ZXN0IHN0cmluZw==") {
t.Errorf("Expected embded file string not found in second output buffer")
}
}
// TestMsg_AttachEmbedReadSeeker_consecutive tests the Msg.AttachReadSeeker and
// Msg.EmbedReadSeeker methods with consecutive calls to Msg.WriteTo to make
// sure the attachments are not lost (see Github issue #110)
func TestMsg_AttachEmbedReadSeeker_consecutive(t *testing.T) {
ts1 := []byte("This is a test string")
ts2 := []byte("Another test string")
m := NewMsg()
m.AttachReadSeeker("attachment.txt", bytes.NewReader(ts1))
m.EmbedReadSeeker("embeded.txt", bytes.NewReader(ts2))
obuf1 := &bytes.Buffer{}
obuf2 := &bytes.Buffer{}
_, err := m.WriteTo(obuf1)
if err != nil {
t.Errorf("WriteTo to first output buffer failed: %s", err)
}
_, err = m.WriteTo(obuf2)
if err != nil {
t.Errorf("WriteTo to second output buffer failed: %s", err)
}
if !strings.Contains(obuf1.String(), "VGhpcyBpcyBhIHRlc3Qgc3RyaW5n") {
t.Errorf("Expected file attachment string not found in first output buffer")
}
if !strings.Contains(obuf2.String(), "VGhpcyBpcyBhIHRlc3Qgc3RyaW5n") {
t.Errorf("Expected file attachment string not found in second output buffer")
}
if !strings.Contains(obuf1.String(), "QW5vdGhlciB0ZXN0IHN0cmluZw==") {
t.Errorf("Expected embeded file string not found in first output buffer")
}
if !strings.Contains(obuf2.String(), "QW5vdGhlciB0ZXN0IHN0cmluZw==") {
t.Errorf("Expected embded file string not found in second output buffer")
}
}
// TestMsg_AttachReadSeeker tests the Msg.AttachReadSeeker method
func TestMsg_AttachReadSeeker(t *testing.T) {
m := NewMsg()
ts := []byte("This is a test string")
r := bytes.NewReader(ts)
m.AttachReadSeeker("testfile.txt", r)
if len(m.attachments) != 1 {
t.Errorf("AttachReadSeeker() failed. Number of attachments expected: %d, got: %d", 1,
len(m.attachments))
return
}
file := m.attachments[0]
if file == nil {
t.Errorf("AttachReadSeeker() failed. Attachment file pointer is nil")
return
}
if file.Name != "testfile.txt" {
t.Errorf("AttachReadSeeker() failed. Expected file name: %s, got: %s", "testfile.txt",
file.Name)
}
wbuf := bytes.Buffer{}
if _, err := file.Writer(&wbuf); err != nil {
t.Errorf("execute WriterFunc failed: %s", err)
}
if wbuf.String() != string(ts) {
t.Errorf("AttachReadSeeker() failed. Expected string: %q, got: %q", ts, wbuf.String())
}
}
// TestMsg_EmbedReadSeeker tests the Msg.EmbedReadSeeker method
func TestMsg_EmbedReadSeeker(t *testing.T) {
m := NewMsg()
ts := []byte("This is a test string")
r := bytes.NewReader(ts)
m.EmbedReadSeeker("testfile.txt", r)
if len(m.embeds) != 1 {
t.Errorf("EmbedReadSeeker() failed. Number of attachments expected: %d, got: %d", 1,
len(m.embeds))
return
}
file := m.embeds[0]
if file == nil {
t.Errorf("EmbedReadSeeker() failed. Embedded file pointer is nil")
return
}
if file.Name != "testfile.txt" {
t.Errorf("EmbedReadSeeker() failed. Expected file name: %s, got: %s", "testfile.txt",
file.Name)
}
wbuf := bytes.Buffer{}
if _, err := file.Writer(&wbuf); err != nil {
t.Errorf("execute WriterFunc failed: %s", err)
}
if wbuf.String() != string(ts) {
t.Errorf("EmbedReadSeeker() failed. Expected string: %q, got: %q", ts, wbuf.String())
}
}