From 6285b5fb4ffef7c3ac4aa3443441acb3b1d77f72 Mon Sep 17 00:00:00 2001 From: Winni Neessen Date: Wed, 9 Mar 2022 16:52:23 +0100 Subject: [PATCH] More progress... calling it a day. --- .idea/workspace.xml | 112 ++++++++++++++++++++++++++++++++++++++++++++ client_test.go | 51 ++++++++++++++++++++ cmd/main.go | 20 ++++---- encoding.go | 15 ++++++ header.go | 27 +++++++++++ mailmsg.go | 88 ++++++++++++++++++++++++++++++++++ 6 files changed, 303 insertions(+), 10 deletions(-) create mode 100644 .idea/workspace.xml create mode 100644 encoding.go create mode 100644 header.go create mode 100644 mailmsg.go diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 0000000..b9a2cf3 --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + + \ No newline at end of file diff --git a/client_test.go b/client_test.go index 5ec156b..a98d178 100644 --- a/client_test.go +++ b/client_test.go @@ -101,3 +101,54 @@ func TestWithSSL(t *testing.T) { }) } } + +// TestWithTLSPolicy tests the WithTLSPolicy() option for the NewClient() method +func TestWithTLSPolicy(t *testing.T) { + tests := []struct { + name string + value TLSPolicy + want TLSPolicy + }{ + {"Policy: TLSMandatory", TLSMandatory, TLSMandatory}, + {"Policy: TLSOpportunistic", TLSOpportunistic, TLSOpportunistic}, + {"Policy: NoTLS", NoTLS, NoTLS}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c, err := NewClient(DefaultHost, WithTLSPolicy(tt.value)) + if err != nil { + t.Errorf("failed to create new client: %s", err) + return + } + if c.tlspolicy != tt.want { + t.Errorf("failed to set TLSPolicy. Want: %s, got: %s", tt.want, c.tlspolicy) + } + }) + } +} + +// TestSetTLSPolicy tests the SetTLSPolicy() method for the Client object +func TestSetTLSPolicy(t *testing.T) { + tests := []struct { + name string + value TLSPolicy + want TLSPolicy + }{ + {"Policy: TLSMandatory", TLSMandatory, TLSMandatory}, + {"Policy: TLSOpportunistic", TLSOpportunistic, TLSOpportunistic}, + {"Policy: NoTLS", NoTLS, NoTLS}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c, err := NewClient(DefaultHost, WithTLSPolicy(NoTLS)) + if err != nil { + t.Errorf("failed to create new client: %s", err) + return + } + c.SetTLSPolicy(tt.value) + if c.tlspolicy != tt.want { + t.Errorf("failed to set TLSPolicy. Want: %s, got: %s", tt.want, c.tlspolicy) + } + }) + } +} diff --git a/cmd/main.go b/cmd/main.go index bdd3387..c7e4f12 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -2,7 +2,6 @@ package main import ( "context" - "crypto/tls" "fmt" "github.com/wneessen/go-mail" "os" @@ -15,18 +14,12 @@ func main() { fmt.Printf("$TEST_HOST env variable cannot be empty\n") os.Exit(1) } - c, err := mail.NewClient(th, mail.WithTimeout(time.Millisecond*500), mail.WithTLSPolicy(mail.TLSOpportunistic)) + c, err := mail.NewClient(th, mail.WithTimeout(time.Millisecond*500)) if err != nil { fmt.Printf("failed to create new client: %s\n", err) os.Exit(1) } //c.SetTLSPolicy(mail.TLSMandatory) - tc := &tls.Config{ - ServerName: th, - MinVersion: tls.VersionTLS10, - MaxVersion: tls.VersionTLS10, - } - c.SetTLSConfig(tc) ctx, cfn := context.WithCancel(context.Background()) defer cfn() @@ -35,6 +28,13 @@ func main() { fmt.Printf("failed to dial: %s\n", err) os.Exit(1) } - fmt.Printf("Client: %+v\n", c) - fmt.Printf("StartTLS policy: %s\n", c.TLSPolicy()) + + m := mail.NewMsg() + m.From("wn@neessen.net") + m.To("test@test.de", "foo@bar.de", "blubb@blah.com") + m.Cc("cc@test.de", "cc@bar.de", "cc@blah.com") + m.SetMessageID() + m.SetBulk() + m.Header() + } diff --git a/encoding.go b/encoding.go new file mode 100644 index 0000000..c82d37e --- /dev/null +++ b/encoding.go @@ -0,0 +1,15 @@ +package mail + +// Encoding represents a MIME encoding scheme like quoted-printable or base64. +type Encoding string + +const ( + // EncodingB64 represents the Base64 encoding as specified in RFC 2045. + EncodingB64 Encoding = "base64" + + // EncodingQP represents the "quoted-printable" encoding as specified in RFC 2045. + EncodingQP Encoding = "quoted-printable" + + // NoEncoding avoids any character encoding (except of the mail headers) + NoEncoding Encoding = "8bit" +) diff --git a/header.go b/header.go new file mode 100644 index 0000000..b84651b --- /dev/null +++ b/header.go @@ -0,0 +1,27 @@ +package mail + +// Header represents a mail header field name +type Header string + +// List of common header field names +const ( + // HeaderFrom is the "From" header field + HeaderFrom Header = "From" + + // HeaderTo is the "Receipient" header field + HeaderTo Header = "To" + + // HeaderCc is the "Carbon Copy" header field + HeaderCc Header = "Cc" + + // HeaderPrecedence is the "Precedence" header field + HeaderPrecedence Header = "Precedence" + + // HeaderDate represents the "Date" field + // See: https://www.rfc-editor.org/rfc/rfc822#section-5.1 + HeaderDate Header = "Date" + + // HeaderMessageID represents the "Message-ID" field for message identification + // See: https://www.rfc-editor.org/rfc/rfc1036#section-2.1.5 + HeaderMessageID Header = "Message-ID" +) diff --git a/mailmsg.go b/mailmsg.go new file mode 100644 index 0000000..20d2f1b --- /dev/null +++ b/mailmsg.go @@ -0,0 +1,88 @@ +package mail + +import ( + "fmt" + "math/rand" + "mime" + "os" + "time" +) + +// Msg is the mail message struct +type Msg struct { + // charset represents the charset of the mail (defaults to UTF-8) + charset string + + // encoder represents a mime.WordEncoder from the std lib + encoder mime.WordEncoder + + // header is a slice of strings that the different mail header fields + header map[Header][]string +} + +// NewMsg returns a new Msg pointer +func NewMsg() *Msg { + m := &Msg{ + charset: "UTF-8", + header: make(map[Header][]string), + } + return m +} + +// SetHeader sets a header field of the Msg +func (m *Msg) SetHeader(h Header, v ...string) { + switch h { + case HeaderFrom: + m.header[h] = []string{v[0]} + default: + m.header[h] = v + } +} + +// From sets the From: address of the Msg +func (m *Msg) From(f string) { + m.SetHeader(HeaderFrom, f) +} + +// To sets the To: addresses of the Msg +func (m *Msg) To(t ...string) { + m.SetHeader(HeaderTo, t...) +} + +// Cc sets the Cc: addresses of the Msg +func (m *Msg) Cc(c ...string) { + m.SetHeader(HeaderCc, c...) +} + +// SetMessageID generates a random message id for the mail +func (m *Msg) SetMessageID() { + hn, err := os.Hostname() + if err != nil { + hn = "localhost.localdomain" + } + ct := time.Now().UnixMicro() + r := rand.New(rand.NewSource(ct)) + rn := r.Int() + pid := os.Getpid() + + mid := fmt.Sprintf("%d.%d.%d@%s", pid, rn, ct, hn) + m.SetMessageIDWithValue(mid) +} + +// SetMessageIDWithValue sets the message id for the mail +func (m *Msg) SetMessageIDWithValue(v string) { + m.SetHeader(HeaderMessageID, v) +} + +// SetBulk sets the "Precedense: bulk" header 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") +} + +// Header FixMe +func (m *Msg) Header() { + fmt.Printf("%+v\n", m.header) + +}