Merge pull request #270 from wneessen/feature/262_support-7bit-encoding

Add 7bit support for EML parsing
This commit is contained in:
Winni Neessen 2024-08-01 11:06:35 +02:00 committed by GitHub
commit 3ca2968158
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 165 additions and 2 deletions

18
eml.go
View file

@ -207,6 +207,12 @@ func parseEMLBodyParts(parsedMsg *netmail.Message, bodybuf *bytes.Buffer, msg *M
// parseEMLBodyPlain parses the mail body of plain type mails // parseEMLBodyPlain parses the mail body of plain type mails
func parseEMLBodyPlain(mediatype string, parsedMsg *netmail.Message, bodybuf *bytes.Buffer, msg *Msg) error { func parseEMLBodyPlain(mediatype string, parsedMsg *netmail.Message, bodybuf *bytes.Buffer, msg *Msg) error {
contentTransferEnc := parsedMsg.Header.Get(HeaderContentTransferEnc.String()) 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()) { if strings.EqualFold(contentTransferEnc, NoEncoding.String()) {
msg.SetEncoding(NoEncoding) msg.SetEncoding(NoEncoding)
msg.SetBodyString(ContentType(mediatype), bodybuf.String()) msg.SetBodyString(ContentType(mediatype), bodybuf.String())
@ -308,14 +314,22 @@ ReadNextPart:
} }
switch { 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()): 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) return fmt.Errorf("failed to handle multipart base64 transfer-encoding: %w", err)
} }
case strings.EqualFold(mutliPartTransferEnc[0], EncodingQP.String()): case strings.EqualFold(mutliPartTransferEnc[0], EncodingQP.String()):
part.SetEncoding(EncodingQP)
part.SetContent(string(multiPartData)) part.SetContent(string(multiPartData))
default: default:
return fmt.Errorf("unsupported Content-Transfer-Encoding") return fmt.Errorf("unsupported Content-Transfer-Encoding: %s", mutliPartTransferEnc[0])
} }
msg.parts = append(msg.parts, part) msg.parts = append(msg.parts, part)

View file

@ -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. 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" <go-mail@go-mail.dev>
To: <go-mail+test@go-mail.dev>
Cc: <go-mail+cc@go-mail.dev>
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! Thank your for your business!
The go-mail team The go-mail team
@ -525,6 +548,72 @@ hw22iFHl7YlpOmedZvtMTfQffXeXnvI+rTKNxguyvDKvB7U4qQAAAAlwSFlzAAALEwAACxMBAJqc
GAAAABFJREFUCJljnMoAA0wMNGcCAEQrAKk9oHKhAAAAAElFTkSuQmCC GAAAABFJREFUCJljnMoAA0wMNGcCAEQrAKk9oHKhAAAAAElFTkSuQmCC
--fe785e0384e2607697cc2ecb17cce003003bb7ca9112104f3e8ce727edb5--` --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" <go-mail@go-mail.dev>
To: <go-mail+test@go-mail.dev>
Cc: <go-mail+cc@go-mail.dev>
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" <go-mail@go-mail.dev>
To: <go-mail+test@go-mail.dev>
Cc: <go-mail+cc@go-mail.dev>
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) { func TestEMLToMsgFromString(t *testing.T) {
@ -534,6 +623,10 @@ func TestEMLToMsgFromString(t *testing.T) {
enc string enc string
sub string sub string
}{ }{
{
"Plain text no encoding (7bit)", exampleMailPlain7Bit, "7bit",
"Example mail // plain text without encoding",
},
{ {
"Plain text no encoding", exampleMailPlainNoEnc, "8bit", "Plain text no encoding", exampleMailPlainNoEnc, "8bit",
"Example mail // plain text without encoding", "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 // stringToTempFile is a helper method that will create a temporary file form a give data string
func stringToTempFile(data, name string) (string, string, error) { func stringToTempFile(data, name string) (string, string, error) {
tempDir, err := os.MkdirTemp("", fmt.Sprintf("*-%s", name)) tempDir, err := os.MkdirTemp("", fmt.Sprintf("*-%s", name))

View file

@ -27,6 +27,9 @@ const (
// EncodingQP represents the "quoted-printable" encoding as specified in RFC 2045. // EncodingQP represents the "quoted-printable" encoding as specified in RFC 2045.
EncodingQP Encoding = "quoted-printable" 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 avoids any character encoding (except of the mail headers)
NoEncoding Encoding = "8bit" NoEncoding Encoding = "8bit"
) )

View file

@ -16,6 +16,7 @@ func TestEncoding_String(t *testing.T) {
{"Encoding: Base64", EncodingB64, "base64"}, {"Encoding: Base64", EncodingB64, "base64"},
{"Encoding: QP", EncodingQP, "quoted-printable"}, {"Encoding: QP", EncodingQP, "quoted-printable"},
{"Encoding: None/8bit", NoEncoding, "8bit"}, {"Encoding: None/8bit", NoEncoding, "8bit"},
{"Encoding: US-ASCII/7bit", EncodingUSASCII, "7bit"},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {