Merge branch 'main' into feature/145_add-eml-parser-to-generate-msg-from-eml-files

This commit is contained in:
Winni Neessen 2023-12-07 13:21:30 +01:00
commit a12d9c327f
Signed by: wneessen
GPG key ID: 5F3AF39B820C119D
7 changed files with 93 additions and 37 deletions

View file

@ -511,10 +511,15 @@ func (c *Client) DialWithContext(pc context.Context) error {
return err return err
} }
c.sc, err = smtp.NewClient(c.co, c.host) sc, err := smtp.NewClient(c.co, c.host)
if err != nil { if err != nil {
return err return err
} }
if sc == nil {
return fmt.Errorf("SMTP client is nil")
}
c.sc = sc
if c.l != nil { if c.l != nil {
c.sc.SetLogger(c.l) c.sc.SetLogger(c.l)
} }

21
msg.go
View file

@ -287,7 +287,9 @@ func (m *Msg) SetAddrHeader(h AddrHeader, v ...string) error {
} }
switch h { switch h {
case HeaderFrom: case HeaderFrom:
if len(al) > 0 {
m.addrHeader[h] = []*mail.Address{al[0]} m.addrHeader[h] = []*mail.Address{al[0]}
}
default: default:
m.addrHeader[h] = al m.addrHeader[h] = al
} }
@ -518,7 +520,9 @@ func (m *Msg) RequestMDNTo(t ...string) error {
} }
tl = append(tl, a.String()) tl = append(tl, a.String())
} }
if _, ok := m.genHeader[HeaderDispositionNotificationTo]; ok {
m.genHeader[HeaderDispositionNotificationTo] = tl m.genHeader[HeaderDispositionNotificationTo] = tl
}
return nil return nil
} }
@ -539,7 +543,9 @@ func (m *Msg) RequestMDNAddTo(t string) error {
var tl []string var tl []string
tl = append(tl, m.genHeader[HeaderDispositionNotificationTo]...) tl = append(tl, m.genHeader[HeaderDispositionNotificationTo]...)
tl = append(tl, a.String()) tl = append(tl, a.String())
if _, ok := m.genHeader[HeaderDispositionNotificationTo]; ok {
m.genHeader[HeaderDispositionNotificationTo] = tl m.genHeader[HeaderDispositionNotificationTo] = tl
}
return nil return nil
} }
@ -591,8 +597,8 @@ func (m *Msg) GetAddrHeader(h AddrHeader) []*mail.Address {
// GetAddrHeaderString returns the address string of the requested address header of the Msg // GetAddrHeaderString returns the address string of the requested address header of the Msg
func (m *Msg) GetAddrHeaderString(h AddrHeader) []string { func (m *Msg) GetAddrHeaderString(h AddrHeader) []string {
var al []string var al []string
for i := range m.addrHeader[h] { for _, mh := range m.addrHeader[h] {
al = append(al, m.addrHeader[h][i].String()) al = append(al, mh.String())
} }
return al return al
} }
@ -999,9 +1005,12 @@ func (m *Msg) WriteToSendmailWithContext(ctx context.Context, sp string, a ...st
if err != nil { if err != nil {
return fmt.Errorf("failed to set STDIN pipe: %w", err) return fmt.Errorf("failed to set STDIN pipe: %w", err)
} }
if se == nil || si == nil {
return fmt.Errorf("received nil for STDERR or STDIN pipe")
}
// Start the execution and write to STDIN // Start the execution and write to STDIN
if err := ec.Start(); err != nil { if err = ec.Start(); err != nil {
return fmt.Errorf("could not start sendmail execution: %w", err) return fmt.Errorf("could not start sendmail execution: %w", err)
} }
_, err = m.WriteTo(si) _, err = m.WriteTo(si)
@ -1012,7 +1021,7 @@ func (m *Msg) WriteToSendmailWithContext(ctx context.Context, sp string, a ...st
} }
// Close STDIN and wait for completion or cancellation of the sendmail executable // Close STDIN and wait for completion or cancellation of the sendmail executable
if err := si.Close(); err != nil { if err = si.Close(); err != nil {
return fmt.Errorf("failed to close STDIN pipe: %w", err) return fmt.Errorf("failed to close STDIN pipe: %w", err)
} }
@ -1025,7 +1034,7 @@ func (m *Msg) WriteToSendmailWithContext(ctx context.Context, sp string, a ...st
return fmt.Errorf("sendmail command failed: %s", string(serr)) return fmt.Errorf("sendmail command failed: %s", string(serr))
} }
if err := ec.Wait(); err != nil { if err = ec.Wait(); err != nil {
return fmt.Errorf("sendmail command execution failed: %w", err) return fmt.Errorf("sendmail command execution failed: %w", err)
} }
@ -1069,7 +1078,7 @@ func (m *Msg) HasSendError() bool {
// corresponding error was of temporary nature and should be retried later // corresponding error was of temporary nature and should be retried later
func (m *Msg) SendErrorIsTemp() bool { func (m *Msg) SendErrorIsTemp() bool {
var e *SendError var e *SendError
if errors.As(m.sendError, &e) { if errors.As(m.sendError, &e) && e != nil {
return e.isTemp return e.isTemp
} }
return false return false

View file

@ -216,6 +216,9 @@ func (mw uppercaseMiddleware) Handle(m *Msg) *Msg {
if !ok { if !ok {
fmt.Println("can't find the subject header") fmt.Println("can't find the subject header")
} }
if s == nil || len(s) < 1 {
s = append(s, "")
}
m.Subject(strings.ToUpper(s[0])) m.Subject(strings.ToUpper(s[0]))
return m return m
} }
@ -231,6 +234,9 @@ func (mw encodeMiddleware) Handle(m *Msg) *Msg {
if !ok { if !ok {
fmt.Println("can't find the subject header") fmt.Println("can't find the subject header")
} }
if s == nil || len(s) < 1 {
s = append(s, "")
}
m.Subject(strings.Replace(s[0], "a", "@", -1)) m.Subject(strings.Replace(s[0], "a", "@", -1))
return m return m
} }
@ -752,7 +758,9 @@ func TestMsg_SetMessageIDWithValue(t *testing.T) {
t.Errorf("SetMessageID() failed. Expected value, got: empty") t.Errorf("SetMessageID() failed. Expected value, got: empty")
return return
} }
if _, ok := m.genHeader[HeaderMessageID]; ok {
m.genHeader[HeaderMessageID] = nil m.genHeader[HeaderMessageID] = nil
}
v := "This.is.a.message.id" v := "This.is.a.message.id"
vf := "<This.is.a.message.id>" vf := "<This.is.a.message.id>"
m.SetMessageIDWithValue(v) m.SetMessageIDWithValue(v)
@ -773,8 +781,10 @@ func TestMsg_SetMessageIDRandomness(t *testing.T) {
m := NewMsg() m := NewMsg()
m.SetMessageID() m.SetMessageID()
mid := m.GetGenHeader(HeaderMessageID) mid := m.GetGenHeader(HeaderMessageID)
if len(mid) > 0 {
mids = append(mids, mid[0]) mids = append(mids, mid[0])
} }
}
c := make(map[string]int) c := make(map[string]int)
for i := range mids { for i := range mids {
c[mids[i]]++ c[mids[i]]++
@ -1125,9 +1135,11 @@ func TestMsg_RequestMDN(t *testing.T) {
if err := m.RequestMDNTo(v); err != nil { if err := m.RequestMDNTo(v); err != nil {
t.Errorf("RequestMDNTo with a single valid address failed: %s", err) t.Errorf("RequestMDNTo with a single valid address failed: %s", err)
} }
if m.genHeader[HeaderDispositionNotificationTo][0] != fmt.Sprintf("<%s>", v) { if val := m.genHeader[HeaderDispositionNotificationTo]; len(val) > 1 {
if val[0] != fmt.Sprintf("<%s>", v) {
t.Errorf("RequestMDNTo with a single valid address failed. Expected: %s, got: %s", v, t.Errorf("RequestMDNTo with a single valid address failed. Expected: %s, got: %s", v,
m.genHeader[HeaderDispositionNotificationTo][0]) val[0])
}
} }
m.Reset() m.Reset()
@ -1135,13 +1147,17 @@ func TestMsg_RequestMDN(t *testing.T) {
if err := m.RequestMDNTo(vl...); err != nil { if err := m.RequestMDNTo(vl...); err != nil {
t.Errorf("RequestMDNTo with a multiple valid address failed: %s", err) t.Errorf("RequestMDNTo with a multiple valid address failed: %s", err)
} }
if m.genHeader[HeaderDispositionNotificationTo][0] != fmt.Sprintf("<%s>", v) { if val := m.genHeader[HeaderDispositionNotificationTo]; len(val) > 0 {
if val[0] != fmt.Sprintf("<%s>", v) {
t.Errorf("RequestMDNTo with a multiple valid addresses failed. Expected 0: %s, got 0: %s", v, t.Errorf("RequestMDNTo with a multiple valid addresses failed. Expected 0: %s, got 0: %s", v,
m.genHeader[HeaderDispositionNotificationTo][0]) val[0])
} }
if m.genHeader[HeaderDispositionNotificationTo][1] != fmt.Sprintf("<%s>", v2) { }
if val := m.genHeader[HeaderDispositionNotificationTo]; len(val) > 1 {
if val[1] != fmt.Sprintf("<%s>", v2) {
t.Errorf("RequestMDNTo with a multiple valid addresses failed. Expected 1: %s, got 1: %s", v2, t.Errorf("RequestMDNTo with a multiple valid addresses failed. Expected 1: %s, got 1: %s", v2,
m.genHeader[HeaderDispositionNotificationTo][1]) val[1])
}
} }
m.Reset() m.Reset()
@ -1158,9 +1174,11 @@ func TestMsg_RequestMDN(t *testing.T) {
if err := m.RequestMDNAddTo(v2); err != nil { if err := m.RequestMDNAddTo(v2); err != nil {
t.Errorf("RequestMDNAddTo with a valid address failed: %s", err) t.Errorf("RequestMDNAddTo with a valid address failed: %s", err)
} }
if m.genHeader[HeaderDispositionNotificationTo][1] != fmt.Sprintf("<%s>", v2) { if val := m.genHeader[HeaderDispositionNotificationTo]; len(val) > 1 {
if val[1] != fmt.Sprintf("<%s>", v2) {
t.Errorf("RequestMDNTo with a multiple valid addresses failed. Expected 1: %s, got 1: %s", v2, t.Errorf("RequestMDNTo with a multiple valid addresses failed. Expected 1: %s, got 1: %s", v2,
m.genHeader[HeaderDispositionNotificationTo][1]) val[1])
}
} }
m.Reset() m.Reset()
@ -1168,16 +1186,20 @@ func TestMsg_RequestMDN(t *testing.T) {
if err := m.RequestMDNToFormat(n, v); err != nil { if err := m.RequestMDNToFormat(n, v); err != nil {
t.Errorf("RequestMDNToFormat with a single valid address failed: %s", err) t.Errorf("RequestMDNToFormat with a single valid address failed: %s", err)
} }
if m.genHeader[HeaderDispositionNotificationTo][0] != fmt.Sprintf(`"%s" <%s>`, n, v) { if val := m.genHeader[HeaderDispositionNotificationTo]; len(val) > 0 {
if val[0] != fmt.Sprintf(`"%s" <%s>`, n, v) {
t.Errorf(`RequestMDNToFormat with a single valid address failed. Expected: "%s" <%s>, got: %s`, n, v, t.Errorf(`RequestMDNToFormat with a single valid address failed. Expected: "%s" <%s>, got: %s`, n, v,
m.genHeader[HeaderDispositionNotificationTo][0]) val[0])
}
} }
if err := m.RequestMDNAddToFormat(n2, v2); err != nil { if err := m.RequestMDNAddToFormat(n2, v2); err != nil {
t.Errorf("RequestMDNAddToFormat with a valid address failed: %s", err) t.Errorf("RequestMDNAddToFormat with a valid address failed: %s", err)
} }
if m.genHeader[HeaderDispositionNotificationTo][1] != fmt.Sprintf(`"%s" <%s>`, n2, v2) { if val := m.genHeader[HeaderDispositionNotificationTo]; len(val) > 1 {
if val[1] != fmt.Sprintf(`"%s" <%s>`, n2, v2) {
t.Errorf(`RequestMDNAddToFormat with a single valid address failed. Expected: "%s" <%s>, got: %s`, n2, v2, t.Errorf(`RequestMDNAddToFormat with a single valid address failed. Expected: "%s" <%s>, got: %s`, n2, v2,
m.genHeader[HeaderDispositionNotificationTo][1]) val[1])
}
} }
m.Reset() m.Reset()
@ -2567,6 +2589,10 @@ func TestMsg_WriteToFile(t *testing.T) {
if err != nil { if err != nil {
t.Errorf("failed to stat output file: %s", err) t.Errorf("failed to stat output file: %s", err)
} }
if fi == nil {
t.Errorf("received empty file handle")
return
}
if fi.Size() <= 0 { if fi.Size() <= 0 {
t.Errorf("output file is expected to contain data but its size is zero") t.Errorf("output file is expected to contain data but its size is zero")
} }

View file

@ -67,13 +67,13 @@ func (mw *msgWriter) writeMsg(m *Msg) {
// Set the FROM header (or envelope FROM if FROM is empty) // Set the FROM header (or envelope FROM if FROM is empty)
hf := true hf := true
f, ok := m.addrHeader[HeaderFrom] f, ok := m.addrHeader[HeaderFrom]
if !ok || len(f) == 0 { if !ok || (len(f) == 0 || f == nil) {
f, ok = m.addrHeader[HeaderEnvelopeFrom] f, ok = m.addrHeader[HeaderEnvelopeFrom]
if !ok || len(f) == 0 { if !ok || (len(f) == 0 || f == nil) {
hf = false hf = false
} }
} }
if hf { if hf && (len(f) > 0 && f[0] != nil) {
mw.writeHeader(Header(HeaderFrom), f[0].String()) mw.writeHeader(Header(HeaderFrom), f[0].String())
} }

View file

@ -25,7 +25,7 @@ func (r *Reader) Read(p []byte) (n int, err error) {
if r.err != nil { if r.err != nil {
return 0, r.err return 0, r.err
} }
if r.empty() { if r.empty() || r.buf == nil {
r.Reset() r.Reset()
if len(p) == 0 { if len(p) == 0 {
return 0, nil return 0, nil

View file

@ -6,7 +6,9 @@ package mail
import ( import (
"bytes" "bytes"
"errors"
"fmt" "fmt"
"io"
"testing" "testing"
) )
@ -64,9 +66,20 @@ func TestReader_Read_error(t *testing.T) {
// TestReader_Read_empty tests the Reader.Read method with an empty buffer // TestReader_Read_empty tests the Reader.Read method with an empty buffer
func TestReader_Read_empty(t *testing.T) { func TestReader_Read_empty(t *testing.T) {
r := Reader{buf: []byte{}} r := Reader{buf: []byte{}}
var p []byte p := make([]byte, 1)
p[0] = 'a'
_, err := r.Read(p) _, err := r.Read(p)
if err != nil { if err != nil && !errors.Is(err, io.EOF) {
t.Errorf("Reader failed: %s", err)
}
}
// TestReader_Read_nil tests the Reader.Read method with a nil buffer
func TestReader_Read_nil(t *testing.T) {
r := Reader{buf: nil, off: -10}
p := make([]byte, 0)
_, err := r.Read(p)
if err != nil && !errors.Is(err, io.EOF) {
t.Errorf("Reader failed: %s", err) t.Errorf("Reader failed: %s", err)
} }
} }

View file

@ -98,7 +98,7 @@ func (e *SendError) Error() string {
// Is implements the errors.Is functionality and compares the SendErrReason // Is implements the errors.Is functionality and compares the SendErrReason
func (e *SendError) Is(et error) bool { func (e *SendError) Is(et error) bool {
var t *SendError var t *SendError
if errors.As(et, &t) { if errors.As(et, &t) && t != nil {
return e.Reason == t.Reason && e.isTemp == t.isTemp return e.Reason == t.Reason && e.isTemp == t.isTemp
} }
return false return false
@ -106,6 +106,9 @@ func (e *SendError) Is(et error) bool {
// IsTemp returns true if the delivery error is of temporary nature and can be retried // IsTemp returns true if the delivery error is of temporary nature and can be retried
func (e *SendError) IsTemp() bool { func (e *SendError) IsTemp() bool {
if e == nil {
return false
}
return e.isTemp return e.isTemp
} }