domodwyer / mailyak

An elegant MIME/SMTP email library with support for attachments

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Probably incorrect MIME data in case if plain/html wasn't specified

Darkclainer opened this issue · comments

Hello! I'm not an expert in MIME data format, but I have encountered with next problem while trying to send message to Maddy mail server.

Initially I tried to send mail that contain only attachment and Maddy server reported that:

submission: DATA error	{"msg_id":"e9728fad","reason":"multipart: NextPart: EOF"}

After some digging I have found, that MailYak "declare" multipart/alternative section without starting and ending it. Consider next example:

package main

import (
	"fmt"
	"net/smtp"
	"strings"

	"github.com/domodwyer/mailyak/v3"
)

func main() {
	mail := mailyak.New("", smtp.PlainAuth("", "", "", ""))
	mail.To("dom@itsallbroken.com")
	buf, err := mail.MimeBuf()
	if err != nil {
		panic(err)
	}
	fmt.Printf("Without plain/html or attachment: \n%s\n\n", buf.String())

	mail = mailyak.New("", smtp.PlainAuth("", "", "", ""))
	mail.To("dom@itsallbroken.com")
	mail.Plain().Set("test")
	buf, err = mail.MimeBuf()
	if err != nil {
		panic(err)
	}
	fmt.Printf("With plain only: \n%s\n\n", buf.String())

	mail = mailyak.New("", smtp.PlainAuth("", "", "", ""))
	mail.To("dom@itsallbroken.com")
	mail.Attach("myattachment", strings.NewReader("content"))
	buf, err = mail.MimeBuf()
	if err != nil {
		panic(err)
	}
	fmt.Printf("With attachment only: \n%s\n\n", buf.String())
}

The program produces next output:

Without plain/html or attachment:
From:
Mime-Version: 1.0
Date: Tue, 18 Jul 2023 16:10:51 +0400
Subject:
To: dom@itsallbroken.com
Content-Type: multipart/mixed;
	boundary="8d455f169f5b75c648ace9dc418bc6cc66a9ad91bc1b84c5422a96ca119d"; charset=UTF-8

--8d455f169f5b75c648ace9dc418bc6cc66a9ad91bc1b84c5422a96ca119d
Content-Type: multipart/alternative;
	boundary="215e77d6e9720917deaf807da239776f10c78ad00e797773767055c35b62"


--8d455f169f5b75c648ace9dc418bc6cc66a9ad91bc1b84c5422a96ca119d--


With plain only:
From:
Mime-Version: 1.0
Date: Tue, 18 Jul 2023 16:10:51 +0400
Subject:
To: dom@itsallbroken.com
Content-Type: multipart/mixed;
	boundary="a2877f81c63ed570e6cbcedb6d66023659c84beeda56255193965019d24c"; charset=UTF-8

--a2877f81c63ed570e6cbcedb6d66023659c84beeda56255193965019d24c
Content-Type: multipart/alternative;
	boundary="ab744051ed44f83173cf7023d22b54443d987acfea3c43906ed8b4734887"

--ab744051ed44f83173cf7023d22b54443d987acfea3c43906ed8b4734887
Content-Transfer-Encoding: quoted-printable
Content-Type: text/plain; charset=UTF-8

test
--ab744051ed44f83173cf7023d22b54443d987acfea3c43906ed8b4734887--

--a2877f81c63ed570e6cbcedb6d66023659c84beeda56255193965019d24c--


With attachment only:
From:
Mime-Version: 1.0
Date: Tue, 18 Jul 2023 16:10:51 +0400
Subject:
To: dom@itsallbroken.com
Content-Type: multipart/mixed;
	boundary="5b015828a43b39e9c393a7602b852053de3f11a4632e5acdbad189bc129f"; charset=UTF-8

--5b015828a43b39e9c393a7602b852053de3f11a4632e5acdbad189bc129f
Content-Type: multipart/alternative;
	boundary="20466d6e600bd4dd414edd0fd41d39d6f635eab3e6704a3c621bebf7bb65"


--5b015828a43b39e9c393a7602b852053de3f11a4632e5acdbad189bc129f
Content-Disposition: attachment;
	filename="myattachment"; name="myattachment"
Content-ID: <myattachment>
Content-Transfer-Encoding: base64
Content-Type: text/plain; charset=utf-8;
	filename="myattachment"; name="myattachment"

Y29udGVudA==
--5b015828a43b39e9c393a7602b852053de3f11a4632e5acdbad189bc129f--

In the case denoted as Without plain/html or attachment we can see:

Content-Type: multipart/alternative;
	boundary="215e77d6e9720917deaf807da239776f10c78ad00e797773767055c35b62"

Without section containing 215e77d6e9720917deaf807da239776f10c78ad00e797773767055c35b62 which is different from case when we set plain data.

As I said I'm no expert, so probably this is correct MIME format, but probably for better interoperability empty section should omitted entirely or written anyway, see early skip in MailYak code:

// writeBody writes the text/plain and text/html mime parts.
func (m *MailYak) writeBody(w io.Writer, boundary string) error {
	if m.plain.Len() == 0 && m.html.Len() == 0 {
		// No body to write - just skip it
		return nil
	}
       // ...
}

Probably I found relevant part in the RFC that describes MIME media types: https://datatracker.ietf.org/doc/html/rfc2046#section-5.1

Quote:

In the case of multipart entities, in which one or more different
   sets of data are combined in a single body, a "multipart" media type
   field must appear in the entity's header.  The body must then contain
   one or more body parts, each preceded by a boundary delimiter line,
   and the last one followed by a closing boundary delimiter line.

Which I understood as if multipart defined then body parts must be present.

commented

Hmm top investigating!

I certainly read your RFC quote the same way - I think the multipart sections shouldn't be included when empty - you're right!

Is this something you'd be interested in opening a PR for? If not I can stick it on my TODO list 👍

Thanks for the help!

(ps: sorry for the slow response - I was on holiday!)

Thank you for the response!

OK, I will gladly submit fix for this issue.