From 2e60d070a657317f87f700d2433762b556bf8d43 Mon Sep 17 00:00:00 2001 From: Winni Neessen Date: Wed, 26 Oct 2022 15:33:03 +0200 Subject: [PATCH] Add SetHeaderPreformatted() method With the SetHeaderPreformatted() method we have the ability to set headers that are already preformatted by the user and will not be altered in the mail message output --- msg.go | 34 +++++++++++++++++++++++++++++----- msg_test.go | 42 +++++++++++++++++++++++++++++++++++++++++- msgwriter.go | 8 ++++++++ 3 files changed, 78 insertions(+), 6 deletions(-) diff --git a/msg.go b/msg.go index 762dbd2..6e4f8b3 100644 --- a/msg.go +++ b/msg.go @@ -77,6 +77,11 @@ type Msg struct { // genHeader is a slice of strings that the different generic mail Header fields genHeader map[Header][]string + // preformHeader is a slice of strings that the different generic mail Header fields + // of which content is already preformated and will not be affected by the automatic line + // breaks + preformHeader map[Header]string + // mimever represents the MIME version mimever MIMEVersion @@ -96,11 +101,12 @@ type MsgOption func(*Msg) // NewMsg returns a new Msg pointer func NewMsg(o ...MsgOption) *Msg { m := &Msg{ - addrHeader: make(map[AddrHeader][]*mail.Address), - charset: CharsetUTF8, - encoding: EncodingQP, - genHeader: make(map[Header][]string), - mimever: Mime10, + addrHeader: make(map[AddrHeader][]*mail.Address), + charset: CharsetUTF8, + encoding: EncodingQP, + genHeader: make(map[Header][]string), + preformHeader: make(map[Header]string), + mimever: Mime10, } // Override defaults with optionally provided MsgOption functions @@ -194,6 +200,24 @@ func (m *Msg) SetHeader(h Header, v ...string) { m.genHeader[h] = v } +// SetHeaderPreformatted sets a generic header field of the Msg which content is +// already preformated. +// +// This method does not take a slice of values but only a single value. This is +// due to the fact, that we do not perform any content alteration and expect the +// user has already done so +// +// **Please note:** This method should be used only as a last resort. Since the +// user is respondible for the formating of the message header, go-mail cannot +// guarantee the fully compliance with the RFC 2822. It is recommended to use +// SetHeader instead. +func (m *Msg) SetHeaderPreformatted(h Header, v string) { + if m.preformHeader == nil { + m.preformHeader = make(map[Header]string) + } + m.preformHeader[h] = v +} + // SetAddrHeader sets an address related header field of the Msg func (m *Msg) SetAddrHeader(h AddrHeader, v ...string) error { if m.addrHeader == nil { diff --git a/msg_test.go b/msg_test.go index dc55eca..8cb3b6d 100644 --- a/msg_test.go +++ b/msg_test.go @@ -255,7 +255,7 @@ func TestApplyMiddlewares(t *testing.T) { } } -// TestMsg_SetHEader tests Msg.SetHeader +// TestMsg_SetHeader tests Msg.SetHeader func TestMsg_SetHeader(t *testing.T) { tests := []struct { name string @@ -289,6 +289,46 @@ func TestMsg_SetHeader(t *testing.T) { } } +// TestMsg_SetHeaderPreformatted tests Msg.SetHeaderPreformatted +func TestMsg_SetHeaderPreformatted(t *testing.T) { + tests := []struct { + name string + header Header + value string + }{ + {"set subject", HeaderSubject, "This is Subject"}, + {"set content-language", HeaderContentLang, fmt.Sprintf("%s, %s, %s, %s", + "en", "de", "fr", "es")}, + {"set subject with newline", HeaderSubject, "This is Subject\r\n with 2nd line"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m := &Msg{} + m.SetHeaderPreformatted(tt.header, tt.value) + m = NewMsg() + m.SetHeaderPreformatted(tt.header, tt.value) + if m.preformHeader[tt.header] == "" { + t.Errorf("SetHeaderPreformatted() failed. Tried to set header %s, but it is empty", tt.header) + } + if m.preformHeader[tt.header] != tt.value { + t.Errorf("SetHeaderPreformatted() failed. Expected: %q, got: %q", tt.value, + m.preformHeader[tt.header]) + } + buf := bytes.Buffer{} + _, err := m.WriteTo(&buf) + if err != nil { + t.Errorf("failed to write message to memory: %s", err) + return + } + if !strings.Contains(buf.String(), fmt.Sprintf("%s: %s%s", tt.header, tt.value, SingleNewLine)) { + t.Errorf("SetHeaderPreformatted() failed. Unable to find correctly formated header in " + + "mail message output") + } + }) + } +} + // TestMsg_AddTo tests the Msg.AddTo method func TestMsg_AddTo(t *testing.T) { a := []string{"address1@example.com", "address2@example.com"} diff --git a/msgwriter.go b/msgwriter.go index 8a28ea1..6b5a4b0 100644 --- a/msgwriter.go +++ b/msgwriter.go @@ -62,6 +62,7 @@ func (mw *msgWriter) writeMsg(m *Msg) { m.addDefaultHeader() m.checkUserAgent() mw.writeGenHeader(m) + mw.writePreformattedGenHeader(m) // Set the FROM header (or envelope FROM if FROM is empty) hf := true @@ -132,6 +133,13 @@ func (mw *msgWriter) writeGenHeader(m *Msg) { } } +// writePreformatedHeader writes out all preformated generic headers to the msgWriter +func (mw *msgWriter) writePreformattedGenHeader(m *Msg) { + for k, v := range m.preformHeader { + mw.writeString(fmt.Sprintf("%s: %s%s", k, v, SingleNewLine)) + } +} + // startMP writes a multipart beginning func (mw *msgWriter) startMP(mt MIMEType, b string) { mp := multipart.NewWriter(mw)