Skip to content

Commit 6f9f764

Browse files
committed
Don't split Quoted Printable characters during folding procedure
1 parent f7e2be8 commit 6f9f764

File tree

2 files changed

+36
-1
lines changed

2 files changed

+36
-1
lines changed

header.go

+21-1
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,17 @@ package message
33
import (
44
"mime"
55
"net/textproto"
6+
"regexp"
67
"strings"
78

89
"github.com/emersion/go-message/charset"
910
)
1011

1112
const maxHeaderLen = 76
1213

14+
// Regexp that detects Quoted Printable (QP) characters
15+
var qpReg = regexp.MustCompile("(=[0-9A-Z]{2,2})+")
16+
1317
func parseHeaderWithParams(s string) (f string, params map[string]string, err error) {
1418
f, params, err = mime.ParseMediaType(s)
1519
if err != nil {
@@ -59,8 +63,24 @@ func formatHeaderField(k, v string) string {
5963
folding = "\r\n"
6064
}
6165
} else {
66+
// Find the last QP character before limit
67+
foldAtQP := qpReg.FindAllStringIndex(v[:foldBefore], -1)
6268
// Find the closest whitespace before i
63-
foldAt = strings.LastIndexAny(v[:foldBefore], " \t\n")
69+
foldAtEOL := strings.LastIndexAny(v[:foldBefore], " \t\n")
70+
71+
// Fold at the latest whitespace by default
72+
foldAt = foldAtEOL
73+
74+
// if there are QP characters in the string
75+
if len(foldAtQP) > 0 {
76+
// Get the start index of the last QP character
77+
foldAtQPLastIndex := foldAtQP[len(foldAtQP)-1][0]
78+
if foldAtQPLastIndex > foldAt {
79+
// Fold at the latest QP character if there are no whitespaces after it and before line hard limit
80+
foldAt = foldAtQPLastIndex
81+
}
82+
}
83+
6484
if foldAt == 0 {
6585
// The whitespace we found was the previous folding WSP
6686
foldAt = foldBefore - 1

header_test.go

+15
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,21 @@ var formatHeaderFieldTests = []struct {
5959
v: "This is yet \t another subject \t with many whitespace characters",
6060
formatted: "Subject: This is yet \t another subject \t \r\n with many whitespace characters\r\n",
6161
},
62+
{
63+
k: "Subject",
64+
v: "=?utf-8?q?=E2=80=9CDeveloper_reads_customer_requested_change.=E2=80=9D=0A?= =?utf-8?q?=0ACaravaggio=0A=0AOil_on...?=",
65+
formatted: "Subject: =?utf-8?q?=E2=80=9CDeveloper_reads_customer_requested_change.\r\n =E2=80=9D=0A?= =?utf-8?q?=0ACaravaggio=0A=0AOil_on...?=\r\n",
66+
},
67+
{
68+
k: "Subject",
69+
v: "=?utf-8?q?=E2=80=9CShort subject=E2=80=9D=0A?= =?utf-8?q?=0AAuthor=0A=0AOil_on...?=",
70+
formatted: "Subject: =?utf-8?q?=E2=80=9CShort subject=E2=80=9D=0A?= =?utf-8?q?\r\n =0AAuthor=0A=0AOil_on...?=\r\n",
71+
},
72+
{
73+
k: "Subject",
74+
v: "=?utf-8?q?=E2=80=9CVery long subject very long subject very long subject very long subject=E2=80=9D=0A?= =?utf-8?q?=0ALong second part of subject long second part of subject long second part of subject long subject=0A=0AOil_on...?=",
75+
formatted: "Subject: =?utf-8?q?=E2=80=9CVery long subject very long subject very long\r\n subject very long subject=E2=80=9D=0A?= =?utf-8?q?=0ALong second part of\r\n subject long second part of subject long second part of subject long\r\n subject=0A=0AOil_on...?=\r\n",
76+
},
6277
{
6378
k: "DKIM-Signature",
6479
v: "v=1;\r\n h=From:To:Reply-To:Subject:Message-ID:References:In-Reply-To:MIME-Version;\r\n d=example.org\r\n",

0 commit comments

Comments
 (0)