diff --git a/eml.go b/eml.go index ed20d0d..896e0c6 100644 --- a/eml.go +++ b/eml.go @@ -207,6 +207,12 @@ func parseEMLBodyParts(parsedMsg *netmail.Message, bodybuf *bytes.Buffer, msg *M // parseEMLBodyPlain parses the mail body of plain type mails func parseEMLBodyPlain(mediatype string, parsedMsg *netmail.Message, bodybuf *bytes.Buffer, msg *Msg) error { contentTransferEnc := parsedMsg.Header.Get(HeaderContentTransferEnc.String()) + // According to RFC2045, if no Content-Transfer-Encoding is set, we can imply 7bit US-ASCII encoding + if contentTransferEnc == "" || strings.EqualFold(contentTransferEnc, EncodingUSASCII.String()) { + msg.SetEncoding(EncodingUSASCII) + msg.SetBodyString(ContentType(mediatype), bodybuf.String()) + return nil + } if strings.EqualFold(contentTransferEnc, NoEncoding.String()) { msg.SetEncoding(NoEncoding) msg.SetBodyString(ContentType(mediatype), bodybuf.String()) @@ -308,14 +314,22 @@ ReadNextPart: } switch { + case strings.EqualFold(mutliPartTransferEnc[0], EncodingUSASCII.String()): + part.SetEncoding(EncodingUSASCII) + part.SetContent(string(multiPartData)) + case strings.EqualFold(mutliPartTransferEnc[0], NoEncoding.String()): + part.SetEncoding(NoEncoding) + part.SetContent(string(multiPartData)) case strings.EqualFold(mutliPartTransferEnc[0], EncodingB64.String()): - if err := handleEMLMultiPartBase64Encoding(multiPartData, part); err != nil { + part.SetEncoding(EncodingB64) + if err = handleEMLMultiPartBase64Encoding(multiPartData, part); err != nil { return fmt.Errorf("failed to handle multipart base64 transfer-encoding: %w", err) } case strings.EqualFold(mutliPartTransferEnc[0], EncodingQP.String()): + part.SetEncoding(EncodingQP) part.SetContent(string(multiPartData)) default: - return fmt.Errorf("unsupported Content-Transfer-Encoding") + return fmt.Errorf("unsupported Content-Transfer-Encoding: %s", mutliPartTransferEnc[0]) } msg.parts = append(msg.parts, part) diff --git a/eml_test.go b/eml_test.go index 8823eec..5704ce4 100644 --- a/eml_test.go +++ b/eml_test.go @@ -32,6 +32,29 @@ This is a test mail. Please do not reply to this. Also this line is very long so should be wrapped. +Thank your for your business! +The go-mail team + +-- +This is a signature` + exampleMailPlain7Bit = `Date: Wed, 01 Nov 2023 00:00:00 +0000 +MIME-Version: 1.0 +Message-ID: <1305604950.683004066175.AAAAAAAAaaaaaaaaB@go-mail.dev> +Subject: Example mail // plain text without encoding +User-Agent: go-mail v0.4.0 // https://github.com/wneessen/go-mail +X-Mailer: go-mail v0.4.0 // https://github.com/wneessen/go-mail +From: "Toni Tester" +To: +Cc: +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 7bit + +Dear Customer, + +This is a test mail. Please do not reply to this. Also this line is very long so it +should be wrapped. + + Thank your for your business! The go-mail team @@ -525,6 +548,72 @@ hw22iFHl7YlpOmedZvtMTfQffXeXnvI+rTKNxguyvDKvB7U4qQAAAAlwSFlzAAALEwAACxMBAJqc GAAAABFJREFUCJljnMoAA0wMNGcCAEQrAKk9oHKhAAAAAElFTkSuQmCC --fe785e0384e2607697cc2ecb17cce003003bb7ca9112104f3e8ce727edb5--` + exampleMultiPart7BitBase64 = `Date: Wed, 01 Nov 2023 00:00:00 +0000 +MIME-Version: 1.0 +Message-ID: <1305604950.683004066175.AAAAAAAAaaaaaaaaB@go-mail.dev> +Subject: Example mail // 7bit with base64 attachment +User-Agent: go-mail v0.4.1 // https://github.com/wneessen/go-mail +X-Mailer: go-mail v0.4.1 // https://github.com/wneessen/go-mail +From: "Toni Tester" +To: +Cc: +Content-Type: multipart/mixed; + boundary="------------26A45336F6C6196BD8BBA2A2" + +This is a multi-part message in MIME format. +--------------26A45336F6C6196BD8BBA2A2 +Content-Type: text/plain; charset=US-ASCII; format=flowed +Content-Transfer-Encoding: 7bit + +testtest +testtest +testtest +testtest +testtest +testtest + +--------------26A45336F6C6196BD8BBA2A2 +Content-Type: text/plain; charset=UTF-8; + name="testfile.txt" +Content-Transfer-Encoding: base64 +Content-Disposition: attachment; + filename="testfile.txt" + +VGhpcyBpcyBhIHRlc3QgaW4gQmFzZTY0 +--------------26A45336F6C6196BD8BBA2A2--` + exampleMultiPart8BitBase64 = `Date: Wed, 01 Nov 2023 00:00:00 +0000 +MIME-Version: 1.0 +Message-ID: <1305604950.683004066175.AAAAAAAAaaaaaaaaB@go-mail.dev> +Subject: Example mail // 8bit with base64 attachment +User-Agent: go-mail v0.4.1 // https://github.com/wneessen/go-mail +X-Mailer: go-mail v0.4.1 // https://github.com/wneessen/go-mail +From: "Toni Tester" +To: +Cc: +Content-Type: multipart/mixed; + boundary="------------26A45336F6C6196BD8BBA2A2" + +This is a multi-part message in MIME format. +--------------26A45336F6C6196BD8BBA2A2 +Content-Type: text/plain; charset=US-ASCII; format=flowed +Content-Transfer-Encoding: 8bit + +testtest +testtest +testtest +testtest +testtest +testtest + +--------------26A45336F6C6196BD8BBA2A2 +Content-Type: text/plain; charset=UTF-8; + name="testfile.txt" +Content-Transfer-Encoding: base64 +Content-Disposition: attachment; + filename="testfile.txt" + +VGhpcyBpcyBhIHRlc3QgaW4gQmFzZTY0 +--------------26A45336F6C6196BD8BBA2A2--` ) func TestEMLToMsgFromString(t *testing.T) { @@ -534,6 +623,10 @@ func TestEMLToMsgFromString(t *testing.T) { enc string sub string }{ + { + "Plain text no encoding (7bit)", exampleMailPlain7Bit, "7bit", + "Example mail // plain text without encoding", + }, { "Plain text no encoding", exampleMailPlainNoEnc, "8bit", "Example mail // plain text without encoding", @@ -866,6 +959,58 @@ func TestEMLToMsgFromStringMultipartMixedAlternativeRelated(t *testing.T) { } } +func TestEMLToMsgFromStringMultipartMixedWith7Bit(t *testing.T) { + wantSubject := "Example mail // 7bit with base64 attachment" + msg, err := EMLToMsgFromString(exampleMultiPart7BitBase64) + if err != nil { + t.Errorf("EML multipart mixed with 7bit: %s", err) + } + if subject := msg.GetGenHeader(HeaderSubject); len(subject) > 0 && !strings.EqualFold(subject[0], wantSubject) { + t.Errorf("EMLToMsgFromString of EML multipart mixed with 7bit: expected subject: %s,"+ + " but got: %s", wantSubject, subject[0]) + } + if len(msg.parts) != 1 { + t.Errorf("EMLToMsgFromString of EML multipart mixed with 7bit failed: expected 1 part, got: %d", + len(msg.parts)) + return + } + if !strings.EqualFold(msg.parts[0].GetEncoding().String(), EncodingUSASCII.String()) { + t.Errorf("EMLToMsgFromString of EML multipart mixed with 7bit failed: expected encoding: %s, got %s", + EncodingUSASCII.String(), msg.parts[0].GetEncoding().String()) + } + if len(msg.attachments) != 1 { + t.Errorf("EMLToMsgFromString of EML multipart mixed with 7bit failed: expected 1 attachment, got: %d", + len(msg.attachments)) + return + } +} + +func TestEMLToMsgFromStringMultipartMixedWith8Bit(t *testing.T) { + wantSubject := "Example mail // 8bit with base64 attachment" + msg, err := EMLToMsgFromString(exampleMultiPart8BitBase64) + if err != nil { + t.Errorf("EML multipart mixed with 8bit: %s", err) + } + if subject := msg.GetGenHeader(HeaderSubject); len(subject) > 0 && !strings.EqualFold(subject[0], wantSubject) { + t.Errorf("EMLToMsgFromString of EML multipart mixed with 8bit: expected subject: %s,"+ + " but got: %s", wantSubject, subject[0]) + } + if len(msg.parts) != 1 { + t.Errorf("EMLToMsgFromString of EML multipart mixed with 8bit failed: expected 1 part, got: %d", + len(msg.parts)) + return + } + if !strings.EqualFold(msg.parts[0].GetEncoding().String(), NoEncoding.String()) { + t.Errorf("EMLToMsgFromString of EML multipart mixed with 8bit failed: expected encoding: %s, got %s", + NoEncoding.String(), msg.parts[0].GetEncoding().String()) + } + if len(msg.attachments) != 1 { + t.Errorf("EMLToMsgFromString of EML multipart mixed with 8bit failed: expected 1 attachment, got: %d", + len(msg.attachments)) + return + } +} + // stringToTempFile is a helper method that will create a temporary file form a give data string func stringToTempFile(data, name string) (string, string, error) { tempDir, err := os.MkdirTemp("", fmt.Sprintf("*-%s", name)) diff --git a/encoding.go b/encoding.go index 2187e5f..47213da 100644 --- a/encoding.go +++ b/encoding.go @@ -27,6 +27,9 @@ const ( // EncodingQP represents the "quoted-printable" encoding as specified in RFC 2045. EncodingQP Encoding = "quoted-printable" + // EncodingUSASCII represents encoding with only US-ASCII characters (aka 7Bit) + EncodingUSASCII Encoding = "7bit" + // NoEncoding avoids any character encoding (except of the mail headers) NoEncoding Encoding = "8bit" ) diff --git a/encoding_test.go b/encoding_test.go index 86f686a..14711b7 100644 --- a/encoding_test.go +++ b/encoding_test.go @@ -16,6 +16,7 @@ func TestEncoding_String(t *testing.T) { {"Encoding: Base64", EncodingB64, "base64"}, {"Encoding: QP", EncodingQP, "quoted-printable"}, {"Encoding: None/8bit", NoEncoding, "8bit"}, + {"Encoding: US-ASCII/7bit", EncodingUSASCII, "7bit"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) {