File: 0002-fix-Reject-invalid-MIME-header-name-characters-to-su.patch

package info (click to toggle)
golang-github-jhillyerd-enmime 0.9.3-5
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 1,156 kB
  • sloc: makefile: 25; sh: 16
file content (155 lines) | stat: -rw-r--r-- 4,987 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
From: James Hillyerd <james@hillyerd.com>
Date: Thu, 2 Feb 2023 11:50:20 -0800
Subject: fix: Reject invalid MIME header name characters to support Go 1.20
 (#270)

* Test with go 1.20 release candidate
* header: Detect invalid header name characters for Go 1.20
* header: test for permitted special chars

Origin: backport, https://github.com/jhillyerd/enmime/commit/415ff72f
---
 header.go      | 59 +++++++++++++++++++++++++++++++++++++++++++++++++---------
 header_test.go | 18 +++++++++++++-----
 2 files changed, 63 insertions(+), 14 deletions(-)

diff --git a/header.go b/header.go
index cd9d28b..628e765 100644
--- a/header.go
+++ b/header.go
@@ -81,9 +81,9 @@ var AddressHeaders = map[string]bool{
 // ParseMediaType is a more tolerant implementation of Go's mime.ParseMediaType function.
 //
 // Tolerances accounted for:
-//   * Missing ';' between content-type and media parameters
-//   * Repeating media parameters
-//   * Unquoted values in media parameters containing 'tspecials' characters
+//   - Missing ';' between content-type and media parameters
+//   - Repeating media parameters
+//   - Unquoted values in media parameters containing 'tspecials' characters
 func ParseMediaType(ctype string) (mtype string, params map[string]string, invalidParams []string,
 	err error) {
 	// Export of internal function.
@@ -97,6 +97,7 @@ func readHeader(r *bufio.Reader, p *Part) (textproto.MIMEHeader, error) {
 	buf := &bytes.Buffer{}
 	tp := textproto.NewReader(r)
 	firstHeader := true
+line:
 	for {
 		// Pull out each line of the headers as a temporary slice s
 		s, err := tp.ReadLineBytes()
@@ -119,18 +120,29 @@ func readHeader(r *bufio.Reader, p *Part) (textproto.MIMEHeader, error) {
 			continue
 		}
 		if firstColon > 0 {
-			// Contains a colon, treat as a new header line
-			if !firstHeader {
-				// New Header line, end the previous
-				buf.Write([]byte{'\r', '\n'})
-			}
-
 			// Behavior change in net/textproto package in Golang 1.12.10 and 1.13.1:
 			// A space preceding the first colon in a header line is no longer handled
 			// automatically due to CVE-2019-16276 which takes advantage of this
 			// particular violation of RFC-7230 to exploit HTTP/1.1
 			if bytes.Contains(s[:firstColon+1], []byte{' ', ':'}) {
 				s = bytes.Replace(s, []byte{' ', ':'}, []byte{':'}, 1)
+				firstColon = bytes.IndexByte(s, ':')
+			}
+
+			// Behavior change in net/textproto package in Golang 1.20: invalid characters
+			// in header keys are no longer allowed; https://github.com/golang/go/issues/53188
+			for _, c := range s[:firstColon] {
+				if c != ' ' && !validHeaderFieldByte(c) {
+					p.addError(
+						ErrorMalformedHeader, "Header name %q contains invalid character %q", s, c)
+					continue line
+				}
+			}
+
+			// Contains a colon, treat as a new header line
+			if !firstHeader {
+				// New Header line, end the previous
+				buf.Write([]byte{'\r', '\n'})
 			}
 
 			s = textproto.TrimBytes(s)
@@ -213,3 +225,32 @@ func quotedDisplayName(s string) string {
 func whiteSpaceRune(r rune) bool {
 	return r == ' ' || r == '\t' || r == '\r' || r == '\n'
 }
+
+// Mirror of func in net/textproto/reader.go; from go 1.20.0-rc.2
+func validHeaderFieldByte(c byte) bool {
+	// mask is a 128-bit bitmap with 1s for allowed bytes,
+	// so that the byte c can be tested with a shift and an and.
+	// If c >= 128, then 1<<c and 1<<(c-64) will both be zero,
+	// and this function will return false.
+	const mask = 0 |
+		(1<<(10)-1)<<'0' |
+		(1<<(26)-1)<<'a' |
+		(1<<(26)-1)<<'A' |
+		1<<'!' |
+		1<<'#' |
+		1<<'$' |
+		1<<'%' |
+		1<<'&' |
+		1<<'\'' |
+		1<<'*' |
+		1<<'+' |
+		1<<'-' |
+		1<<'.' |
+		1<<'^' |
+		1<<'_' |
+		1<<'`' |
+		1<<'|' |
+		1<<'~'
+	return ((uint64(1)<<c)&(mask&(1<<64-1)) |
+		(uint64(1)<<(c-64))&(mask>>64)) != 0
+}
diff --git a/header_test.go b/header_test.go
index 01055be..5b18568 100644
--- a/header_test.go
+++ b/header_test.go
@@ -99,8 +99,6 @@ func TestReadHeader(t *testing.T) {
 		{
 			label:   "missing name",
 			input:   ": line1=foo\r\n",
-			hname:   "",
-			want:    "",
 			correct: false,
 		},
 		{
@@ -127,12 +125,17 @@ func TestReadHeader(t *testing.T) {
 			correct: true,
 		},
 		{
-			label:   "equals in name",
-			input:   "name=value:text\n",
-			hname:   "name=value",
+			label:   "permitted specials in name",
+			input:   "Begin!#$%&'*+-.^_`|~End: text\n",
+			hname:   "Begin!#$%&'*+-.^_`|~End",
 			want:    "text",
 			correct: true,
 		},
+		{
+			label:   "equals in name",
+			input:   "name=value:text\n",
+			correct: false,
+		},
 		{
 			label:   "no space before continuation",
 			input:   "X-Bad-Continuation: line1=foo;\nline2=bar\n",
@@ -236,6 +239,11 @@ func TestReadHeader(t *testing.T) {
 			gotErrs := len(p.Errors)
 			if gotErrs != wantErrs {
 				t.Errorf("Got %v p.Errors, want %v", gotErrs, wantErrs)
+				if gotErrs > 0 {
+					for _, e := range p.Errors {
+						t.Log(e)
+					}
+				}
 			}
 
 			// Check for extra headers by removing expected ones.