Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Attach/EmbedReader and implement Attach/EmbedReadSeeker #111

Merged
merged 3 commits into from
Jan 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 48 additions & 4 deletions msg.go
Original file line number Diff line number Diff line change
Expand Up @@ -714,11 +714,22 @@ func (m *Msg) AttachFile(n string, o ...FileOption) {
}

// 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) {
f := fileFromReader(n, r)
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
func (m *Msg) AttachHTMLTemplate(n string, t *ht.Template, d interface{}, o ...FileOption) error {
f, err := fileFromHTMLTemplate(n, t, d)
Expand Down Expand Up @@ -762,11 +773,22 @@ func (m *Msg) EmbedFile(n string, o ...FileOption) {
}

// 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) {
f := fileFromReader(n, r)
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
func (m *Msg) EmbedHTMLTemplate(n string, t *ht.Template, d interface{}, o ...FileOption) error {
f, err := fileFromHTMLTemplate(n, t, d)
Expand Down Expand Up @@ -1114,15 +1136,37 @@ func fileFromFS(n string) *File {

// fileFromReader returns a File pointer from a given io.Reader
func fileFromReader(n string, r io.Reader) *File {
d, err := io.ReadAll(r)
if err != nil {
return &File{}
}
br := bytes.NewReader(d)
return &File{
Name: filepath.Base(n),
Name: n,
Header: make(map[string][]string),
Writer: func(w io.Writer) (int64, error) {
rb, cerr := io.Copy(w, br)
if cerr != nil {
return rb, cerr
}
_, 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) {
nb, err := io.Copy(w, r)
rb, err := io.Copy(w, r)
if err != nil {
return nb, err
return rb, err
}
return nb, nil
_, err = r.Seek(0, io.SeekStart)
return rb, err
},
}
}
Expand Down
124 changes: 124 additions & 0 deletions msg_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2667,3 +2667,127 @@ func TestMsg_GetBccString(t *testing.T) {
`"Toni Cc" <[email protected]>"`, 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())
}
}