diff --git a/CHANGELOG.md b/CHANGELOG.md index ec301b7..333d120 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,9 +4,8 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## [2.0.0] - unreleased - Mailer has been removed. It has been replaced by Dialer and Sender. -- `CreateFile` has been removed and `OpenFile` has been renamed into `NewFile`. -- `File` fields changed. The `File.Header` field replaces `File.MimeType` -and `File.ContentID`. +- `File` type and the `CreateFile` and `OpenFile` functions have been removed. +- `Message.Attach` and `Message.Embed` have a new signature. - `Message.GetBodyWriter` has been removed. Use `Message.AddAlternativeWriter` instead. - `Message.Export` has been removed. `Message.WriteTo` can be used instead. diff --git a/example_test.go b/example_test.go index cb4b8ee..a09ca73 100644 --- a/example_test.go +++ b/example_test.go @@ -15,7 +15,7 @@ func Example() { m.SetAddressHeader("Cc", "dan@example.com", "Dan") m.SetHeader("Subject", "Hello!") m.SetBody("text/html", "Hello Bob and Cora!") - m.Attach(gomail.NewFile("/home/Alex/lolcat.jpg")) + m.Attach("/home/Alex/lolcat.jpg") d := gomail.NewPlainDialer("smtp.example.com", "user", "123456", 587) diff --git a/message.go b/message.go index c05b49f..2a16c56 100644 --- a/message.go +++ b/message.go @@ -12,8 +12,8 @@ import ( type Message struct { header header parts []part - attachments []*File - embedded []*File + attachments []*file + embedded []*file charset string encoding Encoding hEncoder mimeEncoder @@ -106,12 +106,20 @@ const ( // SetHeader sets a value to the given header field. func (m *Message) SetHeader(field string, value ...string) { - for i := range value { - value[i] = m.encodeHeader(value[i]) - } + m.encodeHeader(value) m.header[field] = value } +func (m *Message) encodeHeader(values []string) { + for i := range values { + values[i] = m.encodeString(values[i]) + } +} + +func (m *Message) encodeString(value string) string { + return m.hEncoder.Encode(m.charset, value) +} + // SetHeaders sets the message headers. // // Example: @@ -134,7 +142,7 @@ func (m *Message) SetAddressHeader(field, address, name string) { // FormatAddress formats an address and a name as a valid RFC 5322 address. func (m *Message) FormatAddress(address, name string) string { - enc := m.encodeHeader(name) + enc := m.encodeString(name) if enc == name { m.buf.WriteByte('"') for i := 0; i < len(name); i++ { @@ -170,10 +178,6 @@ func hasSpecials(text string) bool { return false } -func (m *Message) encodeHeader(value string) string { - return m.hEncoder.Encode(m.charset, value) -} - // SetDateHeader sets a date to the given header field. func (m *Message) SetDateHeader(field string, date time.Time) { m.header[field] = []string{m.FormatDate(date)} @@ -254,27 +258,61 @@ func (m *Message) getPartHeader(contentType string) header { } } -// A File represents a file that can be attached or embedded in an email. -type File struct { - // Name represents the base name of the file. If the file is attached to the - // message it is the name of the attachment. - Name string - // Header represents the MIME header of the message part that contains the - // file content. It is empty by default. Mandatory headers are automatically - // added if they are not sent when sending the email. - Header map[string][]string - // Copier is a function run when the message is sent. It should copy the - // content of the file to w. - Copier func(w io.Writer) error +type file struct { + Name string + Header map[string][]string + CopyFunc func(w io.Writer) error } -// NewFile creates a File from the given filename. -func NewFile(filename string) *File { - return &File{ - Name: filepath.Base(filename), +func (f *file) setHeader(field, value string) { + f.Header[field] = []string{value} +} + +// A FileSetting can be used as an argument in Message.Attach or Message.Embed. +type FileSetting func(*file) + +// SetHeader is a file setting to set the MIME header of the message part that +// contains the file content. +// +// Mandatory headers are automatically added if they are not set when sending +// the email. +// +// Example: +// +// h := map[string][]string{"Content-ID": {""}} +// m.Attach("foo.jpg", gomail.SetHeader(h)) +func SetHeader(h map[string][]string) FileSetting { + return func(f *file) { + for k, v := range h { + f.Header[k] = v + } + } +} + +// SetCopyFunc is a file setting to replace the function that runs when the +// message is sent. +// It should copy the content of the file to the io.Writer. +// The default copy function opens the file with the given filename, and copy +// its content to the io.Writer. +// +// Example: +// +// m.Attach("foo.txt", gomail.SetCopyFunc(func(w io.Writer) error { +// _, err := w.Write([]byte("Content of foo.txt")) +// return err +// })) +func SetCopyFunc(f func(io.Writer) error) FileSetting { + return func(fi *file) { + fi.CopyFunc = f + } +} + +func (m *Message) appendFile(list []*file, name string, settings []FileSetting) []*file { + f := &file{ + Name: filepath.Base(name), Header: make(map[string][]string), - Copier: func(w io.Writer) error { - h, err := os.Open(filename) + CopyFunc: func(w io.Writer) error { + h, err := os.Open(name) if err != nil { return err } @@ -285,35 +323,33 @@ func NewFile(filename string) *File { return h.Close() }, } -} -func (f *File) setHeader(field string, value ...string) { - f.Header[field] = value + for _, s := range settings { + s(f) + } + + if list == nil { + return []*file{f} + } + + return append(list, f) } // Attach attaches the files to the email. // // Example: // -// m.Attach(gomail.NewFile("/tmp/image.jpg")) -func (m *Message) Attach(f ...*File) { - if m.attachments == nil { - m.attachments = f - } else { - m.attachments = append(m.attachments, f...) - } +// m.Attach("/tmp/image.jpg") +func (m *Message) Attach(filename string, settings ...FileSetting) { + m.attachments = m.appendFile(m.attachments, filename, settings) } // Embed embeds the images to the email. // // Example: // -// m.Embed(gomail.NewFile("/tmp/image.jpg")) +// m.Embed("/tmp/image.jpg") // m.SetBody("text/html", `My image`) -func (m *Message) Embed(image ...*File) { - if m.embedded == nil { - m.embedded = image - } else { - m.embedded = append(m.embedded, image...) - } +func (m *Message) Embed(filename string, settings ...FileSetting) { + m.embedded = m.appendFile(m.embedded, filename, settings) } diff --git a/message_test.go b/message_test.go index 0553382..fdd9ff9 100644 --- a/message_test.go +++ b/message_test.go @@ -195,7 +195,7 @@ func TestAttachmentOnly(t *testing.T) { m := NewMessage() m.SetHeader("From", "from@example.com") m.SetHeader("To", "to@example.com") - m.Attach(testFile("/tmp/test.pdf")) + m.Attach(mockCopyFile("/tmp/test.pdf")) want := &message{ from: "from@example.com", @@ -217,7 +217,7 @@ func TestAttachment(t *testing.T) { m.SetHeader("From", "from@example.com") m.SetHeader("To", "to@example.com") m.SetBody("text/plain", "Test") - m.Attach(testFile("/tmp/test.pdf")) + m.Attach(mockCopyFile("/tmp/test.pdf")) want := &message{ from: "from@example.com", @@ -247,8 +247,8 @@ func TestAttachmentsOnly(t *testing.T) { m := NewMessage() m.SetHeader("From", "from@example.com") m.SetHeader("To", "to@example.com") - m.Attach(testFile("/tmp/test.pdf")) - m.Attach(testFile("/tmp/test.zip")) + m.Attach(mockCopyFile("/tmp/test.pdf")) + m.Attach(mockCopyFile("/tmp/test.zip")) want := &message{ from: "from@example.com", @@ -280,8 +280,8 @@ func TestAttachments(t *testing.T) { m.SetHeader("From", "from@example.com") m.SetHeader("To", "to@example.com") m.SetBody("text/plain", "Test") - m.Attach(testFile("/tmp/test.pdf")) - m.Attach(testFile("/tmp/test.zip")) + m.Attach(mockCopyFile("/tmp/test.pdf")) + m.Attach(mockCopyFile("/tmp/test.zip")) want := &message{ from: "from@example.com", @@ -317,10 +317,8 @@ func TestEmbedded(t *testing.T) { m := NewMessage() m.SetHeader("From", "from@example.com") m.SetHeader("To", "to@example.com") - f := testFile("image1.jpg") - f.Header["Content-ID"] = []string{""} - m.Embed(f) - m.Embed(testFile("image2.jpg")) + m.Embed(mockCopyFileWithHeader(m, "image1.jpg", map[string][]string{"Content-ID": {""}})) + m.Embed(mockCopyFile("image2.jpg")) m.SetBody("text/plain", "Test") want := &message{ @@ -361,8 +359,8 @@ func TestFullMessage(t *testing.T) { m.SetHeader("To", "to@example.com") m.SetBody("text/plain", "¡Hola, señor!") m.AddAlternative("text/html", "¡Hola, señor!") - m.Attach(testFile("test.pdf")) - m.Embed(testFile("image.jpg")) + m.Attach(mockCopyFile("test.pdf")) + m.Embed(mockCopyFile("image.jpg")) want := &message{ from: "from@example.com", @@ -591,13 +589,16 @@ func getBoundaries(t *testing.T, count int, m string) []string { var boundaryRegExp = regexp.MustCompile("boundary=(\\w+)") -func testFile(name string) *File { - f := NewFile(name) - f.Copier = func(w io.Writer) error { - _, err := w.Write([]byte("Content of " + filepath.Base(f.Name))) +func mockCopyFile(name string) (string, FileSetting) { + return name, SetCopyFunc(func(w io.Writer) error { + _, err := w.Write([]byte("Content of " + filepath.Base(name))) return err - } - return f + }) +} + +func mockCopyFileWithHeader(m *Message, name string, h map[string][]string) (string, FileSetting, FileSetting) { + name, f := mockCopyFile(name) + return name, f, SetHeader(h) } func BenchmarkFull(b *testing.B) { @@ -618,8 +619,8 @@ func BenchmarkFull(b *testing.B) { }) m.SetBody("text/plain", "¡Hola, señor!") m.AddAlternative("text/html", "

¡Hola, señor!

") - m.Attach(testFile("benchmark.txt")) - m.Embed(testFile("benchmark.jpg")) + m.Attach(mockCopyFile("benchmark.txt")) + m.Embed(mockCopyFile("benchmark.jpg")) if err := Send(discardFunc, m); err != nil { panic(err) diff --git a/writeto.go b/writeto.go index 625d63e..57a1dd7 100644 --- a/writeto.go +++ b/writeto.go @@ -104,7 +104,7 @@ func (w *messageWriter) closeMultipart() { } } -func (w *messageWriter) addFiles(files []*File, isAttachment bool) { +func (w *messageWriter) addFiles(files []*file, isAttachment bool) { for _, f := range files { if _, ok := f.Header["Content-Type"]; !ok { mediaType := mime.TypeByExtension(filepath.Ext(f.Name)) @@ -134,7 +134,7 @@ func (w *messageWriter) addFiles(files []*File, isAttachment bool) { } } w.writeHeaders(f.Header) - w.writeBody(f.Copier, Base64) + w.writeBody(f.CopyFunc, Base64) } }