#80: GetAddrHeader and SetGenHeader

This PR introduces two major changes:

* SetHeader and SetHeaderPreformatted have been deprecated in favour of SetGenHeader and SetGenHeaderPreformatted
  As pointed out in #80 the naming was pretty confusing, given that we already have SetAddrHeader. With the new naming convention it should be more clear. For compatibility reasons the old methods have been kept for now but in reality they are just aliases to the new methods
* GetAddrHeader and GetAddrHeaderString have been introduced
  As requested in #80 analogous to GetGenHeader we also need a similar method for the address headers. Since address headers are *mail.Address pointer, we've also added a *String method that will extract the address string and return a string slice instead
  Additionally we're introducing methods for the actual address headers: GetTo, GetFrom, GetCc and GetBcc (with a *String counterpart as well). This way the user has full flexibility. Either they use the more "low-level" GetAddrHeader method or the higher level methods for the corresponding address type
This commit is contained in:
Winni Neessen 2022-11-19 11:22:20 +01:00
parent 957e705f16
commit 17b9d2ccf6
Signed by: wneessen
GPG key ID: 385AC9889632126E
2 changed files with 327 additions and 30 deletions

104
msg.go
View file

@ -190,7 +190,16 @@ func (m *Msg) Charset() string {
}
// SetHeader sets a generic header field of the Msg
// For adding address headers like "To:" or "From", see SetAddrHeader
//
// Deprecated: This method only exists for compatibility reason. Please use SetGenHeader instead
func (m *Msg) SetHeader(h Header, v ...string) {
m.SetGenHeader(h, v...)
}
// SetGenHeader sets a generic header field of the Msg
// For adding address headers like "To:" or "From", see SetAddrHeader
func (m *Msg) SetGenHeader(h Header, v ...string) {
if m.genHeader == nil {
m.genHeader = make(map[Header][]string)
}
@ -203,6 +212,15 @@ func (m *Msg) SetHeader(h Header, v ...string) {
// SetHeaderPreformatted sets a generic header field of the Msg which content is
// already preformated.
//
// Deprecated: This method only exists for compatibility reason. Please use
// SetGenHeaderPreformatted instead
func (m *Msg) SetHeaderPreformatted(h Header, v string) {
m.SetGenHeaderPreformatted(h, v)
}
// SetGenHeaderPreformatted 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
@ -210,8 +228,8 @@ func (m *Msg) SetHeader(h Header, v ...string) {
// **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) {
// SetGenHeader instead.
func (m *Msg) SetGenHeaderPreformatted(h Header, v string) {
if m.preformHeader == nil {
m.preformHeader = make(map[Header]string)
}
@ -349,7 +367,7 @@ func (m *Msg) ReplyTo(r string) error {
if err != nil {
return fmt.Errorf("failed to parse reply-to address: %w", err)
}
m.SetHeader(HeaderReplyTo, rt.String())
m.SetGenHeader(HeaderReplyTo, rt.String())
return nil
}
@ -371,7 +389,7 @@ func (m *Msg) addAddr(h AddrHeader, a string) error {
// Subject sets the "Subject" header field of the Msg
func (m *Msg) Subject(s string) {
m.SetHeader(HeaderSubject, s)
m.SetGenHeader(HeaderSubject, s)
}
// SetMessageID generates a random message id for the mail
@ -390,25 +408,25 @@ func (m *Msg) SetMessageID() {
// SetMessageIDWithValue sets the message id for the mail
func (m *Msg) SetMessageIDWithValue(v string) {
m.SetHeader(HeaderMessageID, fmt.Sprintf("<%s>", v))
m.SetGenHeader(HeaderMessageID, fmt.Sprintf("<%s>", v))
}
// SetBulk sets the "Precedence: bulk" genHeader which is recommended for
// automated mails like OOO replies
// See: https://www.rfc-editor.org/rfc/rfc2076#section-3.9
func (m *Msg) SetBulk() {
m.SetHeader(HeaderPrecedence, "bulk")
m.SetGenHeader(HeaderPrecedence, "bulk")
}
// SetDate sets the Date genHeader field to the current time in a valid format
func (m *Msg) SetDate() {
ts := time.Now().Format(time.RFC1123Z)
m.SetHeader(HeaderDate, ts)
m.SetGenHeader(HeaderDate, ts)
}
// SetDateWithValue sets the Date genHeader field to the provided time in a valid format
func (m *Msg) SetDateWithValue(t time.Time) {
m.SetHeader(HeaderDate, t.Format(time.RFC1123Z))
m.SetGenHeader(HeaderDate, t.Format(time.RFC1123Z))
}
// SetImportance sets the Msg Importance/Priority header to given Importance
@ -416,21 +434,21 @@ func (m *Msg) SetImportance(i Importance) {
if i == ImportanceNormal {
return
}
m.SetHeader(HeaderImportance, i.String())
m.SetHeader(HeaderPriority, i.NumString())
m.SetHeader(HeaderXPriority, i.XPrioString())
m.SetHeader(HeaderXMSMailPriority, i.NumString())
m.SetGenHeader(HeaderImportance, i.String())
m.SetGenHeader(HeaderPriority, i.NumString())
m.SetGenHeader(HeaderXPriority, i.XPrioString())
m.SetGenHeader(HeaderXMSMailPriority, i.NumString())
}
// SetOrganization sets the provided string as Organization header for the Msg
func (m *Msg) SetOrganization(o string) {
m.SetHeader(HeaderOrganization, o)
m.SetGenHeader(HeaderOrganization, o)
}
// SetUserAgent sets the User-Agent/X-Mailer header for the Msg
func (m *Msg) SetUserAgent(a string) {
m.SetHeader(HeaderUserAgent, a)
m.SetHeader(HeaderXMailer, a)
m.SetGenHeader(HeaderUserAgent, a)
m.SetGenHeader(HeaderXMailer, a)
}
// RequestMDNTo adds the Disposition-Notification-To header to request a MDN from the receiving end
@ -511,6 +529,60 @@ func (m *Msg) GetRecipients() ([]string, error) {
return rl, nil
}
// GetAddrHeader returns the content of the requested address header of the Msg
func (m *Msg) GetAddrHeader(h AddrHeader) []*mail.Address {
return m.addrHeader[h]
}
// GetAddrHeaderString returns the address string of the requested address header of the Msg
func (m *Msg) GetAddrHeaderString(h AddrHeader) []string {
var al []string
for i := range m.addrHeader[h] {
al = append(al, m.addrHeader[h][i].String())
}
return al
}
// GetFrom returns the content of the From address header of the Msg
func (m *Msg) GetFrom() []*mail.Address {
return m.GetAddrHeader(HeaderFrom)
}
// GetFromString returns the content of the From address header of the Msg as string slice
func (m *Msg) GetFromString() []string {
return m.GetAddrHeaderString(HeaderFrom)
}
// GetTo returns the content of the To address header of the Msg
func (m *Msg) GetTo() []*mail.Address {
return m.GetAddrHeader(HeaderTo)
}
// GetToString returns the content of the To address header of the Msg as string slice
func (m *Msg) GetToString() []string {
return m.GetAddrHeaderString(HeaderTo)
}
// GetCc returns the content of the Cc address header of the Msg
func (m *Msg) GetCc() []*mail.Address {
return m.GetAddrHeader(HeaderCc)
}
// GetCcString returns the content of the Cc address header of the Msg as string slice
func (m *Msg) GetCcString() []string {
return m.GetAddrHeaderString(HeaderCc)
}
// GetBcc returns the content of the Bcc address header of the Msg
func (m *Msg) GetBcc() []*mail.Address {
return m.GetAddrHeader(HeaderBcc)
}
// GetBccString returns the content of the Bcc address header of the Msg as string slice
func (m *Msg) GetBccString() []string {
return m.GetAddrHeaderString(HeaderBcc)
}
// GetGenHeader returns the content of the requested generic header of the Msg
func (m *Msg) GetGenHeader(h Header) []string {
return m.genHeader[h]
@ -948,7 +1020,7 @@ func (m *Msg) addDefaultHeader() {
if _, ok := m.genHeader[HeaderMessageID]; !ok {
m.SetMessageID()
}
m.SetHeader(HeaderMIMEVersion, string(m.mimever))
m.SetGenHeader(HeaderMIMEVersion, string(m.mimever))
}
// fileFromEmbedFS returns a File pointer from a given file in the provided embed.FS

View file

@ -255,8 +255,8 @@ func TestApplyMiddlewares(t *testing.T) {
}
}
// TestMsg_SetHeader tests Msg.SetHeader
func TestMsg_SetHeader(t *testing.T) {
// TestMsg_SetGenHeader tests Msg.SetGenHeader
func TestMsg_SetGenHeader(t *testing.T) {
tests := []struct {
name string
header Header
@ -269,9 +269,9 @@ func TestMsg_SetHeader(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
m := NewMsg()
m.SetHeader(tt.header, tt.values...)
m.SetGenHeader(tt.header, tt.values...)
if m.genHeader[tt.header] == nil {
t.Errorf("SetHeader() failed. Tried to set header %s, but it is empty", tt.header)
t.Errorf("SetGenHeader() failed. Tried to set header %s, but it is empty", tt.header)
return
}
for _, v := range tt.values {
@ -282,15 +282,15 @@ func TestMsg_SetHeader(t *testing.T) {
}
}
if !found {
t.Errorf("SetHeader() failed. Value %s not found in header field", v)
t.Errorf("SetGenHeader() failed. Value %s not found in header field", v)
}
}
})
}
}
// TestMsg_SetHeaderPreformatted tests Msg.SetHeaderPreformatted
func TestMsg_SetHeaderPreformatted(t *testing.T) {
// TestMsg_SetGenHeaderPreformatted tests Msg.SetGenHeaderPreformatted
func TestMsg_SetGenHeaderPreformatted(t *testing.T) {
tests := []struct {
name string
header Header
@ -305,14 +305,14 @@ func TestMsg_SetHeaderPreformatted(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
m := &Msg{}
m.SetHeaderPreformatted(tt.header, tt.value)
m.SetGenHeaderPreformatted(tt.header, tt.value)
m = NewMsg()
m.SetHeaderPreformatted(tt.header, tt.value)
m.SetGenHeaderPreformatted(tt.header, tt.value)
if m.preformHeader[tt.header] == "" {
t.Errorf("SetHeaderPreformatted() failed. Tried to set header %s, but it is empty", tt.header)
t.Errorf("SetGenHeaderPreformatted() 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,
t.Errorf("SetGenHeaderPreformatted() failed. Expected: %q, got: %q", tt.value,
m.preformHeader[tt.header])
}
buf := bytes.Buffer{}
@ -322,7 +322,7 @@ func TestMsg_SetHeaderPreformatted(t *testing.T) {
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 " +
t.Errorf("SetGenHeaderPreformatted() failed. Unable to find correctly formated header in " +
"mail message output")
}
})
@ -1703,9 +1703,9 @@ func TestMsg_Write(t *testing.T) {
func TestMsg_WriteWithLongHeader(t *testing.T) {
m := NewMsg()
m.SetBodyString(TypeTextPlain, "Plain")
m.SetHeader(HeaderContentLang, "de", "en", "fr", "es", "xxxx", "yyyy", "de", "en", "fr",
m.SetGenHeader(HeaderContentLang, "de", "en", "fr", "es", "xxxx", "yyyy", "de", "en", "fr",
"es", "xxxx", "yyyy", "de", "en", "fr", "es", "xxxx", "yyyy", "de", "en", "fr")
m.SetHeader(HeaderContentID, "XXXXXXXXXXXXXXX XXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXX",
m.SetGenHeader(HeaderContentID, "XXXXXXXXXXXXXXX XXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXX",
"XXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXX")
wbuf := bytes.Buffer{}
n, err := m.WriteTo(&wbuf)
@ -2324,3 +2324,228 @@ func TestMsg_GetGenHeader(t *testing.T) {
t.Errorf("GetGenHeader on subject failed. Expected: %q, got: %q", "this is a test", sa[0])
}
}
// TestMsg_GetAddrHeader will test the Msg.GetAddrHeader method
func TestMsg_GetAddrHeader(t *testing.T) {
m := NewMsg()
if err := m.FromFormat("Toni Sender", "sender@example.com"); err != nil {
t.Errorf("failed to set FROM address: %s", err)
}
if err := m.AddToFormat("Toni To", "to@example.com"); err != nil {
t.Errorf("failed to set TO address: %s", err)
}
if err := m.AddCcFormat("Toni Cc", "cc@example.com"); err != nil {
t.Errorf("failed to set CC address: %s", err)
}
if err := m.AddBccFormat("Toni Bcc", "bcc@example.com"); err != nil {
t.Errorf("failed to set BCC address: %s", err)
}
fh := m.GetAddrHeader(HeaderFrom)
if len(fh) <= 0 {
t.Errorf("GetAddrHeader on FROM failed. Got empty slice")
return
}
if fh[0].String() == "" {
t.Errorf("GetAddrHeader on FROM failed. Got empty value")
}
if fh[0].String() != `"Toni Sender" <sender@example.com>` {
t.Errorf("GetAddrHeader on FROM failed. Expected: %q, got: %q",
`"Toni Sender" <sender@example.com>"`, fh[0].String())
}
th := m.GetAddrHeader(HeaderTo)
if len(th) <= 0 {
t.Errorf("GetAddrHeader on TO failed. Got empty slice")
return
}
if th[0].String() == "" {
t.Errorf("GetAddrHeader on TO failed. Got empty value")
}
if th[0].String() != `"Toni To" <to@example.com>` {
t.Errorf("GetAddrHeader on TO failed. Expected: %q, got: %q",
`"Toni To" <to@example.com>"`, th[0].String())
}
ch := m.GetAddrHeader(HeaderCc)
if len(ch) <= 0 {
t.Errorf("GetAddrHeader on CC failed. Got empty slice")
return
}
if ch[0].String() == "" {
t.Errorf("GetAddrHeader on CC failed. Got empty value")
}
if ch[0].String() != `"Toni Cc" <cc@example.com>` {
t.Errorf("GetAddrHeader on CC failed. Expected: %q, got: %q",
`"Toni Cc" <cc@example.com>"`, ch[0].String())
}
bh := m.GetAddrHeader(HeaderBcc)
if len(bh) <= 0 {
t.Errorf("GetAddrHeader on BCC failed. Got empty slice")
return
}
if bh[0].String() == "" {
t.Errorf("GetAddrHeader on BCC failed. Got empty value")
}
if bh[0].String() != `"Toni Bcc" <bcc@example.com>` {
t.Errorf("GetAddrHeader on BCC failed. Expected: %q, got: %q",
`"Toni Bcc" <bcc@example.com>"`, bh[0].String())
}
}
// TestMsg_GetFrom will test the Msg.GetFrom method
func TestMsg_GetFrom(t *testing.T) {
m := NewMsg()
if err := m.FromFormat("Toni Sender", "sender@example.com"); err != nil {
t.Errorf("failed to set FROM address: %s", err)
}
fh := m.GetFrom()
if len(fh) <= 0 {
t.Errorf("GetFrom failed. Got empty slice")
return
}
if fh[0].String() == "" {
t.Errorf("GetFrom failed. Got empty value")
}
if fh[0].String() != `"Toni Sender" <sender@example.com>` {
t.Errorf("GetFrom failed. Expected: %q, got: %q",
`"Toni Sender" <sender@example.com>"`, fh[0].String())
}
}
// TestMsg_GetFromString will test the Msg.GetFromString method
func TestMsg_GetFromString(t *testing.T) {
m := NewMsg()
if err := m.FromFormat("Toni Sender", "sender@example.com"); err != nil {
t.Errorf("failed to set FROM address: %s", err)
}
fh := m.GetFromString()
if len(fh) <= 0 {
t.Errorf("GetFromString failed. Got empty slice")
return
}
if fh[0] == "" {
t.Errorf("GetFromString failed. Got empty value")
}
if fh[0] != `"Toni Sender" <sender@example.com>` {
t.Errorf("GetFromString failed. Expected: %q, got: %q",
`"Toni Sender" <sender@example.com>"`, fh[0])
}
}
// TestMsg_GetTo will test the Msg.GetTo method
func TestMsg_GetTo(t *testing.T) {
m := NewMsg()
if err := m.AddToFormat("Toni To", "to@example.com"); err != nil {
t.Errorf("failed to set TO address: %s", err)
}
fh := m.GetTo()
if len(fh) <= 0 {
t.Errorf("GetTo failed. Got empty slice")
return
}
if fh[0].String() == "" {
t.Errorf("GetTo failed. Got empty value")
}
if fh[0].String() != `"Toni To" <to@example.com>` {
t.Errorf("GetTo failed. Expected: %q, got: %q",
`"Toni To" <to@example.com>"`, fh[0].String())
}
}
// TestMsg_GetToString will test the Msg.GetToString method
func TestMsg_GetToString(t *testing.T) {
m := NewMsg()
if err := m.AddToFormat("Toni To", "to@example.com"); err != nil {
t.Errorf("failed to set TO address: %s", err)
}
fh := m.GetToString()
if len(fh) <= 0 {
t.Errorf("GetToString failed. Got empty slice")
return
}
if fh[0] == "" {
t.Errorf("GetToString failed. Got empty value")
}
if fh[0] != `"Toni To" <to@example.com>` {
t.Errorf("GetToString failed. Expected: %q, got: %q",
`"Toni To" <to@example.com>"`, fh[0])
}
}
// TestMsg_GetCc will test the Msg.GetCc method
func TestMsg_GetCc(t *testing.T) {
m := NewMsg()
if err := m.AddCcFormat("Toni Cc", "cc@example.com"); err != nil {
t.Errorf("failed to set TO address: %s", err)
}
fh := m.GetCc()
if len(fh) <= 0 {
t.Errorf("GetCc failed. Got empty slice")
return
}
if fh[0].String() == "" {
t.Errorf("GetCc failed. Got empty value")
}
if fh[0].String() != `"Toni Cc" <cc@example.com>` {
t.Errorf("GetCc failed. Expected: %q, got: %q",
`"Toni Cc" <cc@example.com>"`, fh[0].String())
}
}
// TestMsg_GetCcString will test the Msg.GetCcString method
func TestMsg_GetCcString(t *testing.T) {
m := NewMsg()
if err := m.AddCcFormat("Toni Cc", "cc@example.com"); err != nil {
t.Errorf("failed to set TO address: %s", err)
}
fh := m.GetCcString()
if len(fh) <= 0 {
t.Errorf("GetCcString failed. Got empty slice")
return
}
if fh[0] == "" {
t.Errorf("GetCcString failed. Got empty value")
}
if fh[0] != `"Toni Cc" <cc@example.com>` {
t.Errorf("GetCcString failed. Expected: %q, got: %q",
`"Toni Cc" <cc@example.com>"`, fh[0])
}
}
// TestMsg_GetBcc will test the Msg.GetBcc method
func TestMsg_GetBcc(t *testing.T) {
m := NewMsg()
if err := m.AddBccFormat("Toni Bcc", "bcc@example.com"); err != nil {
t.Errorf("failed to set TO address: %s", err)
}
fh := m.GetBcc()
if len(fh) <= 0 {
t.Errorf("GetBcc failed. Got empty slice")
return
}
if fh[0].String() == "" {
t.Errorf("GetBcc failed. Got empty value")
}
if fh[0].String() != `"Toni Bcc" <bcc@example.com>` {
t.Errorf("GetBcc failed. Expected: %q, got: %q",
`"Toni Cc" <bcc@example.com>"`, fh[0].String())
}
}
// TestMsg_GetBccString will test the Msg.GetBccString method
func TestMsg_GetBccString(t *testing.T) {
m := NewMsg()
if err := m.AddBccFormat("Toni Bcc", "bcc@example.com"); err != nil {
t.Errorf("failed to set TO address: %s", err)
}
fh := m.GetBccString()
if len(fh) <= 0 {
t.Errorf("GetBccString failed. Got empty slice")
return
}
if fh[0] == "" {
t.Errorf("GetBccString failed. Got empty value")
}
if fh[0] != `"Toni Bcc" <bcc@example.com>` {
t.Errorf("GetBccString failed. Expected: %q, got: %q",
`"Toni Cc" <bcc@example.com>"`, fh[0])
}
}