mirror of
https://github.com/wneessen/go-mail.git
synced 2024-11-26 07:25:07 +01:00
Implement middleware concept and their test
This commit is contained in:
parent
7b030473a6
commit
a4733f0618
2 changed files with 162 additions and 37 deletions
25
msg.go
25
msg.go
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
174
msg_test.go
174
msg_test.go
|
@ -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>",
|
{
|
||||||
"<script>alert(1)</script>", false},
|
"HTML with HTML", "<p>This is a {{.Placeholder}}</p>", "<script>alert(1)</script>",
|
||||||
|
"<script>alert(1)</script>", 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>",
|
{
|
||||||
"<script>alert(1)</script>", false},
|
"HTML with HTML", "<p>This is a {{.Placeholder}}</p>", "<script>alert(1)</script>",
|
||||||
|
"<script>alert(1)</script>", 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},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue