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

Support for MIME headers when using NewEmailFromReader #140

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
2 changes: 2 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ jobs:
build:
name: Build
runs-on: ubuntu-latest
env:
GO111MODULE: auto
steps:

- name: Set up Go 1.x
Expand Down
21 changes: 14 additions & 7 deletions email.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,9 +165,9 @@ func NewEmailFromReader(r io.Reader) (*Email, error) {
if err != nil {
return e, err
}
filename, filenameDefined := params["filename"]
if cd == "attachment" || (cd == "inline" && filenameDefined) {
_, err = e.Attach(bytes.NewReader(p.body), filename, ct)
filename, _ := params["filename"]
if cd == "attachment" || (cd == "inline" && p.header.Get("Content-ID") != "") {
_, err = e.AttachWithHeaders(bytes.NewReader(p.body), filename, ct, p.header)
if err != nil {
return e, err
}
Expand Down Expand Up @@ -262,14 +262,21 @@ func parseMIMEParts(hs textproto.MIMEHeader, b io.Reader) ([]*part, error) {
// Required parameters include an io.Reader, the desired filename for the attachment, and the Content-Type
// The function will return the created Attachment for reference, as well as nil for the error, if successful.
func (e *Email) Attach(r io.Reader, filename string, c string) (a *Attachment, err error) {
return e.AttachWithHeaders(r, filename, c, textproto.MIMEHeader{})
}

// AttachWithHeaders is used to attach content from an io.Reader to the email. Required parameters include an io.Reader,
// the desired filename for the attachment, the Content-Type and the original MIME headers.
// The function will return the created Attachment for reference, as well as nil for the error, if successful.
func (e *Email) AttachWithHeaders(r io.Reader, filename string, c string, headers textproto.MIMEHeader) (a *Attachment, err error) {
var buffer bytes.Buffer
if _, err = io.Copy(&buffer, r); err != nil {
return
}
at := &Attachment{
Filename: filename,
ContentType: c,
Header: textproto.MIMEHeader{},
Header: headers,
Content: buffer.Bytes(),
}
e.Attachments = append(e.Attachments, at)
Expand Down Expand Up @@ -714,11 +721,11 @@ func (at *Attachment) setDefaultHeaders() {
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", "inline")
}else{
at.Header.Set("Content-Disposition", fmt.Sprintf("%s;\r\n filename=\"%s\"", "attachment", at.Filename))
}
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))
Expand Down
56 changes: 50 additions & 6 deletions email_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,12 @@ func TestEmailWithHTMLAttachments(t *testing.T) {
}
attachment.HTMLRelated = true

// Set regular HTML attachment.
_, err = e.Attach(bytes.NewBufferString("Normal attachment"), "normal.pdf", "application/pdf; charset=utf-8")
if err != nil {
t.Fatal("Could not add an attachment to the message: ", err)
}

b, err := e.Bytes()
if err != nil {
t.Fatal("Could not serialize e-mail:", err)
Expand All @@ -110,14 +116,17 @@ func TestEmailWithHTMLAttachments(t *testing.T) {
plainTextFound := false
htmlFound := false
imageFound := false
if expected, actual := 3, len(ps); actual != expected {
if expected, actual := 4, len(ps); actual != expected {
t.Error("Unexpected number of parts. Expected:", expected, "Was:", actual)
}
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") && strings.HasPrefix(cd, "inline") {
if strings.Contains(ct, "image/png") && strings.HasPrefix(cd, "inline") && !strings.Contains(cd, "rad.txt"){
imageFound = true
}
if strings.Contains(ct, "application/pdf") && strings.HasPrefix(cd, "attachment") && strings.Contains(cd, "normal.pdf"){
imageFound = true
}
if strings.Contains(ct, "text/html") {
Expand Down Expand Up @@ -650,6 +659,11 @@ func TestAttachmentEmailFromReader(t *testing.T) {
if err != nil {
t.Fatalf("Error attaching inline image %s", err.Error())
}
c, err := ex.Attach(bytes.NewReader([]byte("Let's just pretend this is raw JPEG data.")), "html-related-cat.jpeg", "image/jpeg")
if err != nil {
t.Fatalf("Error attaching html-related inline image %s", err.Error())
}
b.HTMLRelated = true
raw := []byte(`
From: Jordan Wright <jmwright798@gmail.com>
Date: Thu, 17 Oct 2019 08:55:37 +0100
Expand Down Expand Up @@ -679,7 +693,7 @@ Content-Type: text/html; charset=UTF-8
--35d10c2224bd787fe700c2c6f4769ddc936eb8a0b58e9c8717e406c5abb7
Content-Disposition: attachment;
filename="cat.jpeg"
Content-Id: <cat.jpeg>
Content-Id: <cat.content-id>
Content-Transfer-Encoding: base64
Content-Type: image/jpeg

Expand All @@ -688,7 +702,15 @@ TGV0J3MganVzdCBwcmV0ZW5kIHRoaXMgaXMgcmF3IEpQRUcgZGF0YS4=
--35d10c2224bd787fe700c2c6f4769ddc936eb8a0b58e9c8717e406c5abb7
Content-Disposition: inline;
filename="cat-inline.jpeg"
Content-Id: <cat-inline.jpeg>
Content-Id: <cat-inline.content-id>
Content-Transfer-Encoding: base64
Content-Type: image/jpeg

TGV0J3MganVzdCBwcmV0ZW5kIHRoaXMgaXMgcmF3IEpQRUcgZGF0YS4=

--35d10c2224bd787fe700c2c6f4769ddc936eb8a0b58e9c8717e406c5abb7
Content-Disposition: inline
Content-Id: <html-related-cat.content-id>
Content-Transfer-Encoding: base64
Content-Type: image/jpeg

Expand All @@ -711,21 +733,43 @@ TGV0J3MganVzdCBwcmV0ZW5kIHRoaXMgaXMgcmF3IEpQRUcgZGF0YS4=
if e.From != ex.From {
t.Fatalf("Incorrect \"From\": %#q != %#q", e.From, ex.From)
}
if len(e.Attachments) != 2 {
t.Fatalf("Incorrect number of attachments %d != %d", len(e.Attachments), 1)
if len(e.Attachments) != 3 {
t.Fatalf("Incorrect number of attachments %d != %d", len(e.Attachments), 3)
}
if e.Attachments[0].Filename != a.Filename {
t.Fatalf("Incorrect attachment filename %s != %s", e.Attachments[0].Filename, a.Filename)
}
if !bytes.Equal(e.Attachments[0].Content, a.Content) {
t.Fatalf("Incorrect attachment content %#q != %#q", e.Attachments[0].Content, a.Content)
}
if e.Attachments[0].Header != nil {
if e.Attachments[0].Header.Get("Content-Id") != "<cat.content-id>" {
t.Fatalf("Incorrect attachment header Content-Id %s != %s", e.Attachments[0].Header.Get("Content-Id"), "<cat.content-id>")
}
}
if e.Attachments[1].Filename != b.Filename {
t.Fatalf("Incorrect attachment filename %s != %s", e.Attachments[1].Filename, b.Filename)
}
if !bytes.Equal(e.Attachments[1].Content, b.Content) {
t.Fatalf("Incorrect attachment content %#q != %#q", e.Attachments[1].Content, b.Content)
}
if e.Attachments[1].Header != nil {
if e.Attachments[1].Header.Get("Content-Id") != "<cat-inline.content-id>" {
t.Fatalf("Incorrect attachment header Content-Id %s != %s", e.Attachments[1].Header.Get("Content-Id"), "<cat-inline.content-id>")
}
}
//Filename should be empty as we are using html-related and inline attachment
if e.Attachments[2].Filename != "" {
t.Fatalf("Incorrect attachment filename %s != %s", e.Attachments[2].Filename, "")
}
if !bytes.Equal(e.Attachments[2].Content, c.Content) {
t.Fatalf("Incorrect attachment content %#q != %#q", e.Attachments[2].Content, c.Content)
}
if e.Attachments[2].Header != nil {
if e.Attachments[2].Header.Get("Content-Id") != "<html-related-cat.content-id>" {
t.Fatalf("Incorrect attachment header Content-Id %s != %s", e.Attachments[2].Header.Get("Content-Id"), "<html-related-cat.content-id>")
}
}
}

func ExampleGmail() {
Expand Down