Browse Source

if Attachment.HTMLRelated is set, mark attachment as inline (#135)

Properly setting "Content-Disposition: inline" for attachments marked as
related. Otherwise some email clients (e.g. Thunderbird) are not showing
inline images correctly.
Bo Simonsen 4 years ago
parent
commit
b84218f6af
2 changed files with 36 additions and 13 deletions
  1. 29 11
      email.go
  2. 7 2
      email_test.go

+ 29 - 11
email.go

@@ -276,18 +276,11 @@ func (e *Email) Attach(r io.Reader, filename string, c string) (a *Attachment, e
 		return
 	}
 	at := &Attachment{
-		Filename: filename,
-		Header:   textproto.MIMEHeader{},
-		Content:  buffer.Bytes(),
+		Filename:    filename,
+		ContentType: c,
+		Header:      textproto.MIMEHeader{},
+		Content:     buffer.Bytes(),
 	}
-	if c != "" {
-		at.Header.Set("Content-Type", c)
-	} else {
-		at.Header.Set("Content-Type", "application/octet-stream")
-	}
-	at.Header.Set("Content-Disposition", fmt.Sprintf("attachment;\r\n filename=\"%s\"", filename))
-	at.Header.Set("Content-ID", fmt.Sprintf("<%s>", filename))
-	at.Header.Set("Content-Transfer-Encoding", "base64")
 	e.Attachments = append(e.Attachments, at)
 	return at, nil
 }
@@ -482,6 +475,7 @@ func (e *Email) Bytes() ([]byte, error) {
 			}
 			if len(htmlAttachments) > 0 {
 				for _, a := range htmlAttachments {
+					a.setDefaultHeaders()
 					ap, err := relatedWriter.CreatePart(a.Header)
 					if err != nil {
 						return nil, err
@@ -503,6 +497,7 @@ func (e *Email) Bytes() ([]byte, error) {
 	}
 	// Create attachment part, if necessary
 	for _, a := range otherAttachments {
+		a.setDefaultHeaders()
 		ap, err := w.CreatePart(a.Header)
 		if err != nil {
 			return nil, err
@@ -714,11 +709,34 @@ func (e *Email) SendWithStartTLS(addr string, a smtp.Auth, t *tls.Config) error
 // Based on the mime/multipart.FileHeader struct, Attachment contains the name, MIMEHeader, and content of the attachment in question
 type Attachment struct {
 	Filename    string
+	ContentType string
 	Header      textproto.MIMEHeader
 	Content     []byte
 	HTMLRelated bool
 }
 
+func (at *Attachment) setDefaultHeaders() {
+	contentType := "application/octet-stream"
+	if len(at.ContentType) > 0 {
+		contentType = at.ContentType
+	}
+	at.Header.Set("Content-Type", contentType)
+
+	if len(at.Header.Get("Content-Disposition")) == 0 {
+		disposition := "attachment"
+		if at.HTMLRelated {
+			disposition = "inline"
+		}
+		at.Header.Set("Content-Disposition", fmt.Sprintf("%s;\r\n filename=\"%s\"", disposition, at.Filename))
+	}
+	if len(at.Header.Get("Content-ID")) == 0 {
+		at.Header.Set("Content-ID", fmt.Sprintf("<%s>", at.Filename))
+	}
+	if len(at.Header.Get("Content-Transfer-Encoding")) == 0 {
+		at.Header.Set("Content-Transfer-Encoding", "base64")
+	}
+}
+
 // base64Wrap encodes the attachment content, and wraps it according to RFC 2045 standards (every 76 chars)
 // The output is then written to the specified io.Writer
 func base64Wrap(w io.Writer, b []byte) {

+ 7 - 2
email_test.go

@@ -115,8 +115,9 @@ func TestEmailWithHTMLAttachments(t *testing.T) {
 	}
 	for _, part := range ps {
 		// part has "header" and "body []byte"
+		cd := part.header.Get("Content-Disposition")
 		ct := part.header.Get("Content-Type")
-		if strings.Contains(ct, "image/png") {
+		if strings.Contains(ct, "image/png") && strings.HasPrefix(cd, "inline") {
 			imageFound = true
 		}
 		if strings.Contains(ct, "text/html") {
@@ -371,11 +372,15 @@ func TestEmailAttachment(t *testing.T) {
 	mixed := multipart.NewReader(msg.Body, params["boundary"])
 
 	// Check attachments.
-	_, err = mixed.NextPart()
+	a, err := mixed.NextPart()
 	if err != nil {
 		t.Fatalf("Could not find attachment component of email: %s", err)
 	}
 
+	if !strings.HasPrefix(a.Header.Get("Content-Disposition"), "attachment") {
+		t.Fatalf("Content disposition is not attachment: %s", a.Header.Get("Content-Disposition"))
+	}
+
 	if _, err = mixed.NextPart(); err != io.EOF {
 		t.Error("Expected only one attachment!")
 	}