mirror of
https://github.com/wneessen/go-mail.git
synced 2024-11-22 22:00:49 +01:00
commit
c32d2770e9
4 changed files with 194 additions and 43 deletions
33
msg.go
33
msg.go
|
@ -181,6 +181,9 @@ func (m *Msg) Charset() string {
|
||||||
|
|
||||||
// SetHeader sets a generic header field of the Msg
|
// SetHeader sets a generic header field of the Msg
|
||||||
func (m *Msg) SetHeader(h Header, v ...string) {
|
func (m *Msg) SetHeader(h Header, v ...string) {
|
||||||
|
if m.genHeader == nil {
|
||||||
|
m.genHeader = make(map[Header][]string)
|
||||||
|
}
|
||||||
for i, hv := range v {
|
for i, hv := range v {
|
||||||
v[i] = m.encodeString(hv)
|
v[i] = m.encodeString(hv)
|
||||||
}
|
}
|
||||||
|
@ -189,6 +192,9 @@ func (m *Msg) SetHeader(h Header, v ...string) {
|
||||||
|
|
||||||
// SetAddrHeader sets an address related header field of the Msg
|
// SetAddrHeader sets an address related header field of the Msg
|
||||||
func (m *Msg) SetAddrHeader(h AddrHeader, v ...string) error {
|
func (m *Msg) SetAddrHeader(h AddrHeader, v ...string) error {
|
||||||
|
if m.addrHeader == nil {
|
||||||
|
m.addrHeader = make(map[AddrHeader][]*mail.Address)
|
||||||
|
}
|
||||||
var al []*mail.Address
|
var al []*mail.Address
|
||||||
for _, av := range v {
|
for _, av := range v {
|
||||||
a, err := mail.ParseAddress(av)
|
a, err := mail.ParseAddress(av)
|
||||||
|
@ -807,14 +813,31 @@ func (m *Msg) WriteToSendmailWithContext(ctx context.Context, sp string, a ...st
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read outputs the length of p into p to satisfy the io.Reader interface
|
// NewReader returns a Reader type that satisfies the io.Reader interface.
|
||||||
func (m *Msg) Read(p []byte) (int, error) {
|
//
|
||||||
|
// IMPORTANT: when creating a new Reader, the current state of the Msg is taken, as
|
||||||
|
// basis for the Reader. If you perform changes on Msg after creating the Reader, these
|
||||||
|
// changes will not be reflected in the Reader. You will have to use Msg.UpdateReader
|
||||||
|
// first to update the Reader's buffer with the current Msg content
|
||||||
|
func (m *Msg) NewReader() *Reader {
|
||||||
|
r := &Reader{}
|
||||||
wbuf := bytes.Buffer{}
|
wbuf := bytes.Buffer{}
|
||||||
_, err := m.WriteTo(&wbuf)
|
_, err := m.Write(&wbuf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, fmt.Errorf("failed to write message to internal write buffer: %w", err)
|
r.err = fmt.Errorf("failed to write Msg to Reader buffer: %w", err)
|
||||||
}
|
}
|
||||||
return wbuf.Read(p)
|
r.buf = wbuf.Bytes()
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateReader will update a Reader with the content of the given Msg and reset the
|
||||||
|
// Reader position to the start
|
||||||
|
func (m *Msg) UpdateReader(r *Reader) {
|
||||||
|
wbuf := bytes.Buffer{}
|
||||||
|
_, err := m.Write(&wbuf)
|
||||||
|
r.Reset()
|
||||||
|
r.buf = wbuf.Bytes()
|
||||||
|
r.err = err
|
||||||
}
|
}
|
||||||
|
|
||||||
// encodeString encodes a string based on the configured message encoder and the corresponding
|
// encodeString encodes a string based on the configured message encoder and the corresponding
|
||||||
|
|
84
msg_test.go
84
msg_test.go
|
@ -1718,52 +1718,29 @@ func TestMsg_multipleWrites(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestMsg_Read tests the Msg.Read method that implements the io.Reader interface
|
// TestMsg_NewReader tests the Msg.NewReader method
|
||||||
func TestMsg_Read(t *testing.T) {
|
func TestMsg_NewReader(t *testing.T) {
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
plen int
|
|
||||||
}{
|
|
||||||
{"P length is bigger than the mail", 32000},
|
|
||||||
{"P length is smaller than the mail", 128},
|
|
||||||
}
|
|
||||||
|
|
||||||
m := NewMsg()
|
m := NewMsg()
|
||||||
m.SetBodyString(TypeTextPlain, "TEST123")
|
m.SetBodyString(TypeTextPlain, "TEST123")
|
||||||
wbuf := bytes.Buffer{}
|
mr := m.NewReader()
|
||||||
_, err := m.Write(&wbuf)
|
if mr == nil {
|
||||||
if err != nil {
|
t.Errorf("NewReader failed: Reader is nil")
|
||||||
t.Errorf("failed to write message into temporary buffer: %s", err)
|
|
||||||
}
|
}
|
||||||
elen := wbuf.Len()
|
if mr.Error() != nil {
|
||||||
|
t.Errorf("NewReader failed: %s", mr.Error())
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
p := make([]byte, tt.plen)
|
|
||||||
n, err := m.Read(p)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("failed to Read(): %s", err)
|
|
||||||
}
|
|
||||||
if n == 0 {
|
|
||||||
t.Errorf("failed to Read() - received 0 bytes of data")
|
|
||||||
}
|
|
||||||
if tt.plen >= elen && n != elen {
|
|
||||||
t.Errorf("failed to Read() - not all data received. Expected: %d, got: %d", elen, n)
|
|
||||||
}
|
|
||||||
if tt.plen < elen && n != tt.plen {
|
|
||||||
t.Errorf("failed to Read() - full length of p wasn't filled with data. Expected: %d, got: %d",
|
|
||||||
tt.plen, n)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestMsg_Read_ioCopy tests the Msg.Read method using io.Copy
|
// TestMsg_NewReader_ioCopy tests the Msg.NewReader method using io.Copy
|
||||||
func TestMsg_Read_ioCopy(t *testing.T) {
|
func TestMsg_NewReader_ioCopy(t *testing.T) {
|
||||||
wbuf1 := bytes.Buffer{}
|
wbuf1 := bytes.Buffer{}
|
||||||
wbuf2 := bytes.Buffer{}
|
wbuf2 := bytes.Buffer{}
|
||||||
m := NewMsg()
|
m := NewMsg()
|
||||||
m.SetBodyString(TypeTextPlain, "TEST123")
|
m.SetBodyString(TypeTextPlain, "TEST123")
|
||||||
|
mr := m.NewReader()
|
||||||
|
if mr == nil {
|
||||||
|
t.Errorf("NewReader failed: Reader is nil")
|
||||||
|
}
|
||||||
|
|
||||||
// First we use WriteTo to have something to compare to
|
// First we use WriteTo to have something to compare to
|
||||||
_, err := m.WriteTo(&wbuf1)
|
_, err := m.WriteTo(&wbuf1)
|
||||||
|
@ -1772,9 +1749,9 @@ func TestMsg_Read_ioCopy(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Then we write to wbuf2 via io.Copy
|
// Then we write to wbuf2 via io.Copy
|
||||||
n, err := io.Copy(&wbuf2, m)
|
n, err := io.Copy(&wbuf2, mr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("failed to use io.Copy on Msg: %s", err)
|
t.Errorf("failed to use io.Copy on Reader: %s", err)
|
||||||
}
|
}
|
||||||
if n != int64(wbuf1.Len()) {
|
if n != int64(wbuf1.Len()) {
|
||||||
t.Errorf("message length of WriteTo and io.Copy differ. Expected: %d, got: %d", wbuf1.Len(), n)
|
t.Errorf("message length of WriteTo and io.Copy differ. Expected: %d, got: %d", wbuf1.Len(), n)
|
||||||
|
@ -1784,6 +1761,37 @@ func TestMsg_Read_ioCopy(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestMsg_UpdateReader tests the Msg.UpdateReader method
|
||||||
|
func TestMsg_UpdateReader(t *testing.T) {
|
||||||
|
m := NewMsg()
|
||||||
|
m.Subject("Subject-Run 1")
|
||||||
|
mr := m.NewReader()
|
||||||
|
if mr == nil {
|
||||||
|
t.Errorf("NewReader failed: Reader is nil")
|
||||||
|
}
|
||||||
|
wbuf1 := bytes.Buffer{}
|
||||||
|
_, err := io.Copy(&wbuf1, mr)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("io.Copy on Reader failed: %s", err)
|
||||||
|
}
|
||||||
|
if !strings.Contains(wbuf1.String(), "Subject: Subject-Run 1") {
|
||||||
|
t.Errorf("io.Copy on Reader failed. Expected to find %q but string in Subject was not found",
|
||||||
|
"Subject-Run 1")
|
||||||
|
}
|
||||||
|
|
||||||
|
m.Subject("Subject-Run 2")
|
||||||
|
m.UpdateReader(mr)
|
||||||
|
wbuf2 := bytes.Buffer{}
|
||||||
|
_, err = io.Copy(&wbuf2, mr)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("2nd io.Copy on Reader failed: %s", err)
|
||||||
|
}
|
||||||
|
if !strings.Contains(wbuf2.String(), "Subject: Subject-Run 2") {
|
||||||
|
t.Errorf("io.Copy on Reader failed. Expected to find %q but string in Subject was not found",
|
||||||
|
"Subject-Run 2")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TestMsg_SetBodyTextTemplate tests the Msg.SetBodyTextTemplate method
|
// TestMsg_SetBodyTextTemplate tests the Msg.SetBodyTextTemplate method
|
||||||
func TestMsg_SetBodyTextTemplate(t *testing.T) {
|
func TestMsg_SetBodyTextTemplate(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
|
|
48
reader.go
Normal file
48
reader.go
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
// SPDX-FileCopyrightText: 2022 Winni Neessen <winni@neessen.dev>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package mail
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Reader is a type that implements the io.Reader interface for a Msg
|
||||||
|
type Reader struct {
|
||||||
|
buf []byte // contents are the bytes buf[off : len(buf)]
|
||||||
|
off int // read at &buf[off], write at &buf[len(buf)]
|
||||||
|
err error // initalization error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error returns an error if the Reader err field is not nil
|
||||||
|
func (r *Reader) Error() error {
|
||||||
|
return r.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read reads the length of p of the Msg buffer to satisfy the io.Reader interface
|
||||||
|
func (r *Reader) Read(p []byte) (n int, err error) {
|
||||||
|
if r.err != nil {
|
||||||
|
return 0, r.err
|
||||||
|
}
|
||||||
|
if r.empty() {
|
||||||
|
r.Reset()
|
||||||
|
if len(p) == 0 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
return 0, io.EOF
|
||||||
|
}
|
||||||
|
n = copy(p, r.buf[r.off:])
|
||||||
|
r.off += n
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset resets the Reader buffer to be empty, but it retains the underlying storage
|
||||||
|
// for use by future writes.
|
||||||
|
func (r *Reader) Reset() {
|
||||||
|
r.buf = r.buf[:0]
|
||||||
|
r.off = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// empty reports whether the unread portion of the Reader buffer is empty.
|
||||||
|
func (r *Reader) empty() bool { return len(r.buf) <= r.off }
|
72
reader_test.go
Normal file
72
reader_test.go
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
// SPDX-FileCopyrightText: 2022 Winni Neessen <winni@neessen.dev>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package mail
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestReader_Read tests the Reader.Read method that implements the io.Reader interface
|
||||||
|
func TestReader_Read(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
plen int
|
||||||
|
}{
|
||||||
|
{"P length is bigger than the mail", 3200000},
|
||||||
|
{"P length is smaller than the mail", 128},
|
||||||
|
}
|
||||||
|
|
||||||
|
m := NewMsg()
|
||||||
|
m.SetBodyString(TypeTextPlain, "TEST123")
|
||||||
|
wbuf := bytes.Buffer{}
|
||||||
|
_, err := m.Write(&wbuf)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to write message into temporary buffer: %s", err)
|
||||||
|
}
|
||||||
|
elen := wbuf.Len()
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
p := make([]byte, tt.plen)
|
||||||
|
mr := m.NewReader()
|
||||||
|
n, err := mr.Read(p)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to Read(): %s", err)
|
||||||
|
}
|
||||||
|
if n == 0 {
|
||||||
|
t.Errorf("failed to Read() - received 0 bytes of data")
|
||||||
|
}
|
||||||
|
if tt.plen >= elen && n != elen {
|
||||||
|
t.Errorf("failed to Read() - not all data received. Expected: %d, got: %d", elen, n)
|
||||||
|
}
|
||||||
|
if tt.plen < elen && n != tt.plen {
|
||||||
|
t.Errorf("failed to Read() - full length of p wasn't filled with data. Expected: %d, got: %d",
|
||||||
|
tt.plen, n)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestReader_Read_error tests the Reader.Read method with an intentional error
|
||||||
|
func TestReader_Read_error(t *testing.T) {
|
||||||
|
r := Reader{err: fmt.Errorf("FAILED")}
|
||||||
|
var p []byte
|
||||||
|
_, err := r.Read(p)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("Reader was supposed to fail, but didn't")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestReader_Read_empty tests the Reader.Read method with an empty buffer
|
||||||
|
func TestReader_Read_empty(t *testing.T) {
|
||||||
|
r := Reader{buf: []byte{}}
|
||||||
|
var p []byte
|
||||||
|
_, err := r.Read(p)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Reader failed: %s", err)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue