Replaced Mailer type with the Sender interface and SMTPDialer type
Fixes #10 Fixes #17 Fixes #32
This commit is contained in:
parent
a7fe250544
commit
01674ee5b6
|
@ -2,7 +2,8 @@ package gomail
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"net/smtp"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -11,6 +12,12 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
now = func() time.Time {
|
||||||
|
return time.Date(2014, 06, 25, 17, 46, 0, 0, time.UTC)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type message struct {
|
type message struct {
|
||||||
from string
|
from string
|
||||||
to []string
|
to []string
|
||||||
|
@ -23,8 +30,8 @@ func TestMessage(t *testing.T) {
|
||||||
msg.SetHeader("To", msg.FormatAddress("to@example.com", "Señor To"), "tobis@example.com")
|
msg.SetHeader("To", msg.FormatAddress("to@example.com", "Señor To"), "tobis@example.com")
|
||||||
msg.SetAddressHeader("Cc", "cc@example.com", "A, B")
|
msg.SetAddressHeader("Cc", "cc@example.com", "A, B")
|
||||||
msg.SetAddressHeader("X-To", "ccbis@example.com", "à, b")
|
msg.SetAddressHeader("X-To", "ccbis@example.com", "à, b")
|
||||||
msg.SetDateHeader("X-Date", stubNow())
|
msg.SetDateHeader("X-Date", now())
|
||||||
msg.SetHeader("X-Date-2", msg.FormatDate(stubNow()))
|
msg.SetHeader("X-Date-2", msg.FormatDate(now()))
|
||||||
msg.SetHeader("Subject", "¡Hola, señor!")
|
msg.SetHeader("Subject", "¡Hola, señor!")
|
||||||
msg.SetHeaders(map[string][]string{
|
msg.SetHeaders(map[string][]string{
|
||||||
"X-Headers": {"Test", "Café"},
|
"X-Headers": {"Test", "Café"},
|
||||||
|
@ -488,31 +495,20 @@ func TestBase64LineLength(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testMessage(t *testing.T, msg *Message, bCount int, emails ...message) {
|
func testMessage(t *testing.T, msg *Message, bCount int, emails ...message) {
|
||||||
now = stubNow
|
err := Send(stubSendMail(t, bCount, emails...), msg)
|
||||||
mailer := NewMailer("host", "username", "password", 587, SetSendMail(stubSendMail(t, bCount, emails...)))
|
|
||||||
|
|
||||||
err := mailer.Send(msg)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func stubNow() time.Time {
|
func stubSendMail(t *testing.T, bCount int, emails ...message) SendFunc {
|
||||||
return time.Date(2014, 06, 25, 17, 46, 0, 0, time.UTC)
|
|
||||||
}
|
|
||||||
|
|
||||||
func stubSendMail(t *testing.T, bCount int, emails ...message) SendMailFunc {
|
|
||||||
i := 0
|
i := 0
|
||||||
return func(addr string, a smtp.Auth, from string, to []string, msg []byte) error {
|
return func(from string, to []string, msg io.Reader) error {
|
||||||
if i > len(emails) {
|
if i > len(emails) {
|
||||||
t.Fatalf("Only %d mails should be sent", len(emails))
|
t.Fatalf("Only %d mails should be sent", len(emails))
|
||||||
}
|
}
|
||||||
want := emails[i]
|
want := emails[i]
|
||||||
|
|
||||||
if addr != "host:587" {
|
|
||||||
t.Fatalf("Invalid address, got %q, want host:587", addr)
|
|
||||||
}
|
|
||||||
|
|
||||||
if from != want.from {
|
if from != want.from {
|
||||||
t.Fatalf("Invalid from, got %q, want %q", from, want.from)
|
t.Fatalf("Invalid from, got %q, want %q", from, want.from)
|
||||||
}
|
}
|
||||||
|
@ -531,7 +527,11 @@ func stubSendMail(t *testing.T, bCount int, emails ...message) SendMailFunc {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
got := string(msg)
|
content, err := ioutil.ReadAll(msg)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
got := string(content)
|
||||||
wantMsg := string("Mime-Version: 1.0\r\n" +
|
wantMsg := string("Mime-Version: 1.0\r\n" +
|
||||||
"Date: Wed, 25 Jun 2014 17:46:00 +0000\r\n" +
|
"Date: Wed, 25 Jun 2014 17:46:00 +0000\r\n" +
|
||||||
want.content)
|
want.content)
|
||||||
|
@ -613,7 +613,7 @@ func getBoundaries(t *testing.T, count int, msg string) []string {
|
||||||
var boundaryRegExp = regexp.MustCompile("boundary=(\\w+)")
|
var boundaryRegExp = regexp.MustCompile("boundary=(\\w+)")
|
||||||
|
|
||||||
func BenchmarkFull(b *testing.B) {
|
func BenchmarkFull(b *testing.B) {
|
||||||
emptyFunc := func(addr string, a smtp.Auth, from string, to []string, msg []byte) error {
|
emptyFunc := func(from string, to []string, msg io.Reader) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -631,8 +631,7 @@ func BenchmarkFull(b *testing.B) {
|
||||||
msg.Attach(CreateFile("benchmark.txt", []byte("Benchmark")))
|
msg.Attach(CreateFile("benchmark.txt", []byte("Benchmark")))
|
||||||
msg.Embed(CreateFile("benchmark.jpg", []byte("Benchmark")))
|
msg.Embed(CreateFile("benchmark.jpg", []byte("Benchmark")))
|
||||||
|
|
||||||
mailer := NewMailer("host", "username", "password", 587, SetSendMail(emptyFunc))
|
if err := Send(SendFunc(emptyFunc), msg); err != nil {
|
||||||
if err := mailer.Send(msg); err != nil {
|
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
205
mailer.go
205
mailer.go
|
@ -1,205 +0,0 @@
|
||||||
package gomail
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/tls"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"net"
|
|
||||||
"net/mail"
|
|
||||||
"net/smtp"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// A Mailer represents an SMTP server.
|
|
||||||
type Mailer struct {
|
|
||||||
addr string
|
|
||||||
host string
|
|
||||||
config *tls.Config
|
|
||||||
auth smtp.Auth
|
|
||||||
send SendMailFunc
|
|
||||||
}
|
|
||||||
|
|
||||||
// A MailerSetting can be used in a mailer constructor to configure it.
|
|
||||||
type MailerSetting func(m *Mailer)
|
|
||||||
|
|
||||||
// SetSendMail allows to set the email-sending function of a mailer.
|
|
||||||
//
|
|
||||||
// Example:
|
|
||||||
//
|
|
||||||
// myFunc := func(addr string, a smtp.Auth, from string, to []string, msg []byte) error {
|
|
||||||
// // Implement your email-sending function similar to smtp.SendMail
|
|
||||||
// }
|
|
||||||
// mailer := gomail.NewMailer("host", "user", "pwd", 465, SetSendMail(myFunc))
|
|
||||||
func SetSendMail(s SendMailFunc) MailerSetting {
|
|
||||||
return func(m *Mailer) {
|
|
||||||
m.send = s
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetTLSConfig allows to set the TLS configuration used to connect the SMTP
|
|
||||||
// server.
|
|
||||||
func SetTLSConfig(c *tls.Config) MailerSetting {
|
|
||||||
return func(m *Mailer) {
|
|
||||||
m.config = c
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// A SendMailFunc is a function to send emails with the same signature than
|
|
||||||
// smtp.SendMail.
|
|
||||||
type SendMailFunc func(addr string, a smtp.Auth, from string, to []string, msg []byte) error
|
|
||||||
|
|
||||||
// NewMailer returns a mailer. The given parameters are used to connect to the
|
|
||||||
// SMTP server via a PLAIN authentication mechanism.
|
|
||||||
func NewMailer(host string, username string, password string, port int, settings ...MailerSetting) *Mailer {
|
|
||||||
return NewCustomMailer(
|
|
||||||
fmt.Sprintf("%s:%d", host, port),
|
|
||||||
smtp.PlainAuth("", username, password, host),
|
|
||||||
settings...,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewCustomMailer creates a mailer with the given authentication mechanism.
|
|
||||||
//
|
|
||||||
// Example:
|
|
||||||
//
|
|
||||||
// gomail.NewCustomMailer("host:587", smtp.CRAMMD5Auth("username", "secret"))
|
|
||||||
func NewCustomMailer(addr string, auth smtp.Auth, settings ...MailerSetting) *Mailer {
|
|
||||||
// Error is not handled here to preserve backward compatibility
|
|
||||||
host, port, _ := net.SplitHostPort(addr)
|
|
||||||
|
|
||||||
m := &Mailer{
|
|
||||||
addr: addr,
|
|
||||||
host: host,
|
|
||||||
auth: auth,
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, s := range settings {
|
|
||||||
s(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
if m.config == nil {
|
|
||||||
m.config = &tls.Config{ServerName: host}
|
|
||||||
}
|
|
||||||
if m.send == nil {
|
|
||||||
m.send = m.getSendMailFunc(port == "465")
|
|
||||||
}
|
|
||||||
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send sends the emails to all the recipients of the message.
|
|
||||||
func (m *Mailer) Send(msg *Message) error {
|
|
||||||
message := msg.Export()
|
|
||||||
|
|
||||||
from, err := getFrom(message)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
recipients, bcc, err := getRecipients(message)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
h := flattenHeader(message, "")
|
|
||||||
body, err := ioutil.ReadAll(message.Body)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
mail := append(h, body...)
|
|
||||||
if err := m.send(m.addr, m.auth, from, recipients, mail); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, to := range bcc {
|
|
||||||
h = flattenHeader(message, to)
|
|
||||||
mail = append(h, body...)
|
|
||||||
if err := m.send(m.addr, m.auth, from, []string{to}, mail); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func flattenHeader(msg *mail.Message, bcc string) []byte {
|
|
||||||
buf := getBuffer()
|
|
||||||
defer putBuffer(buf)
|
|
||||||
|
|
||||||
for field, value := range msg.Header {
|
|
||||||
if field != "Bcc" {
|
|
||||||
buf.WriteString(field)
|
|
||||||
buf.WriteString(": ")
|
|
||||||
buf.WriteString(strings.Join(value, ", "))
|
|
||||||
buf.WriteString("\r\n")
|
|
||||||
} else if bcc != "" {
|
|
||||||
for _, to := range value {
|
|
||||||
if strings.Contains(to, bcc) {
|
|
||||||
buf.WriteString(field)
|
|
||||||
buf.WriteString(": ")
|
|
||||||
buf.WriteString(to)
|
|
||||||
buf.WriteString("\r\n")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
buf.WriteString("\r\n")
|
|
||||||
|
|
||||||
return buf.Bytes()
|
|
||||||
}
|
|
||||||
|
|
||||||
func getFrom(msg *mail.Message) (string, error) {
|
|
||||||
from := msg.Header.Get("Sender")
|
|
||||||
if from == "" {
|
|
||||||
from = msg.Header.Get("From")
|
|
||||||
if from == "" {
|
|
||||||
return "", errors.New("mailer: invalid message, \"From\" field is absent")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return parseAddress(from)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getRecipients(msg *mail.Message) (recipients, bcc []string, err error) {
|
|
||||||
for _, field := range []string{"Bcc", "To", "Cc"} {
|
|
||||||
if addresses, ok := msg.Header[field]; ok {
|
|
||||||
for _, addr := range addresses {
|
|
||||||
switch field {
|
|
||||||
case "Bcc":
|
|
||||||
bcc, err = addAdress(bcc, addr)
|
|
||||||
default:
|
|
||||||
recipients, err = addAdress(recipients, addr)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return recipients, bcc, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return recipients, bcc, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func addAdress(list []string, addr string) ([]string, error) {
|
|
||||||
addr, err := parseAddress(addr)
|
|
||||||
if err != nil {
|
|
||||||
return list, err
|
|
||||||
}
|
|
||||||
for _, a := range list {
|
|
||||||
if addr == a {
|
|
||||||
return list, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return append(list, addr), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseAddress(field string) (string, error) {
|
|
||||||
a, err := mail.ParseAddress(field)
|
|
||||||
if a == nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return a.Address, err
|
|
||||||
}
|
|
243
send.go
243
send.go
|
@ -1,102 +1,161 @@
|
||||||
package gomail
|
package gomail
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"io/ioutil"
|
||||||
"net/smtp"
|
"net/mail"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (m *Mailer) getSendMailFunc(ssl bool) SendMailFunc {
|
// Sender is the interface that wraps the Send method.
|
||||||
return func(addr string, a smtp.Auth, from string, to []string, msg []byte) error {
|
//
|
||||||
var c smtpClient
|
// Send sends an email to the given addresses.
|
||||||
var err error
|
type Sender interface {
|
||||||
if ssl {
|
Send(from string, to []string, msg io.Reader) error
|
||||||
c, err = sslDial(addr, m.host, m.config)
|
|
||||||
} else {
|
|
||||||
c, err = starttlsDial(addr, m.config)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer c.Close()
|
|
||||||
|
|
||||||
if a != nil {
|
|
||||||
if ok, _ := c.Extension("AUTH"); ok {
|
|
||||||
if err = c.Auth(a); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = c.Mail(from); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, addr := range to {
|
|
||||||
if err = c.Rcpt(addr); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
w, err := c.Data()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = w.Write(msg)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = w.Close()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.Quit()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func sslDial(addr, host string, config *tls.Config) (smtpClient, error) {
|
// SendCloser is the interface that groups the Send and Close methods.
|
||||||
conn, err := initTLS("tcp", addr, config)
|
type SendCloser interface {
|
||||||
if err != nil {
|
Sender
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return newClient(conn, host)
|
|
||||||
}
|
|
||||||
|
|
||||||
func starttlsDial(addr string, config *tls.Config) (smtpClient, error) {
|
|
||||||
c, err := initSMTP(addr)
|
|
||||||
if err != nil {
|
|
||||||
return c, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if ok, _ := c.Extension("STARTTLS"); ok {
|
|
||||||
return c, c.StartTLS(config)
|
|
||||||
}
|
|
||||||
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var initSMTP = func(addr string) (smtpClient, error) {
|
|
||||||
return smtp.Dial(addr)
|
|
||||||
}
|
|
||||||
|
|
||||||
var initTLS = func(network, addr string, config *tls.Config) (*tls.Conn, error) {
|
|
||||||
return tls.Dial(network, addr, config)
|
|
||||||
}
|
|
||||||
|
|
||||||
var newClient = func(conn net.Conn, host string) (smtpClient, error) {
|
|
||||||
return smtp.NewClient(conn, host)
|
|
||||||
}
|
|
||||||
|
|
||||||
type smtpClient interface {
|
|
||||||
Extension(string) (bool, string)
|
|
||||||
StartTLS(*tls.Config) error
|
|
||||||
Auth(smtp.Auth) error
|
|
||||||
Mail(string) error
|
|
||||||
Rcpt(string) error
|
|
||||||
Data() (io.WriteCloser, error)
|
|
||||||
Quit() error
|
|
||||||
Close() error
|
Close() error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A SendFunc is a function that sends emails to the given adresses.
|
||||||
|
// The SendFunc type is an adapter to allow the use of ordinary functions as
|
||||||
|
// email senders. If f is a function with the appropriate signature, SendFunc(f)
|
||||||
|
// is a Sender object that calls f.
|
||||||
|
type SendFunc func(from string, to []string, msg io.Reader) error
|
||||||
|
|
||||||
|
// Send calls f(from, to, msg).
|
||||||
|
func (f SendFunc) Send(from string, to []string, msg io.Reader) error {
|
||||||
|
return f(from, to, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send sends emails using the given Sender.
|
||||||
|
func Send(s Sender, msg ...*Message) error {
|
||||||
|
for i, m := range msg {
|
||||||
|
if err := send(s, m); err != nil {
|
||||||
|
return fmt.Errorf("gomail: could not send email %d: %v", i+1, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func send(s Sender, msg *Message) error {
|
||||||
|
message := msg.Export()
|
||||||
|
|
||||||
|
from, err := getFrom(message)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
recipients, bcc, err := getRecipients(message)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
h := flattenHeader(message, "")
|
||||||
|
body, err := ioutil.ReadAll(message.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
mail := bytes.NewReader(append(h, body...))
|
||||||
|
if err := s.Send(from, recipients, mail); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, to := range bcc {
|
||||||
|
h = flattenHeader(message, to)
|
||||||
|
mail = bytes.NewReader(append(h, body...))
|
||||||
|
if err := s.Send(from, []string{to}, mail); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func flattenHeader(msg *mail.Message, bcc string) []byte {
|
||||||
|
buf := getBuffer()
|
||||||
|
defer putBuffer(buf)
|
||||||
|
|
||||||
|
for field, value := range msg.Header {
|
||||||
|
if field != "Bcc" {
|
||||||
|
buf.WriteString(field)
|
||||||
|
buf.WriteString(": ")
|
||||||
|
buf.WriteString(strings.Join(value, ", "))
|
||||||
|
buf.WriteString("\r\n")
|
||||||
|
} else if bcc != "" {
|
||||||
|
for _, to := range value {
|
||||||
|
if strings.Contains(to, bcc) {
|
||||||
|
buf.WriteString(field)
|
||||||
|
buf.WriteString(": ")
|
||||||
|
buf.WriteString(to)
|
||||||
|
buf.WriteString("\r\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf.WriteString("\r\n")
|
||||||
|
|
||||||
|
return buf.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFrom(msg *mail.Message) (string, error) {
|
||||||
|
from := msg.Header.Get("Sender")
|
||||||
|
if from == "" {
|
||||||
|
from = msg.Header.Get("From")
|
||||||
|
if from == "" {
|
||||||
|
return "", errors.New("mailer: invalid message, \"From\" field is absent")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseAddress(from)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getRecipients(msg *mail.Message) (recipients, bcc []string, err error) {
|
||||||
|
for _, field := range []string{"Bcc", "To", "Cc"} {
|
||||||
|
if addresses, ok := msg.Header[field]; ok {
|
||||||
|
for _, addr := range addresses {
|
||||||
|
switch field {
|
||||||
|
case "Bcc":
|
||||||
|
bcc, err = addAdress(bcc, addr)
|
||||||
|
default:
|
||||||
|
recipients, err = addAdress(recipients, addr)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return recipients, bcc, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return recipients, bcc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func addAdress(list []string, addr string) ([]string, error) {
|
||||||
|
addr, err := parseAddress(addr)
|
||||||
|
if err != nil {
|
||||||
|
return list, err
|
||||||
|
}
|
||||||
|
for _, a := range list {
|
||||||
|
if addr == a {
|
||||||
|
return list, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return append(list, addr), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseAddress(field string) (string, error) {
|
||||||
|
a, err := mail.ParseAddress(field)
|
||||||
|
if a == nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return a.Address, err
|
||||||
|
}
|
||||||
|
|
270
send_test.go
270
send_test.go
|
@ -1,245 +1,79 @@
|
||||||
package gomail
|
package gomail
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"io/ioutil"
|
||||||
"net/smtp"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
const (
|
||||||
testAddr = "smtp.example.com:587"
|
testTo1 = "to1@example.com"
|
||||||
testSSLAddr = "smtp.example.com:465"
|
testTo2 = "to2@example.com"
|
||||||
testTLSConn = &tls.Conn{}
|
|
||||||
testConfig = &tls.Config{InsecureSkipVerify: true}
|
|
||||||
testHost = "smtp.example.com"
|
|
||||||
testAuth = smtp.PlainAuth("", "user", "pwd", "smtp.example.com")
|
|
||||||
testFrom = "from@example.com"
|
testFrom = "from@example.com"
|
||||||
testTo = []string{"to1@example.com", "to2@example.com"}
|
|
||||||
testBody = "Test message"
|
testBody = "Test message"
|
||||||
)
|
testMsg = "To: " + testTo1 + ", " + testTo2 + "\r\n" +
|
||||||
|
"From: " + testFrom + "\r\n" +
|
||||||
const wantMsg = "To: to1@example.com, to2@example.com\r\n" +
|
|
||||||
"From: from@example.com\r\n" +
|
|
||||||
"Mime-Version: 1.0\r\n" +
|
"Mime-Version: 1.0\r\n" +
|
||||||
"Date: Wed, 25 Jun 2014 17:46:00 +0000\r\n" +
|
"Date: Wed, 25 Jun 2014 17:46:00 +0000\r\n" +
|
||||||
"Content-Type: text/plain; charset=UTF-8\r\n" +
|
"Content-Type: text/plain; charset=UTF-8\r\n" +
|
||||||
"Content-Transfer-Encoding: quoted-printable\r\n" +
|
"Content-Transfer-Encoding: quoted-printable\r\n" +
|
||||||
"\r\n" +
|
"\r\n" +
|
||||||
"Test message"
|
testBody
|
||||||
|
)
|
||||||
|
|
||||||
func TestDefaultSendMail(t *testing.T) {
|
type mockSender SendFunc
|
||||||
testSendMail(t, testAddr, nil, []string{
|
|
||||||
"Extension STARTTLS",
|
func (s mockSender) Send(from string, to []string, msg io.Reader) error {
|
||||||
"StartTLS",
|
return s(from, to, msg)
|
||||||
"Extension AUTH",
|
|
||||||
"Auth",
|
|
||||||
"Mail " + testFrom,
|
|
||||||
"Rcpt " + testTo[0],
|
|
||||||
"Rcpt " + testTo[1],
|
|
||||||
"Data",
|
|
||||||
"Write message",
|
|
||||||
"Close writer",
|
|
||||||
"Quit",
|
|
||||||
"Close",
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSSLSendMail(t *testing.T) {
|
type mockSendCloser struct {
|
||||||
testSendMail(t, testSSLAddr, nil, []string{
|
mockSender
|
||||||
"Extension AUTH",
|
close func() error
|
||||||
"Auth",
|
|
||||||
"Mail " + testFrom,
|
|
||||||
"Rcpt " + testTo[0],
|
|
||||||
"Rcpt " + testTo[1],
|
|
||||||
"Data",
|
|
||||||
"Write message",
|
|
||||||
"Close writer",
|
|
||||||
"Quit",
|
|
||||||
"Close",
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTLSConfigSendMail(t *testing.T) {
|
func (s *mockSendCloser) Close() error {
|
||||||
testSendMail(t, testAddr, testConfig, []string{
|
return s.close()
|
||||||
"Extension STARTTLS",
|
|
||||||
"StartTLS",
|
|
||||||
"Extension AUTH",
|
|
||||||
"Auth",
|
|
||||||
"Mail " + testFrom,
|
|
||||||
"Rcpt " + testTo[0],
|
|
||||||
"Rcpt " + testTo[1],
|
|
||||||
"Data",
|
|
||||||
"Write message",
|
|
||||||
"Close writer",
|
|
||||||
"Quit",
|
|
||||||
"Close",
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTLSConfigSSLSendMail(t *testing.T) {
|
func TestSend(t *testing.T) {
|
||||||
testSendMail(t, testSSLAddr, testConfig, []string{
|
s := &mockSendCloser{
|
||||||
"Extension AUTH",
|
mockSender: stubSend(t, testFrom, []string{testTo1, testTo2}, testMsg),
|
||||||
"Auth",
|
close: func() error {
|
||||||
"Mail " + testFrom,
|
t.Error("Close() should not be called in Send()")
|
||||||
"Rcpt " + testTo[0],
|
|
||||||
"Rcpt " + testTo[1],
|
|
||||||
"Data",
|
|
||||||
"Write message",
|
|
||||||
"Close writer",
|
|
||||||
"Quit",
|
|
||||||
"Close",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
type mockClient struct {
|
|
||||||
t *testing.T
|
|
||||||
i int
|
|
||||||
want []string
|
|
||||||
addr string
|
|
||||||
auth smtp.Auth
|
|
||||||
config *tls.Config
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *mockClient) Extension(ext string) (bool, string) {
|
|
||||||
c.do("Extension " + ext)
|
|
||||||
return true, ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *mockClient) StartTLS(config *tls.Config) error {
|
|
||||||
assertConfig(c.t, config, c.config)
|
|
||||||
c.do("StartTLS")
|
|
||||||
return nil
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if err := Send(s, getTestMessage()); err != nil {
|
||||||
|
t.Errorf("Send(): %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *mockClient) Auth(a smtp.Auth) error {
|
func getTestMessage() *Message {
|
||||||
assertAuth(c.t, a, c.auth)
|
m := NewMessage()
|
||||||
c.do("Auth")
|
m.SetHeader("From", testFrom)
|
||||||
|
m.SetHeader("To", testTo1, testTo2)
|
||||||
|
m.SetBody("text/plain", testBody)
|
||||||
|
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func stubSend(t *testing.T, wantFrom string, wantTo []string, wantBody string) mockSender {
|
||||||
|
return func(from string, to []string, msg io.Reader) error {
|
||||||
|
if from != wantFrom {
|
||||||
|
t.Errorf("invalid from, got %q, want %q", from, wantFrom)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(to, wantTo) {
|
||||||
|
t.Errorf("invalid to, got %v, want %v", to, wantTo)
|
||||||
|
}
|
||||||
|
|
||||||
|
content, err := ioutil.ReadAll(msg)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
compareBodies(t, string(content), wantBody)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
|
||||||
|
|
||||||
func (c *mockClient) Mail(from string) error {
|
|
||||||
c.do("Mail " + from)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *mockClient) Rcpt(to string) error {
|
|
||||||
c.do("Rcpt " + to)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *mockClient) Data() (io.WriteCloser, error) {
|
|
||||||
c.do("Data")
|
|
||||||
return &mockWriter{c: c, want: wantMsg}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *mockClient) Quit() error {
|
|
||||||
c.do("Quit")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *mockClient) Close() error {
|
|
||||||
c.do("Close")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *mockClient) do(cmd string) {
|
|
||||||
if c.i >= len(c.want) {
|
|
||||||
c.t.Fatalf("Invalid command %q", cmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
if cmd != c.want[c.i] {
|
|
||||||
c.t.Fatalf("Invalid command, got %q, want %q", cmd, c.want[c.i])
|
|
||||||
}
|
|
||||||
c.i++
|
|
||||||
}
|
|
||||||
|
|
||||||
type mockWriter struct {
|
|
||||||
want string
|
|
||||||
c *mockClient
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *mockWriter) Write(p []byte) (int, error) {
|
|
||||||
w.c.do("Write message")
|
|
||||||
compareBodies(w.c.t, string(p), w.want)
|
|
||||||
return len(p), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *mockWriter) Close() error {
|
|
||||||
w.c.do("Close writer")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func testSendMail(t *testing.T, addr string, config *tls.Config, want []string) {
|
|
||||||
testClient := &mockClient{
|
|
||||||
t: t,
|
|
||||||
want: want,
|
|
||||||
addr: addr,
|
|
||||||
auth: testAuth,
|
|
||||||
config: config,
|
|
||||||
}
|
|
||||||
|
|
||||||
initSMTP = func(addr string) (smtpClient, error) {
|
|
||||||
assertAddr(t, addr, testClient.addr)
|
|
||||||
return testClient, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
initTLS = func(network, addr string, config *tls.Config) (*tls.Conn, error) {
|
|
||||||
if network != "tcp" {
|
|
||||||
t.Errorf("Invalid network, got %q, want tcp", network)
|
|
||||||
}
|
|
||||||
assertAddr(t, addr, testClient.addr)
|
|
||||||
assertConfig(t, config, testClient.config)
|
|
||||||
return testTLSConn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
newClient = func(conn net.Conn, host string) (smtpClient, error) {
|
|
||||||
if conn != testTLSConn {
|
|
||||||
t.Error("Invalid TLS connection used")
|
|
||||||
}
|
|
||||||
if host != testHost {
|
|
||||||
t.Errorf("Invalid host, got %q, want %q", host, testHost)
|
|
||||||
}
|
|
||||||
return testClient, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
msg := NewMessage()
|
|
||||||
msg.SetHeader("From", testFrom)
|
|
||||||
msg.SetHeader("To", testTo...)
|
|
||||||
msg.SetBody("text/plain", testBody)
|
|
||||||
|
|
||||||
var settings []MailerSetting
|
|
||||||
if config != nil {
|
|
||||||
settings = []MailerSetting{SetTLSConfig(config)}
|
|
||||||
}
|
|
||||||
|
|
||||||
mailer := NewCustomMailer(addr, testAuth, settings...)
|
|
||||||
if err := mailer.Send(msg); err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func assertAuth(t *testing.T, got, want smtp.Auth) {
|
|
||||||
if got != want {
|
|
||||||
t.Errorf("Invalid auth, got %#v, want %#v", got, want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func assertAddr(t *testing.T, got, want string) {
|
|
||||||
if got != want {
|
|
||||||
t.Errorf("Invalid addr, got %q, want %q", got, want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func assertConfig(t *testing.T, got, want *tls.Config) {
|
|
||||||
if want == nil {
|
|
||||||
want = &tls.Config{ServerName: testHost}
|
|
||||||
}
|
|
||||||
if got.ServerName != want.ServerName {
|
|
||||||
t.Errorf("Invalid field ServerName in config, got %q, want %q", got.ServerName, want.ServerName)
|
|
||||||
}
|
|
||||||
if got.InsecureSkipVerify != want.InsecureSkipVerify {
|
|
||||||
t.Errorf("Invalid field InsecureSkipVerify in config, got %v, want %v", got.InsecureSkipVerify, want.InsecureSkipVerify)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,168 @@
|
||||||
|
package gomail
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/smtp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// An SMTPDialer is a dialer to an SMTP server.
|
||||||
|
type SMTPDialer struct {
|
||||||
|
// Host represents the host of the SMTP server.
|
||||||
|
Host string
|
||||||
|
// Port represents the port of the SMTP server.
|
||||||
|
Port int
|
||||||
|
// Auth represents the authentication mechanism used to authenticate to the
|
||||||
|
// SMTP server.
|
||||||
|
Auth smtp.Auth
|
||||||
|
// SSL defines whether an SSL connection is used. It should be false in
|
||||||
|
// most cases since the authentication mechanism should use the STARTTLS
|
||||||
|
// extension instead.
|
||||||
|
SSL bool
|
||||||
|
// TSLConfig represents the TLS configuration used for the TLS (when the
|
||||||
|
// STARTTLS extension is used) or SSL connection.
|
||||||
|
TLSConfig *tls.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPlainDialer returns an SMTPDialer. The given parameters are used to
|
||||||
|
// connect to the SMTP server via a PLAIN authentication mechanism.
|
||||||
|
func NewPlainDialer(host, username, password string, port int) *SMTPDialer {
|
||||||
|
return &SMTPDialer{
|
||||||
|
Host: host,
|
||||||
|
Port: port,
|
||||||
|
Auth: smtp.PlainAuth("", username, password, host),
|
||||||
|
SSL: port == 465,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dial dials and authenticates to an SMTP server. The returned SendCloser
|
||||||
|
// should be closed when done using it.
|
||||||
|
func (d *SMTPDialer) Dial() (SendCloser, error) {
|
||||||
|
c, err := d.dial()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.Auth != nil {
|
||||||
|
if ok, _ := c.Extension("AUTH"); ok {
|
||||||
|
if err = c.Auth(d.Auth); err != nil {
|
||||||
|
c.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &smtpSender{c}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *SMTPDialer) dial() (smtpClient, error) {
|
||||||
|
if d.SSL {
|
||||||
|
return d.sslDial()
|
||||||
|
}
|
||||||
|
return d.starttlsDial()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *SMTPDialer) starttlsDial() (smtpClient, error) {
|
||||||
|
c, err := smtpDial(addr(d.Host, d.Port))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if ok, _ := c.Extension("STARTTLS"); ok {
|
||||||
|
if err := c.StartTLS(d.tlsConfig()); err != nil {
|
||||||
|
c.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *SMTPDialer) sslDial() (smtpClient, error) {
|
||||||
|
conn, err := tlsDial("tcp", addr(d.Host, d.Port), d.tlsConfig())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return newClient(conn, d.Host)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *SMTPDialer) tlsConfig() *tls.Config {
|
||||||
|
if d.TLSConfig == nil {
|
||||||
|
return &tls.Config{ServerName: d.Host}
|
||||||
|
}
|
||||||
|
|
||||||
|
return d.TLSConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func addr(host string, port int) string {
|
||||||
|
return fmt.Sprintf("%s:%d", host, port)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DialAndSend opens a connection to an SMTP server, sends the given emails and
|
||||||
|
// closes the connection.
|
||||||
|
func (d *SMTPDialer) DialAndSend(msg ...*Message) error {
|
||||||
|
s, err := d.Dial()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
return Send(s, msg...)
|
||||||
|
}
|
||||||
|
|
||||||
|
type smtpSender struct {
|
||||||
|
smtpClient
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *smtpSender) Send(from string, to []string, msg io.Reader) error {
|
||||||
|
if err := c.Mail(from); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, addr := range to {
|
||||||
|
if err := c.Rcpt(addr); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
w, err := c.Data()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = io.Copy(w, msg); err != nil {
|
||||||
|
w.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return w.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *smtpSender) Close() error {
|
||||||
|
return c.Quit()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stubbed out for tests.
|
||||||
|
var (
|
||||||
|
smtpDial = func(addr string) (smtpClient, error) {
|
||||||
|
return smtp.Dial(addr)
|
||||||
|
}
|
||||||
|
tlsDial = tls.Dial
|
||||||
|
newClient = func(conn net.Conn, host string) (smtpClient, error) {
|
||||||
|
return smtp.NewClient(conn, host)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
type smtpClient interface {
|
||||||
|
Extension(string) (bool, string)
|
||||||
|
StartTLS(*tls.Config) error
|
||||||
|
Auth(smtp.Auth) error
|
||||||
|
Mail(string) error
|
||||||
|
Rcpt(string) error
|
||||||
|
Data() (io.WriteCloser, error)
|
||||||
|
Quit() error
|
||||||
|
Close() error
|
||||||
|
}
|
|
@ -0,0 +1,248 @@
|
||||||
|
package gomail
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/smtp"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
testHost = "smtp.example.com"
|
||||||
|
testPort = 587
|
||||||
|
testSSLPort = 465
|
||||||
|
testTLSConn = &tls.Conn{}
|
||||||
|
testConfig = &tls.Config{InsecureSkipVerify: true}
|
||||||
|
testAuth = smtp.PlainAuth("", "user", "pwd", testHost)
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSMTPDialer(t *testing.T) {
|
||||||
|
d := NewPlainDialer(testHost, "user", "pwd", testPort)
|
||||||
|
testSendMail(t, d, []string{
|
||||||
|
"Extension STARTTLS",
|
||||||
|
"StartTLS",
|
||||||
|
"Extension AUTH",
|
||||||
|
"Auth",
|
||||||
|
"Mail " + testFrom,
|
||||||
|
"Rcpt " + testTo1,
|
||||||
|
"Rcpt " + testTo2,
|
||||||
|
"Data",
|
||||||
|
"Write message",
|
||||||
|
"Close writer",
|
||||||
|
"Quit",
|
||||||
|
"Close",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSMTPDialerSSL(t *testing.T) {
|
||||||
|
d := NewPlainDialer(testHost, "user", "pwd", testSSLPort)
|
||||||
|
testSendMail(t, d, []string{
|
||||||
|
"Extension AUTH",
|
||||||
|
"Auth",
|
||||||
|
"Mail " + testFrom,
|
||||||
|
"Rcpt " + testTo1,
|
||||||
|
"Rcpt " + testTo2,
|
||||||
|
"Data",
|
||||||
|
"Write message",
|
||||||
|
"Close writer",
|
||||||
|
"Quit",
|
||||||
|
"Close",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSMTPDialerConfig(t *testing.T) {
|
||||||
|
d := NewPlainDialer(testHost, "user", "pwd", testPort)
|
||||||
|
d.TLSConfig = testConfig
|
||||||
|
testSendMail(t, d, []string{
|
||||||
|
"Extension STARTTLS",
|
||||||
|
"StartTLS",
|
||||||
|
"Extension AUTH",
|
||||||
|
"Auth",
|
||||||
|
"Mail " + testFrom,
|
||||||
|
"Rcpt " + testTo1,
|
||||||
|
"Rcpt " + testTo2,
|
||||||
|
"Data",
|
||||||
|
"Write message",
|
||||||
|
"Close writer",
|
||||||
|
"Quit",
|
||||||
|
"Close",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSMTPDialerSSLConfig(t *testing.T) {
|
||||||
|
d := NewPlainDialer(testHost, "user", "pwd", testSSLPort)
|
||||||
|
d.TLSConfig = testConfig
|
||||||
|
testSendMail(t, d, []string{
|
||||||
|
"Extension AUTH",
|
||||||
|
"Auth",
|
||||||
|
"Mail " + testFrom,
|
||||||
|
"Rcpt " + testTo1,
|
||||||
|
"Rcpt " + testTo2,
|
||||||
|
"Data",
|
||||||
|
"Write message",
|
||||||
|
"Close writer",
|
||||||
|
"Quit",
|
||||||
|
"Close",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSMTPDialerNoAuth(t *testing.T) {
|
||||||
|
d := &SMTPDialer{
|
||||||
|
Host: testHost,
|
||||||
|
Port: testPort,
|
||||||
|
}
|
||||||
|
testSendMail(t, d, []string{
|
||||||
|
"Extension STARTTLS",
|
||||||
|
"StartTLS",
|
||||||
|
"Mail " + testFrom,
|
||||||
|
"Rcpt " + testTo1,
|
||||||
|
"Rcpt " + testTo2,
|
||||||
|
"Data",
|
||||||
|
"Write message",
|
||||||
|
"Close writer",
|
||||||
|
"Quit",
|
||||||
|
"Close",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockClient struct {
|
||||||
|
t *testing.T
|
||||||
|
i int
|
||||||
|
want []string
|
||||||
|
addr string
|
||||||
|
auth smtp.Auth
|
||||||
|
config *tls.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *mockClient) Extension(ext string) (bool, string) {
|
||||||
|
c.do("Extension " + ext)
|
||||||
|
return true, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *mockClient) StartTLS(config *tls.Config) error {
|
||||||
|
assertConfig(c.t, config, c.config)
|
||||||
|
c.do("StartTLS")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *mockClient) Auth(a smtp.Auth) error {
|
||||||
|
assertAuth(c.t, a, c.auth)
|
||||||
|
c.do("Auth")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *mockClient) Mail(from string) error {
|
||||||
|
c.do("Mail " + from)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *mockClient) Rcpt(to string) error {
|
||||||
|
c.do("Rcpt " + to)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *mockClient) Data() (io.WriteCloser, error) {
|
||||||
|
c.do("Data")
|
||||||
|
return &mockWriter{c: c, want: testMsg}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *mockClient) Quit() error {
|
||||||
|
c.do("Quit")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *mockClient) Close() error {
|
||||||
|
c.do("Close")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *mockClient) do(cmd string) {
|
||||||
|
if c.i >= len(c.want) {
|
||||||
|
c.t.Fatalf("Invalid command %q", cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cmd != c.want[c.i] {
|
||||||
|
c.t.Fatalf("Invalid command, got %q, want %q", cmd, c.want[c.i])
|
||||||
|
}
|
||||||
|
c.i++
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockWriter struct {
|
||||||
|
want string
|
||||||
|
c *mockClient
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *mockWriter) Write(p []byte) (int, error) {
|
||||||
|
w.c.do("Write message")
|
||||||
|
compareBodies(w.c.t, string(p), w.want)
|
||||||
|
return len(p), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *mockWriter) Close() error {
|
||||||
|
w.c.do("Close writer")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSendMail(t *testing.T, d *SMTPDialer, want []string) {
|
||||||
|
testClient := &mockClient{
|
||||||
|
t: t,
|
||||||
|
want: want,
|
||||||
|
addr: addr(d.Host, d.Port),
|
||||||
|
auth: testAuth,
|
||||||
|
config: d.TLSConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
smtpDial = func(addr string) (smtpClient, error) {
|
||||||
|
assertAddr(t, addr, testClient.addr)
|
||||||
|
return testClient, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
tlsDial = func(network, addr string, config *tls.Config) (*tls.Conn, error) {
|
||||||
|
if network != "tcp" {
|
||||||
|
t.Errorf("Invalid network, got %q, want tcp", network)
|
||||||
|
}
|
||||||
|
assertAddr(t, addr, testClient.addr)
|
||||||
|
assertConfig(t, config, testClient.config)
|
||||||
|
return testTLSConn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
newClient = func(conn net.Conn, host string) (smtpClient, error) {
|
||||||
|
if conn != testTLSConn {
|
||||||
|
t.Error("Invalid TLS connection used")
|
||||||
|
}
|
||||||
|
if host != testHost {
|
||||||
|
t.Errorf("Invalid host, got %q, want %q", host, testHost)
|
||||||
|
}
|
||||||
|
return testClient, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := d.DialAndSend(getTestMessage()); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertAuth(t *testing.T, got, want smtp.Auth) {
|
||||||
|
if !reflect.DeepEqual(got, want) {
|
||||||
|
t.Errorf("Invalid auth, got %#v, want %#v", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertAddr(t *testing.T, got, want string) {
|
||||||
|
if got != want {
|
||||||
|
t.Errorf("Invalid addr, got %q, want %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertConfig(t *testing.T, got, want *tls.Config) {
|
||||||
|
if want == nil {
|
||||||
|
want = &tls.Config{ServerName: testHost}
|
||||||
|
}
|
||||||
|
if got.ServerName != want.ServerName {
|
||||||
|
t.Errorf("Invalid field ServerName in config, got %q, want %q", got.ServerName, want.ServerName)
|
||||||
|
}
|
||||||
|
if got.InsecureSkipVerify != want.InsecureSkipVerify {
|
||||||
|
t.Errorf("Invalid field InsecureSkipVerify in config, got %v, want %v", got.InsecureSkipVerify, want.InsecureSkipVerify)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue