Forráskód Böngészése

Allow HTML only body with attachments in "multipart/related" (#133)

Previously the module did only handle HTML body with related attachments
in either "multipart/mixed" or "multipart/alternative". However, it is very
common to see the following structure:

 - multipart/related
   - text/html
   - image/png
   - ...

With this commit, it is being allowed.
Bo Simonsen 4 éve
szülő
commit
127602ebff
2 módosított fájl, 77 hozzáadás és 6 törlés
  1. 14 6
      email.go
  2. 63 0
      email_test.go

+ 14 - 6
email.go

@@ -177,7 +177,7 @@ func NewEmailFromReader(r io.Reader) (*Email, error) {
 				return e, err
 			}
 			filename, filenameDefined := params["filename"]
-			if cd == "attachment" || (cd == "inline" && filenameDefined){
+			if cd == "attachment" || (cd == "inline" && filenameDefined) {
 				_, err = e.Attach(bytes.NewReader(p.body), filename, ct)
 				if err != nil {
 					return e, err
@@ -409,10 +409,11 @@ func (e *Email) Bytes() ([]byte, error) {
 	var (
 		isMixed       = len(otherAttachments) > 0
 		isAlternative = len(e.Text) > 0 && len(e.HTML) > 0
+		isRelated     = len(e.HTML) > 0 && len(htmlAttachments) > 0
 	)
 
 	var w *multipart.Writer
-	if isMixed || isAlternative {
+	if isMixed || isAlternative || isRelated {
 		w = multipart.NewWriter(buff)
 	}
 	switch {
@@ -420,6 +421,8 @@ func (e *Email) Bytes() ([]byte, error) {
 		headers.Set("Content-Type", "multipart/mixed;\r\n boundary="+w.Boundary())
 	case isAlternative:
 		headers.Set("Content-Type", "multipart/alternative;\r\n boundary="+w.Boundary())
+	case isRelated:
+		headers.Set("Content-Type", "multipart/related;\r\n boundary="+w.Boundary())
 	case len(e.HTML) > 0:
 		headers.Set("Content-Type", "text/html; charset=UTF-8")
 		headers.Set("Content-Transfer-Encoding", "quoted-printable")
@@ -459,7 +462,7 @@ func (e *Email) Bytes() ([]byte, error) {
 		if len(e.HTML) > 0 {
 			messageWriter := subWriter
 			var relatedWriter *multipart.Writer
-			if len(htmlAttachments) > 0 {
+			if (isMixed || isAlternative) && len(htmlAttachments) > 0 {
 				relatedWriter = multipart.NewWriter(buff)
 				header := textproto.MIMEHeader{
 					"Content-Type": {"multipart/related;\r\n boundary=" + relatedWriter.Boundary()},
@@ -469,9 +472,12 @@ func (e *Email) Bytes() ([]byte, error) {
 				}
 
 				messageWriter = relatedWriter
+			} else if isRelated && len(htmlAttachments) > 0 {
+				relatedWriter = w
+				messageWriter = w
 			}
 			// Write the HTML
-			if err := writeMessage(buff, e.HTML, isMixed || isAlternative, "text/html", messageWriter); err != nil {
+			if err := writeMessage(buff, e.HTML, isMixed || isAlternative || isRelated, "text/html", messageWriter); err != nil {
 				return nil, err
 			}
 			if len(htmlAttachments) > 0 {
@@ -484,7 +490,9 @@ func (e *Email) Bytes() ([]byte, error) {
 					base64Wrap(ap, a.Content)
 				}
 
-				relatedWriter.Close()
+				if isMixed || isAlternative {
+					relatedWriter.Close()
+				}
 			}
 		}
 		if isMixed && isAlternative {
@@ -502,7 +510,7 @@ func (e *Email) Bytes() ([]byte, error) {
 		// Write the base64Wrapped content to the part
 		base64Wrap(ap, a.Content)
 	}
-	if isMixed || isAlternative {
+	if isMixed || isAlternative || isRelated {
 		if err := w.Close(); err != nil {
 			return nil, err
 		}

+ 63 - 0
email_test.go

@@ -138,6 +138,69 @@ func TestEmailWithHTMLAttachments(t *testing.T) {
 	}
 }
 
+func TestEmailWithHTMLAttachmentsHTMLOnly(t *testing.T) {
+	e := prepareEmail()
+
+	e.HTML = []byte("<html><body>This is a text.</body></html>")
+
+	// Set HTML attachment to exercise "mime/related".
+	attachment, err := e.Attach(bytes.NewBufferString("Rad attachment"), "rad.txt", "image/png; charset=utf-8")
+	if err != nil {
+		t.Fatal("Could not add an attachment to the message: ", err)
+	}
+	attachment.HTMLRelated = true
+
+	b, err := e.Bytes()
+	if err != nil {
+		t.Fatal("Could not serialize e-mail:", err)
+	}
+
+	// Print the bytes for ocular validation and make sure no errors.
+	//fmt.Println(string(b))
+
+	// TODO: Verify the attachments.
+	s := &trimReader{rd: bytes.NewBuffer(b)}
+	tp := textproto.NewReader(bufio.NewReader(s))
+	// Parse the main headers
+	hdrs, err := tp.ReadMIMEHeader()
+	if err != nil {
+		t.Fatal("Could not parse the headers:", err)
+	}
+
+	if !strings.HasPrefix(hdrs.Get("Content-Type"), "multipart/related") {
+		t.Error("Envelope Content-Type is not multipart/related: ", hdrs["Content-Type"])
+	}
+
+	// Recursively parse the MIME parts
+	ps, err := parseMIMEParts(hdrs, tp.R)
+	if err != nil {
+		t.Fatal("Could not parse the MIME parts recursively:", err)
+	}
+
+	htmlFound := false
+	imageFound := false
+	if expected, actual := 2, len(ps); actual != expected {
+		t.Error("Unexpected number of parts. Expected:", expected, "Was:", actual)
+	}
+	for _, part := range ps {
+		// part has "header" and "body []byte"
+		ct := part.header.Get("Content-Type")
+		if strings.Contains(ct, "image/png") {
+			imageFound = true
+		}
+		if strings.Contains(ct, "text/html") {
+			htmlFound = true
+		}
+	}
+
+	if !htmlFound {
+		t.Error("Did not find HTML part.")
+	}
+	if !imageFound {
+		t.Error("Did not find image part.")
+	}
+}
+
 func TestEmailHTML(t *testing.T) {
 	e := prepareEmail()
 	e.HTML = []byte("<h1>Fancy Html is supported, too!</h1>\n")