diff --git a/mail/attachment.go b/mail/attachment.go index 3fbbce2..928fe5f 100644 --- a/mail/attachment.go +++ b/mail/attachment.go @@ -4,13 +4,8 @@ import ( "github.com/emersion/go-message" ) -// An AttachmentHeader represents an attachment's header. -type AttachmentHeader struct { - message.Header -} - -// Filename parses the attachment's filename. -func (h *AttachmentHeader) Filename() (string, error) { +// parseFilename parses the filename from the header. +func parseFilename(h message.Header) (string, error) { _, params, err := h.ContentDisposition() filename, ok := params["filename"] @@ -23,8 +18,34 @@ func (h *AttachmentHeader) Filename() (string, error) { return filename, err } +// An AttachmentHeader represents an attachment's header. +type AttachmentHeader struct { + message.Header +} + +// Filename parses the attachment's filename. +func (h *AttachmentHeader) Filename() (string, error) { + return parseFilename(h.Header) +} + // SetFilename formats the attachment's filename. func (h *AttachmentHeader) SetFilename(filename string) { dispParams := map[string]string{"filename": filename} h.SetContentDisposition("attachment", dispParams) } + +// An InlineAttachmentHeader represents an inlined attachment's header. +type InlineAttachmentHeader struct { + message.Header +} + +// Filename parses the attachment's filename. +func (h *InlineAttachmentHeader) Filename() (string, error) { + return parseFilename(h.Header) +} + +// SetFilename formats the attachment's filename. +func (h *InlineAttachmentHeader) SetFilename(filename string) { + dispParams := map[string]string{"filename": filename} + h.SetContentDisposition("inline", dispParams) +} diff --git a/mail/attachment_test.go b/mail/attachment_test.go index 9424b49..1c70ef3 100644 --- a/mail/attachment_test.go +++ b/mail/attachment_test.go @@ -49,3 +49,14 @@ func TestAttachmentHeader_Filename_encoded(t *testing.T) { t.Errorf("Expected filename to be %q but got %q", "", filename) } } + +func TestInlineAttachmentHeader_Filename(t *testing.T) { + var h mail.InlineAttachmentHeader + h.Set("Content-Disposition", "inline; filename=note.txt") + + if filename, err := h.Filename(); err != nil { + t.Error("Expected no error while parsing filename, got:", err) + } else if filename != "note.txt" { + t.Errorf("Expected filename to be %q but got %q", "note.txt", filename) + } +} diff --git a/mail/writer.go b/mail/writer.go index 6e6a0d2..3cbc52b 100644 --- a/mail/writer.go +++ b/mail/writer.go @@ -33,6 +33,16 @@ func initAttachmentHeader(h *AttachmentHeader) { } } +func initInlineAttachmentHeader(h *InlineAttachmentHeader) { + disp, _, _ := h.ContentDisposition() + if disp != "inline" { + h.Set("Content-Disposition", "inline") + } + if !h.Has("Content-Transfer-Encoding") { + h.Set("Content-Transfer-Encoding", "base64") + } +} + // A Writer writes a mail message. A mail message contains one or more text // parts and zero or more attachments. type Writer struct { @@ -52,9 +62,25 @@ func CreateWriter(w io.Writer, header Header) (*Writer, error) { return &Writer{mw}, nil } +// CreateAlternativeWriter writes a mail header to w. The mail will contain an +// inline part, allowing to represent the same text in different formats. +// Attachments cannot be included. +func CreateAlternativeWriter(w io.Writer, header Header) (*AlternativeWriter, error) { + header = header.Copy() // don't modify the caller's view + header.Set("Content-Type", "multipart/alternative") + + mw, err := message.CreateWriter(w, header.Header) + if err != nil { + return nil, err + } + + return &AlternativeWriter{mw}, nil +} + // CreateInlineWriter writes a mail header to w. The mail will contain an // inline part, allowing to represent the same text in different formats. // Attachments cannot be included. +// Deprecated: Use CreateAlternativeWriter and the returned AlternativeWriter instead. func CreateInlineWriter(w io.Writer, header Header) (*InlineWriter, error) { header = header.Copy() // don't modify the caller's view header.Set("Content-Type", "multipart/alternative") @@ -67,6 +93,20 @@ func CreateInlineWriter(w io.Writer, header Header) (*InlineWriter, error) { return &InlineWriter{mw}, nil } +// CreateRelatedWriter writes a mail header to w. The mail will contain an +// inline part and any inline attachments. Non-inline attachments cannot be added. +func CreateRelatedWriter(w io.Writer, header Header) (*RelatedWriter, error) { + header = header.Copy() // don't modify the caller's view + header.Set("Content-Type", "multipart/related") + + mw, err := message.CreateWriter(w, header.Header) + if err != nil { + return nil, err + } + + return &RelatedWriter{mw}, nil +} + // CreateSingleInlineWriter writes a mail header to w. The mail will contain a // single inline part. The body of the part should be written to the returned // io.WriteCloser. Only one single inline part should be written, use @@ -77,8 +117,24 @@ func CreateSingleInlineWriter(w io.Writer, header Header) (io.WriteCloser, error return message.CreateWriter(w, header.Header) } +// ------------------------------------------------------------------------------------------------------------------ // + +// CreateAlternative creates an AlternativeWriter. One or more parts representing the same +// text in different formats can be written to an AlternativeWriter. +func (w *Writer) CreateAlternative() (*AlternativeWriter, error) { + var h message.Header + h.Set("Content-Type", "multipart/alternative") + + mw, err := w.mw.CreatePart(h) + if err != nil { + return nil, err + } + return &AlternativeWriter{mw}, nil +} + // CreateInline creates a InlineWriter. One or more parts representing the same // text in different formats can be written to a InlineWriter. +// Deprecated: Use CreateAlternative() and the AlternativeWriter instead. func (w *Writer) CreateInline() (*InlineWriter, error) { var h message.Header h.Set("Content-Type", "multipart/alternative") @@ -90,10 +146,24 @@ func (w *Writer) CreateInline() (*InlineWriter, error) { return &InlineWriter{mw}, nil } +// CreateRelated created a RelatedWriter. Inline attachments and one or more +// parts representing the same text in different format can be written to a +// RelatedWriter. +func (w *Writer) CreateRelated() (*RelatedWriter, error) { + var h message.Header + h.Set("Content-Type", "multipart/related") + + mw, err := w.mw.CreatePart(h) + if err != nil { + return nil, err + } + return &RelatedWriter{mw}, nil +} + // CreateSingleInline creates a new single text part with the provided header. // The body of the part should be written to the returned io.WriteCloser. Only -// one single text part should be written, use CreateInline if you want multiple -// text parts. +// one single text part should be written, use CreateAlternative if you want +// multiple text parts. func (w *Writer) CreateSingleInline(h InlineHeader) (io.WriteCloser, error) { h = InlineHeader{h.Header.Copy()} // don't modify the caller's view initInlineHeader(&h) @@ -113,13 +183,33 @@ func (w *Writer) Close() error { return w.mw.Close() } +// AlternativeWriter writes a mail message's text. +type AlternativeWriter struct { + mw *message.Writer +} + +// CreatePart creates a new text part with the provided header. The body of the +// part should be written to the returned io.WriteCloser. +func (w *AlternativeWriter) CreatePart(h InlineHeader) (io.WriteCloser, error) { + h = InlineHeader{h.Header.Copy()} // don't modify the caller's view + initInlineHeader(&h) + return w.mw.CreatePart(h.Header) +} + +// Close finishes the AlternativeWriter. +func (w *AlternativeWriter) Close() error { + return w.mw.Close() +} + // InlineWriter writes a mail message's text. +// Deprecated: Use AlternativeWriter instead. type InlineWriter struct { mw *message.Writer } // CreatePart creates a new text part with the provided header. The body of the // part should be written to the returned io.WriteCloser. +// Deprecated: Use AlternativeWriter and its CreatePart instead. func (w *InlineWriter) CreatePart(h InlineHeader) (io.WriteCloser, error) { h = InlineHeader{h.Header.Copy()} // don't modify the caller's view initInlineHeader(&h) @@ -130,3 +220,44 @@ func (w *InlineWriter) CreatePart(h InlineHeader) (io.WriteCloser, error) { func (w *InlineWriter) Close() error { return w.mw.Close() } + +// RelatedWriter write a mail message with inline attachments and text parts. +type RelatedWriter struct { + mw *message.Writer +} + +// CreateAlternative creates an AlternativeWriter. One or more parts representing the same +// text in different formats can be written to the AlternativeWriter. +func (w *RelatedWriter) CreateAlternative() (*AlternativeWriter, error) { + var h message.Header + h.Set("Content-Type", "multipart/alternative") + + mw, err := w.mw.CreatePart(h) + if err != nil { + return nil, err + } + return &AlternativeWriter{mw}, nil +} + +// CreateSingleInline creates a new single text part with the provided header. +// The body of the part should be written to the returned io.WriteCloser. Only +// one single text part should be written, use CreateAlternative if you want multiple +// text parts. +func (w *RelatedWriter) CreateSingleInline(h InlineHeader) (io.WriteCloser, error) { + h = InlineHeader{h.Header.Copy()} // don't modify the caller's view + initInlineHeader(&h) + return w.mw.CreatePart(h.Header) +} + +// CreateInlineAttachment creates a new inline attachment with the provided header. +// The body of the part should be written to the returned io.WriteCloser. +func (w *RelatedWriter) CreateInlineAttachment(h InlineAttachmentHeader) (io.WriteCloser, error) { + h = InlineAttachmentHeader{h.Header.Copy()} // don't modify the caller's view + initInlineAttachmentHeader(&h) + return w.mw.CreatePart(h.Header) +} + +// Close finishes the RelatedWriter. +func (w *RelatedWriter) Close() error { + return w.mw.Close() +} diff --git a/mail/writer_test.go b/mail/writer_test.go index edaeaba..79bd9b8 100644 --- a/mail/writer_test.go +++ b/mail/writer_test.go @@ -29,7 +29,7 @@ func ExampleWriter() { } // Create a text part - tw, err := mw.CreateInline() + tw, err := mw.CreateAlternative() if err != nil { log.Fatal(err) } @@ -70,7 +70,7 @@ func TestWriter(t *testing.T) { } // Create a text part - tw, err := mw.CreateInline() + tw, err := mw.CreateAlternative() if err != nil { t.Fatal(err) }