Skip to content

Commit

Permalink
Merge pull request #270 from wneessen/feature/262_support-7bit-encoding
Browse files Browse the repository at this point in the history
Add 7bit support for EML parsing
  • Loading branch information
wneessen authored Aug 1, 2024
2 parents b4acaf1 + ed93e51 commit 3ca2968
Show file tree
Hide file tree
Showing 4 changed files with 165 additions and 2 deletions.
18 changes: 16 additions & 2 deletions eml.go
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down Expand Up @@ -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)
Expand Down
145 changes: 145 additions & 0 deletions eml_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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: <[email protected]>
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" <[email protected]>
To: <[email protected]>
Cc: <[email protected]>
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
Expand Down Expand Up @@ -525,6 +548,72 @@ hw22iFHl7YlpOmedZvtMTfQffXeXnvI+rTKNxguyvDKvB7U4qQAAAAlwSFlzAAALEwAACxMBAJqc
GAAAABFJREFUCJljnMoAA0wMNGcCAEQrAKk9oHKhAAAAAElFTkSuQmCC
--fe785e0384e2607697cc2ecb17cce003003bb7ca9112104f3e8ce727edb5--`
exampleMultiPart7BitBase64 = `Date: Wed, 01 Nov 2023 00:00:00 +0000
MIME-Version: 1.0
Message-ID: <[email protected]>
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" <[email protected]>
To: <[email protected]>
Cc: <[email protected]>
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: <[email protected]>
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" <[email protected]>
To: <[email protected]>
Cc: <[email protected]>
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) {
Expand All @@ -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",
Expand Down Expand Up @@ -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))
Expand Down
3 changes: 3 additions & 0 deletions encoding.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down
1 change: 1 addition & 0 deletions encoding_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down

0 comments on commit 3ca2968

Please sign in to comment.