Skip to content

Commit 27695fa

Browse files
committed
http/httpguts: reject leading and trailing spaces in field values
RFC9113 is extremely clear about this: > A field value MUST NOT start or end with an ASCII whitespace character > (ASCII SP or HTAB, 0x20 or 0x09). RFC9114 defers to RFC9110, which in turn provides a grammer that does not permit leading and/or trailing whitespace either.
1 parent 3e7a445 commit 27695fa

File tree

2 files changed

+63
-39
lines changed

2 files changed

+63
-39
lines changed

http/httpguts/httplex.go

+24-39
Original file line numberDiff line numberDiff line change
@@ -262,48 +262,33 @@ var validHostByte = [256]bool{
262262
'~': true, // unreserved
263263
}
264264

265+
// validFieldValueChar reports whether v is an RFC9110 field-vchar, SP, or HTAB.
266+
func validFieldValueChar(v uint8) bool {
267+
if v < ' ' {
268+
return v == '\t'
269+
} else {
270+
return v != 0x7F
271+
}
272+
}
273+
265274
// ValidHeaderFieldValue reports whether v is a valid "field-value" according to
266-
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2 :
267-
//
268-
// message-header = field-name ":" [ field-value ]
269-
// field-value = *( field-content | LWS )
270-
// field-content = <the OCTETs making up the field-value
271-
// and consisting of either *TEXT or combinations
272-
// of token, separators, and quoted-string>
273-
//
274-
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html#sec2.2 :
275-
//
276-
// TEXT = <any OCTET except CTLs,
277-
// but including LWS>
278-
// LWS = [CRLF] 1*( SP | HT )
279-
// CTL = <any US-ASCII control character
280-
// (octets 0 - 31) and DEL (127)>
275+
// <https://rfc-editor.org/rfc/rfc9110#name-field-values>:
281276
//
282-
// RFC 7230 says:
283-
//
284-
// field-value = *( field-content / obs-fold )
285-
// obj-fold = N/A to http2, and deprecated
286-
// field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ]
287-
// field-vchar = VCHAR / obs-text
288-
// obs-text = %x80-FF
289-
// VCHAR = "any visible [USASCII] character"
290-
//
291-
// http2 further says: "Similarly, HTTP/2 allows header field values
292-
// that are not valid. While most of the values that can be encoded
293-
// will not alter header field parsing, carriage return (CR, ASCII
294-
// 0xd), line feed (LF, ASCII 0xa), and the zero character (NUL, ASCII
295-
// 0x0) might be exploited by an attacker if they are translated
296-
// verbatim. Any request or response that contains a character not
297-
// permitted in a header field value MUST be treated as malformed
298-
// (Section 8.1.2.6). Valid characters are defined by the
299-
// field-content ABNF rule in Section 3.2 of [RFC7230]."
300-
//
301-
// This function does not (yet?) properly handle the rejection of
302-
// strings that begin or end with SP or HTAB.
277+
// field-value = *field-content
278+
// field-content = field-vchar
279+
// [ 1*( SP / HTAB / field-vchar ) field-vchar ]
280+
// field-vchar = VCHAR / obs-text
281+
// obs-text = %x80-FF
303282
func ValidHeaderFieldValue(v string) bool {
304-
for i := 0; i < len(v); i++ {
305-
b := v[i]
306-
if isCTL(b) && !isLWS(b) {
283+
l := len(v)
284+
if l == 0 {
285+
return true
286+
}
287+
if v[0] <= ' ' || v[l - 1] <= ' ' {
288+
return false
289+
}
290+
for i := 0; i < l; i++ {
291+
if !validFieldValueChar(v[i]) {
307292
return false
308293
}
309294
}

http/httpguts/httplex_test.go

+39
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,45 @@ func TestValidHeaderFieldName(t *testing.T) {
129129
}
130130
}
131131

132+
func TestValidHeaderFieldValue(t *testing.T) {
133+
tests := []struct {
134+
in string
135+
want bool
136+
}{
137+
{"", true},
138+
{" junk", false},
139+
{"\tjunk", false},
140+
{"junk\t", false},
141+
{"junk ", false},
142+
{" ", false},
143+
{"\t", false},
144+
}
145+
for i := byte(0); true; i++ {
146+
bad := i < ' '
147+
if i == 0x7f {
148+
bad = true
149+
}
150+
if i == '\t' {
151+
bad = false
152+
}
153+
tests = append(tests,
154+
struct {
155+
in string
156+
want bool
157+
}{string([]byte{'a', i, 'b'}), !bad})
158+
if i == 255 {
159+
break
160+
}
161+
}
162+
163+
for _, tt := range tests {
164+
got := ValidHeaderFieldValue(tt.in)
165+
if tt.want != got {
166+
t.Errorf("ValidHeaderFieldValue(%q) = %t; want %t", tt.in, got, tt.want)
167+
}
168+
}
169+
}
170+
132171
func BenchmarkValidHeaderFieldName(b *testing.B) {
133172
names := []string{
134173
"",

0 commit comments

Comments
 (0)