Merge pull request #50 from dhia-gharsallaoui/main

Implement middleware concept and their tests
This commit is contained in:
Winni Neessen 2022-09-23 11:17:57 +02:00 committed by GitHub
commit 3a5f6393f3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 162 additions and 37 deletions

25
msg.go
View file

@ -42,6 +42,11 @@ const (
errParseMailAddr = "failed to parse mail address %q: %w" errParseMailAddr = "failed to parse mail address %q: %w"
) )
// Middleware is an interface to define a function to apply to Msg before sending
type Middleware interface {
Handle(*Msg) *Msg
}
// Msg is the mail message struct // Msg is the mail message struct
type Msg struct { type Msg struct {
// addrHeader is a slice of strings that the different mail AddrHeader fields // addrHeader is a slice of strings that the different mail AddrHeader fields
@ -73,6 +78,9 @@ type Msg struct {
// parts represent the different parts of the Msg // parts represent the different parts of the Msg
parts []*Part parts []*Part
// middlewares is the list of middlewares to apply to the Msg before sending in FIFO order
middlewares []Middleware
} }
// SendmailPath is the default system path to the sendmail binary // SendmailPath is the default system path to the sendmail binary
@ -133,6 +141,13 @@ func WithBoundary(b string) MsgOption {
} }
} }
// WithMiddleware add the given middleware in the end of the list of the client middlewares
func WithMiddleware(mw Middleware) MsgOption {
return func(m *Msg) {
m.middlewares = append(m.middlewares, mw)
}
}
// SetCharset sets the encoding charset of the Msg // SetCharset sets the encoding charset of the Msg
func (m *Msg) SetCharset(c Charset) { func (m *Msg) SetCharset(c Charset) {
m.charset = c m.charset = c
@ -657,10 +672,18 @@ func (m *Msg) Reset() {
m.parts = nil m.parts = nil
} }
// ApplyMiddlewares apply the list of middlewares to a Msg
func (m *Msg) applyMiddlewares(ms *Msg) *Msg {
for _, mw := range m.middlewares {
ms = mw.Handle(ms)
}
return ms
}
// WriteTo writes the formated Msg into a give io.Writer and satisfies the io.WriteTo interface // WriteTo writes the formated Msg into a give io.Writer and satisfies the io.WriteTo interface
func (m *Msg) WriteTo(w io.Writer) (int64, error) { func (m *Msg) WriteTo(w io.Writer) (int64, error) {
mw := &msgWriter{w: w, c: m.charset, en: m.encoder} mw := &msgWriter{w: w, c: m.charset, en: m.encoder}
mw.writeMsg(m) mw.writeMsg(m.applyMiddlewares(m))
return mw.n, mw.err return mw.n, mw.err
} }

View file

@ -177,6 +177,75 @@ func TestNewMsgWithBoundary(t *testing.T) {
} }
} }
type uppercaseMiddleware struct{}
func (mw uppercaseMiddleware) Handle(m *Msg) *Msg {
s, ok := m.genHeader[HeaderSubject]
if !ok {
fmt.Println("can't find the subject header")
}
m.Subject(strings.ToUpper(s[0]))
return m
}
type encodeMiddleware struct{}
func (mw encodeMiddleware) Handle(m *Msg) *Msg {
s, ok := m.genHeader[HeaderSubject]
if !ok {
fmt.Println("can't find the subject header")
}
m.Subject(strings.Replace(s[0], "a", "@", -1))
return m
}
// TestNewMsgWithMiddleware tests WithMiddleware
func TestNewMsgWithMiddleware(t *testing.T) {
m := NewMsg()
if len(m.middlewares) != 0 {
t.Errorf("empty middlewares failed. m.middlewares expected to be: empty, got: %d middleware", len(m.middlewares))
}
m = NewMsg(WithMiddleware(uppercaseMiddleware{}))
if len(m.middlewares) != 1 {
t.Errorf("empty middlewares failed. m.middlewares expected to be: 1, got: %d middleware", len(m.middlewares))
}
m = NewMsg(WithMiddleware(uppercaseMiddleware{}), WithMiddleware(encodeMiddleware{}))
if len(m.middlewares) != 2 {
t.Errorf("empty middlewares failed. m.middlewares expected to be: 2, got: %d middleware", len(m.middlewares))
}
}
// TestApplyMiddlewares tests the applyMiddlewares for the Msg object
func TestApplyMiddlewares(t *testing.T) {
tests := []struct {
name string
sub string
want string
}{
{"normal subject", "This is a test subject", "THIS IS @ TEST SUBJECT"},
{"subject with one middleware effect", "This is test subject", "THIS IS TEST SUBJECT"},
{"subject with one middleware effect", "This is A test subject", "THIS IS A TEST SUBJECT"},
}
m := NewMsg(WithMiddleware(encodeMiddleware{}), WithMiddleware(uppercaseMiddleware{}))
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
m.Subject(tt.sub)
if m.genHeader[HeaderSubject] == nil {
t.Errorf("Subject() method failed in applyMiddlewares() test. Generic header for subject is empty")
return
}
m = m.applyMiddlewares(m)
s, ok := m.genHeader[HeaderSubject]
if !ok {
t.Errorf("failed to get subject header")
}
if s[0] != tt.want {
t.Errorf("applyMiddlewares() method failed. Expected: %s, got: %s", tt.want, s[0])
}
})
}
}
// TestMsg_SetHEader tests Msg.SetHeader // TestMsg_SetHEader tests Msg.SetHeader
func TestMsg_SetHeader(t *testing.T) { func TestMsg_SetHeader(t *testing.T) {
tests := []struct { tests := []struct {
@ -626,12 +695,18 @@ func TestMsg_FromFormat(t *testing.T) {
want string want string
fail bool fail bool
}{ }{
{"valid name and addr", "Toni Tester", "tester@example.com", {
`"Toni Tester" <tester@example.com>`, false}, "valid name and addr", "Toni Tester", "tester@example.com",
{"no name with valid addr", "", "tester@example.com", `"Toni Tester" <tester@example.com>`, false,
`<tester@example.com>`, false}, },
{"valid name with invalid addr", "Toni Tester", "@example.com", {
``, true}, "no name with valid addr", "", "tester@example.com",
`<tester@example.com>`, false,
},
{
"valid name with invalid addr", "Toni Tester", "@example.com",
``, true,
},
} }
m := NewMsg() m := NewMsg()
@ -697,7 +772,7 @@ func TestMsg_GetRecipients(t *testing.T) {
return return
} }
var tf, cf, bf = false, false, false tf, cf, bf := false, false, false
for _, r := range al { for _, r := range al {
if r == a[0] { if r == a[0] {
tf = true tf = true
@ -732,12 +807,18 @@ func TestMsg_ReplyTo(t *testing.T) {
want string want string
sf bool sf bool
}{ }{
{"valid name and addr", "Toni Tester", "tester@example.com", {
`"Toni Tester" <tester@example.com>`, false}, "valid name and addr", "Toni Tester", "tester@example.com",
{"no name with valid addr", "", "tester@example.com", `"Toni Tester" <tester@example.com>`, false,
`<tester@example.com>`, false}, },
{"valid name with invalid addr", "Toni Tester", "@example.com", {
``, true}, "no name with valid addr", "", "tester@example.com",
`<tester@example.com>`, false,
},
{
"valid name with invalid addr", "Toni Tester", "@example.com",
``, true,
},
} }
m := NewMsg() m := NewMsg()
for _, tt := range tests { for _, tt := range tests {
@ -792,10 +873,14 @@ func TestMsg_Subject(t *testing.T) {
want string want string
}{ }{
{"normal subject", "This is a test subject", "This is a test subject"}, {"normal subject", "This is a test subject", "This is a test subject"},
{"subject with umlauts", "This is a test subject with umlauts: üäöß", {
"=?UTF-8?q?This_is_a_test_subject_with_umlauts:_=C3=BC=C3=A4=C3=B6=C3=9F?="}, "subject with umlauts", "This is a test subject with umlauts: üäöß",
{"subject with emoji", "This is a test subject with emoji: 📧", "=?UTF-8?q?This_is_a_test_subject_with_umlauts:_=C3=BC=C3=A4=C3=B6=C3=9F?=",
"=?UTF-8?q?This_is_a_test_subject_with_emoji:_=F0=9F=93=A7?="}, },
{
"subject with emoji", "This is a test subject with emoji: 📧",
"=?UTF-8?q?This_is_a_test_subject_with_emoji:_=F0=9F=93=A7?=",
},
} }
m := NewMsg() m := NewMsg()
for _, tt := range tests { for _, tt := range tests {
@ -868,7 +953,6 @@ func TestMsg_SetImportance(t *testing.T) {
m.genHeader = make(map[Header][]string) m.genHeader = make(map[Header][]string)
}) })
} }
} }
// TestMsg_SetOrganization tests the Msg.SetOrganization method // TestMsg_SetOrganization tests the Msg.SetOrganization method
@ -1021,8 +1105,10 @@ func TestMsg_SetBodyString(t *testing.T) {
sf bool sf bool
}{ }{
{"Body: test", TypeTextPlain, "test", "test", false}, {"Body: test", TypeTextPlain, "test", "test", false},
{"Body: with Umlauts", TypeTextHTML, "<strong>üäöß</strong>", {
"<strong>üäöß</strong>", false}, "Body: with Umlauts", TypeTextHTML, "<strong>üäöß</strong>",
"<strong>üäöß</strong>", false,
},
{"Body: with emoji", TypeTextPlain, "📧", "📧", false}, {"Body: with emoji", TypeTextPlain, "📧", "📧", false},
} }
m := NewMsg() m := NewMsg()
@ -1654,8 +1740,10 @@ func TestMsg_SetBodyHTMLTemplate(t *testing.T) {
sf bool sf bool
}{ }{
{"normal HTML", "<p>This is a {{.Placeholder}}</p>", "TemplateTest", "TemplateTest", false}, {"normal HTML", "<p>This is a {{.Placeholder}}</p>", "TemplateTest", "TemplateTest", false},
{"HTML with HTML", "<p>This is a {{.Placeholder}}</p>", "<script>alert(1)</script>", {
"&lt;script&gt;alert(1)&lt;/script&gt;", false}, "HTML with HTML", "<p>This is a {{.Placeholder}}</p>", "<script>alert(1)</script>",
"&lt;script&gt;alert(1)&lt;/script&gt;", false,
},
{"invalid tpl", "<p>This is a {{ foo .Placeholder}}</p>", "TemplateTest", "", true}, {"invalid tpl", "<p>This is a {{ foo .Placeholder}}</p>", "TemplateTest", "", true},
} }
@ -1738,8 +1826,10 @@ func TestMsg_AddAlternativeHTMLTemplate(t *testing.T) {
sf bool sf bool
}{ }{
{"normal HTML", "<p>This is a {{.Placeholder}}</p>", "TemplateTest", "TemplateTest", false}, {"normal HTML", "<p>This is a {{.Placeholder}}</p>", "TemplateTest", "TemplateTest", false},
{"HTML with HTML", "<p>This is a {{.Placeholder}}</p>", "<script>alert(1)</script>", {
"&lt;script&gt;alert(1)&lt;/script&gt;", false}, "HTML with HTML", "<p>This is a {{.Placeholder}}</p>", "<script>alert(1)</script>",
"&lt;script&gt;alert(1)&lt;/script&gt;", false,
},
{"invalid tpl", "<p>This is a {{ foo .Placeholder}}</p>", "TemplateTest", "", true}, {"invalid tpl", "<p>This is a {{ foo .Placeholder}}</p>", "TemplateTest", "", true},
} }
@ -1782,8 +1872,10 @@ func TestMsg_AttachTextTemplate(t *testing.T) {
ac int ac int
sf bool sf bool
}{ }{
{"normal text", "This is a {{.Placeholder}}", "TemplateTest", {
"VGhpcyBpcyBhIFRlbXBsYXRlVGVzdA==", 1, false}, "normal text", "This is a {{.Placeholder}}", "TemplateTest",
"VGhpcyBpcyBhIFRlbXBsYXRlVGVzdA==", 1, false,
},
{"invalid tpl", "This is a {{ foo .Placeholder}}", "TemplateTest", "", 0, true}, {"invalid tpl", "This is a {{ foo .Placeholder}}", "TemplateTest", "", 0, true},
} }
@ -1829,10 +1921,14 @@ func TestMsg_AttachHTMLTemplate(t *testing.T) {
ac int ac int
sf bool sf bool
}{ }{
{"normal HTML", "<p>This is a {{.Placeholder}}</p>", "TemplateTest", {
"PHA+VGhpcyBpcyBhIFRlbXBsYXRlVGVzdDwvcD4=", 1, false}, "normal HTML", "<p>This is a {{.Placeholder}}</p>", "TemplateTest",
{"HTML with HTML", "<p>This is a {{.Placeholder}}</p>", "<script>alert(1)</script>", "PHA+VGhpcyBpcyBhIFRlbXBsYXRlVGVzdDwvcD4=", 1, false,
"PHA+VGhpcyBpcyBhICZsdDtzY3JpcHQmZ3Q7YWxlcnQoMSkmbHQ7L3NjcmlwdCZndDs8L3A+", 1, false}, },
{
"HTML with HTML", "<p>This is a {{.Placeholder}}</p>", "<script>alert(1)</script>",
"PHA+VGhpcyBpcyBhICZsdDtzY3JpcHQmZ3Q7YWxlcnQoMSkmbHQ7L3NjcmlwdCZndDs8L3A+", 1, false,
},
{"invalid tpl", "<p>This is a {{ foo .Placeholder}}</p>", "TemplateTest", "", 0, true}, {"invalid tpl", "<p>This is a {{ foo .Placeholder}}</p>", "TemplateTest", "", 0, true},
} }
@ -1878,8 +1974,10 @@ func TestMsg_EmbedTextTemplate(t *testing.T) {
ec int ec int
sf bool sf bool
}{ }{
{"normal text", "This is a {{.Placeholder}}", "TemplateTest", {
"VGhpcyBpcyBhIFRlbXBsYXRlVGVzdA==", 1, false}, "normal text", "This is a {{.Placeholder}}", "TemplateTest",
"VGhpcyBpcyBhIFRlbXBsYXRlVGVzdA==", 1, false,
},
{"invalid tpl", "This is a {{ foo .Placeholder}}", "TemplateTest", "", 0, true}, {"invalid tpl", "This is a {{ foo .Placeholder}}", "TemplateTest", "", 0, true},
} }
@ -1925,10 +2023,14 @@ func TestMsg_EmbedHTMLTemplate(t *testing.T) {
ec int ec int
sf bool sf bool
}{ }{
{"normal HTML", "<p>This is a {{.Placeholder}}</p>", "TemplateTest", {
"PHA+VGhpcyBpcyBhIFRlbXBsYXRlVGVzdDwvcD4=", 1, false}, "normal HTML", "<p>This is a {{.Placeholder}}</p>", "TemplateTest",
{"HTML with HTML", "<p>This is a {{.Placeholder}}</p>", "<script>alert(1)</script>", "PHA+VGhpcyBpcyBhIFRlbXBsYXRlVGVzdDwvcD4=", 1, false,
"PHA+VGhpcyBpcyBhICZsdDtzY3JpcHQmZ3Q7YWxlcnQoMSkmbHQ7L3NjcmlwdCZndDs8L3A+", 1, false}, },
{
"HTML with HTML", "<p>This is a {{.Placeholder}}</p>", "<script>alert(1)</script>",
"PHA+VGhpcyBpcyBhICZsdDtzY3JpcHQmZ3Q7YWxlcnQoMSkmbHQ7L3NjcmlwdCZndDs8L3A+", 1, false,
},
{"invalid tpl", "<p>This is a {{ foo .Placeholder}}</p>", "TemplateTest", "", 0, true}, {"invalid tpl", "<p>This is a {{ foo .Placeholder}}</p>", "TemplateTest", "", 0, true},
} }