diff --git a/doc_test.go b/doc_test.go index b53420d..8653919 100644 --- a/doc_test.go +++ b/doc_test.go @@ -27,6 +27,13 @@ func ExampleClient_SetTLSPolicy() { // Output: TLSMandatory } +// Code example for the NewMsg method +func ExampleNewMsg() { + m := mail.NewMsg(mail.WithEncoding(mail.EncodingQP), mail.WithCharset(mail.CharsetASCII)) + fmt.Printf("%s // %s\n", m.Encoding(), m.Charset()) + // Output: quoted-printable // US-ASCII +} + // Code example for the Client.DialAndSend method func ExampleClient_DialAndSend() { from := "Toni Tester " diff --git a/encoding.go b/encoding.go index 44e333b..74f8e86 100644 --- a/encoding.go +++ b/encoding.go @@ -116,3 +116,13 @@ const ( // CharsetGBK represents the "GBK" charset CharsetGBK Charset = "GBK" ) + +// String is a standard method to convert an Encoding into a printable format +func (e Encoding) String() string { + return string(e) +} + +// String is a standard method to convert an Charset into a printable format +func (c Charset) String() string { + return string(c) +} diff --git a/msg.go b/msg.go index 4de923e..8919903 100644 --- a/msg.go +++ b/msg.go @@ -87,6 +87,16 @@ func (m *Msg) SetEncoding(e Encoding) { m.encoding = e } +// Encoding returns the currently set encoding of the Msg +func (m *Msg) Encoding() string { + return m.encoding.String() +} + +// Charset returns the currently set charset of the Msg +func (m *Msg) Charset() string { + return m.charset.String() +} + // SetHeader sets a generic header field of the Msg func (m *Msg) SetHeader(h Header, v ...string) { for i, hv := range v { @@ -146,17 +156,13 @@ func (m *Msg) To(t ...string) error { // AddTo adds an additional address to the To address header field func (m *Msg) AddTo(t string) error { - var tl []string - for _, ct := range m.addrHeader[HeaderTo] { - tl = append(tl, ct.String()) - } - tl = append(tl, t) - return m.To(tl...) + return m.addAddr(HeaderTo, t) } -// Subject sets the "Subject" header field of the Msg -func (m *Msg) Subject(s string) { - m.SetHeader(HeaderSubject, s) +// AddToFormat takes a name and address, formats them RFC5322 compliant and stores them as +// as additional To address header field +func (m *Msg) AddToFormat(n, a string) error { + return m.addAddr(HeaderTo, fmt.Sprintf(`"%s" <%s>`, n, a)) } // ToIgnoreInvalid takes and validates a given mail address list sets the To: addresses of the Msg @@ -170,6 +176,17 @@ func (m *Msg) Cc(c ...string) error { return m.SetAddrHeader(HeaderCc, c...) } +// AddCc adds an additional address to the Cc address header field +func (m *Msg) AddCc(t string) error { + return m.addAddr(HeaderCc, t) +} + +// AddCcFormat takes a name and address, formats them RFC5322 compliant and stores them as +// as additional Cc address header field +func (m *Msg) AddCcFormat(n, a string) error { + return m.addAddr(HeaderCc, fmt.Sprintf(`"%s" <%s>`, n, a)) +} + // CcIgnoreInvalid takes and validates a given mail address list sets the Cc: addresses of the Msg // Any provided address that is not RFC5322 compliant, will be ignored func (m *Msg) CcIgnoreInvalid(c ...string) { @@ -181,12 +198,38 @@ func (m *Msg) Bcc(b ...string) error { return m.SetAddrHeader(HeaderBcc, b...) } +// AddBcc adds an additional address to the Bcc address header field +func (m *Msg) AddBcc(t string) error { + return m.addAddr(HeaderBcc, t) +} + +// AddBccFormat takes a name and address, formats them RFC5322 compliant and stores them as +// as additional Bcc address header field +func (m *Msg) AddBccFormat(n, a string) error { + return m.addAddr(HeaderBcc, fmt.Sprintf(`"%s" <%s>`, n, a)) +} + // BccIgnoreInvalid takes and validates a given mail address list sets the Bcc: addresses of the Msg // Any provided address that is not RFC5322 compliant, will be ignored func (m *Msg) BccIgnoreInvalid(b ...string) { m.SetAddrHeaderIgnoreInvalid(HeaderBcc, b...) } +// addAddr adds an additional address to the given addrHeader of the Msg +func (m *Msg) addAddr(h AddrHeader, a string) error { + var al []string + for _, ca := range m.addrHeader[h] { + al = append(al, ca.String()) + } + al = append(al, a) + return m.SetAddrHeader(h, al...) +} + +// Subject sets the "Subject" header field of the Msg +func (m *Msg) Subject(s string) { + m.SetHeader(HeaderSubject, s) +} + // SetMessageID generates a random message id for the mail func (m *Msg) SetMessageID() { hn, err := os.Hostname() diff --git a/msg_test.go b/msg_test.go index b626bf6..d360996 100644 --- a/msg_test.go +++ b/msg_test.go @@ -1,11 +1,152 @@ package mail import ( + "fmt" "net/mail" "testing" ) -// TestMsg_AddTo tests the AddTo() method for the Msg object +// TestNewMsg tests the NewMsg method +func TestNewMsg(t *testing.T) { + m := NewMsg() + var err error + if m.encoding != EncodingQP { + err = fmt.Errorf("default encoding is not Quoted-Prinable") + } + if m.charset != CharsetUTF8 { + err = fmt.Errorf("default charset is not UTF-8") + } + + if err != nil { + t.Errorf("NewMsg() failed: %s", err) + return + } +} + +// TestNewMsgCharset tests WithCharset and Msg.SetCharset +func TestNewMsgCharset(t *testing.T) { + tests := []struct { + name string + value Charset + want Charset + }{ + {"charset is UTF-7", CharsetUTF7, "UTF-7"}, + {"charset is UTF-8", CharsetUTF8, "UTF-8"}, + {"charset is US-ASCII", CharsetASCII, "US-ASCII"}, + {"charset is ISO-8859-1", CharsetISO88591, "ISO-8859-1"}, + {"charset is ISO-8859-2", CharsetISO88592, "ISO-8859-2"}, + {"charset is ISO-8859-3", CharsetISO88593, "ISO-8859-3"}, + {"charset is ISO-8859-4", CharsetISO88594, "ISO-8859-4"}, + {"charset is ISO-8859-5", CharsetISO88595, "ISO-8859-5"}, + {"charset is ISO-8859-6", CharsetISO88596, "ISO-8859-6"}, + {"charset is ISO-8859-7", CharsetISO88597, "ISO-8859-7"}, + {"charset is ISO-8859-9", CharsetISO88599, "ISO-8859-9"}, + {"charset is ISO-8859-13", CharsetISO885913, "ISO-8859-13"}, + {"charset is ISO-8859-14", CharsetISO885914, "ISO-8859-14"}, + {"charset is ISO-8859-15", CharsetISO885915, "ISO-8859-15"}, + {"charset is ISO-8859-16", CharsetISO885916, "ISO-8859-16"}, + {"charset is ISO-2022-JP", CharsetISO2022JP, "ISO-2022-JP"}, + {"charset is ISO-2022-KR", CharsetISO2022KR, "ISO-2022-KR"}, + {"charset is windows-1250", CharsetWindows1250, "windows-1250"}, + {"charset is windows-1251", CharsetWindows1251, "windows-1251"}, + {"charset is windows-1252", CharsetWindows1252, "windows-1252"}, + {"charset is windows-1255", CharsetWindows1255, "windows-1255"}, + {"charset is windows-1256", CharsetWindows1256, "windows-1256"}, + {"charset is KOI8-R", CharsetKOI8R, "KOI8-R"}, + {"charset is KOI8-U", CharsetKOI8U, "KOI8-U"}, + {"charset is Big5", CharsetBig5, "Big5"}, + {"charset is GB18030", CharsetGB18030, "GB18030"}, + {"charset is GB2312", CharsetGB2312, "GB2312"}, + {"charset is TIS-620", CharsetTIS620, "TIS-620"}, + {"charset is EUC-KR", CharsetEUCKR, "EUC-KR"}, + {"charset is Shift_JIS", CharsetShiftJIS, "Shift_JIS"}, + {"charset is GBK", CharsetGBK, "GBK"}, + {"charset is Unknown", CharsetUnknown, "Unknown"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m := NewMsg(WithCharset(tt.value)) + if m.charset != tt.want { + t.Errorf("WithCharset() failed. Expected: %s, got: %s", tt.want, m.charset) + } + m.SetCharset(CharsetUTF8) + if m.charset != CharsetUTF8 { + t.Errorf("SetCharset() failed. Expected: %s, got: %s", CharsetUTF8, m.charset) + } + m.SetCharset(tt.value) + if m.charset != tt.want { + t.Errorf("SetCharset() failed. Expected: %s, got: %s", tt.want, m.charset) + } + }) + } +} + +// TestNewMsgWithCharset tests WithEncoding and Msg.SetEncoding +func TestNewMsgWithEncoding(t *testing.T) { + tests := []struct { + name string + value Encoding + want Encoding + }{ + {"encoding is Quoted-Printable", EncodingQP, "quoted-printable"}, + {"encoding is Base64", EncodingB64, "base64"}, + {"encoding is Unencoded 8-Bit", NoEncoding, "8bit"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m := NewMsg(WithEncoding(tt.value)) + if m.encoding != tt.want { + t.Errorf("WithEncoding() failed. Expected: %s, got: %s", tt.want, m.encoding) + } + m.SetEncoding(NoEncoding) + if m.encoding != NoEncoding { + t.Errorf("SetEncoding() failed. Expected: %s, got: %s", NoEncoding, m.encoding) + } + m.SetEncoding(tt.want) + if m.encoding != tt.want { + t.Errorf("SetEncoding() failed. Expected: %s, got: %s", tt.want, m.encoding) + } + }) + } +} + +// TestMsg_SetHEader tests Msg.SetHeader +func TestMsg_SetHeader(t *testing.T) { + tests := []struct { + name string + header Header + values []string + }{ + {"set subject", HeaderSubject, []string{"This is Subject"}}, + {"set content-language", HeaderContentLang, []string{"en", "de", "fr", "es"}}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m := NewMsg() + m.SetHeader(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) + return + } + for _, v := range tt.values { + found := false + for _, hv := range m.genHeader[tt.header] { + if hv == v { + found = true + } + } + if !found { + t.Errorf("SetHeader() failed. Value %s not found in header field", v) + } + } + }) + } +} + +// TestMsg_AddTo tests the Msg.AddTo method func TestMsg_AddTo(t *testing.T) { a := []string{"address1@example.com", "address2@example.com"} na := "address3@example.com" @@ -30,6 +171,56 @@ func TestMsg_AddTo(t *testing.T) { } } +// TestMsg_AddCc tests the Msg.AddCc method +func TestMsg_AddCc(t *testing.T) { + a := []string{"address1@example.com", "address2@example.com"} + na := "address3@example.com" + m := NewMsg() + if err := m.Cc(a...); err != nil { + t.Errorf("failed to set CC addresses: %s", err) + return + } + if err := m.AddCc(na); err != nil { + t.Errorf("AddCc failed: %s", err) + return + } + + atf := false + for _, v := range m.addrHeader[HeaderCc] { + if v.Address == na { + atf = true + } + } + if !atf { + t.Errorf("AddCc() failed. Address %q not found in CC address slice.", na) + } +} + +// TestMsg_AddBcc tests the Msg.AddBcc method +func TestMsg_AddBcc(t *testing.T) { + a := []string{"address1@example.com", "address2@example.com"} + na := "address3@example.com" + m := NewMsg() + if err := m.Bcc(a...); err != nil { + t.Errorf("failed to set BCC addresses: %s", err) + return + } + if err := m.AddBcc(na); err != nil { + t.Errorf("AddBcc failed: %s", err) + return + } + + atf := false + for _, v := range m.addrHeader[HeaderBcc] { + if v.Address == na { + atf = true + } + } + if !atf { + t.Errorf("AddBcc() failed. Address %q not found in BCC address slice.", na) + } +} + // TestMsg_FromFormat tests the FromFormat() method for the Msg object func TestMsg_FromFormat(t *testing.T) { tests := []struct { diff --git a/tls_test.go b/tls_test.go new file mode 100644 index 0000000..99f7028 --- /dev/null +++ b/tls_test.go @@ -0,0 +1,36 @@ +package mail + +import "testing" + +// TestTLSPolicy_String tests the TLSPolicy.String method +func TestTLSPolicy_String(t *testing.T) { + tests := []struct { + name string + value TLSPolicy + want int + }{ + {"TLSPolicy is Mandatory", TLSMandatory, 0}, + {"TLSPolicy is Opportunistic", TLSOpportunistic, 1}, + {"TLSPolicy is NoTLS", NoTLS, 2}, + {"TLSPolicy is Unknown", 3, 3}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c, err := NewClient("mail.example.com", WithTLSPolicy(tt.value)) + if err != nil { + t.Errorf("failed to create new Client: %s", err) + return + } + + if c.tlspolicy != tt.value { + t.Errorf("WithTLSPolicy() failed. Expected: %s (%d), got: %s (%d)", tt.value.String(), tt.value, + c.tlspolicy.String(), c.tlspolicy) + } + if c.tlspolicy.String() != tt.value.String() { + t.Errorf("WithTLSPolicy() failed. Expected: %s (%d), got: %s (%d)", tt.value.String(), tt.value, + c.tlspolicy.String(), c.tlspolicy) + } + }) + } +}