Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds support of multipart/related for inline attachments #181

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 28 additions & 7 deletions mail/attachment.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand All @@ -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)
}
11 changes: 11 additions & 0 deletions mail/attachment_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
135 changes: 133 additions & 2 deletions mail/writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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")
Expand All @@ -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
Expand All @@ -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")
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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()
}
4 changes: 2 additions & 2 deletions mail/writer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ func ExampleWriter() {
}

// Create a text part
tw, err := mw.CreateInline()
tw, err := mw.CreateAlternative()
if err != nil {
log.Fatal(err)
}
Expand Down Expand Up @@ -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)
}
Expand Down