Skip to content

Commit 8f42967

Browse files
bradfitzbroady
authored andcommitted
[release-branch.go1.4] net/textproto: don't treat spaces as hyphens in header keys
This was originally done in https://codereview.appspot.com/5690059 (Feb 2012) to deal with bad response headers coming back from webcams, but it presents a potential security problem with HTTP request smuggling for request headers containing "Content Length" instead of "Content-Length". Part of overall HTTP hardening for request smuggling. See RFC 7230. Thanks to Régis Leroy for the report. Change-Id: I92b17fb637c9171c5774ea1437979ae2c17ca88a Reviewed-on: https://go-review.googlesource.com/11772 Reviewed-by: Russ Cox <[email protected]> Run-TryBot: Brad Fitzpatrick <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-on: https://go-review.googlesource.com/14249 Reviewed-by: Andrew Gerrand <[email protected]>
1 parent e938de2 commit 8f42967

File tree

3 files changed

+42
-7
lines changed

3 files changed

+42
-7
lines changed

src/net/http/header.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,8 @@ func (h Header) WriteSubset(w io.Writer, exclude map[string]bool) error {
168168
// letter and any letter following a hyphen to upper case;
169169
// the rest are converted to lowercase. For example, the
170170
// canonical key for "accept-encoding" is "Accept-Encoding".
171+
// If s contains a space or invalid header field bytes, it is
172+
// returned without modifications.
171173
func CanonicalHeaderKey(s string) string { return textproto.CanonicalMIMEHeaderKey(s) }
172174

173175
// hasToken reports whether token appears with v, ASCII

src/net/textproto/reader.go

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -540,11 +540,16 @@ func (r *Reader) upcomingHeaderNewlines() (n int) {
540540
// the rest are converted to lowercase. For example, the
541541
// canonical key for "accept-encoding" is "Accept-Encoding".
542542
// MIME header keys are assumed to be ASCII only.
543+
// If s contains a space or invalid header field bytes, it is
544+
// returned without modifications.
543545
func CanonicalMIMEHeaderKey(s string) string {
544546
// Quick check for canonical encoding.
545547
upper := true
546548
for i := 0; i < len(s); i++ {
547549
c := s[i]
550+
if !validHeaderFieldByte(c) {
551+
return s
552+
}
548553
if upper && 'a' <= c && c <= 'z' {
549554
return canonicalMIMEHeaderKey([]byte(s))
550555
}
@@ -558,19 +563,44 @@ func CanonicalMIMEHeaderKey(s string) string {
558563

559564
const toLower = 'a' - 'A'
560565

566+
// validHeaderFieldByte reports whether b is a valid byte in a header
567+
// field key. This is actually stricter than RFC 7230, which says:
568+
// tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." /
569+
// "^" / "_" / "`" / "|" / "~" / DIGIT / ALPHA
570+
// token = 1*tchar
571+
// TODO: revisit in Go 1.6+ and possibly expand this. But note that many
572+
// servers have historically dropped '_' to prevent ambiguities when mapping
573+
// to CGI environment variables.
574+
func validHeaderFieldByte(b byte) bool {
575+
return ('A' <= b && b <= 'Z') ||
576+
('a' <= b && b <= 'z') ||
577+
('0' <= b && b <= '9') ||
578+
b == '-'
579+
}
580+
561581
// canonicalMIMEHeaderKey is like CanonicalMIMEHeaderKey but is
562582
// allowed to mutate the provided byte slice before returning the
563583
// string.
584+
//
585+
// For invalid inputs (if a contains spaces or non-token bytes), a
586+
// is unchanged and a string copy is returned.
564587
func canonicalMIMEHeaderKey(a []byte) string {
588+
// See if a looks like a header key. If not, return it unchanged.
589+
for _, c := range a {
590+
if validHeaderFieldByte(c) {
591+
continue
592+
}
593+
// Don't canonicalize.
594+
return string(a)
595+
}
596+
565597
upper := true
566598
for i, c := range a {
567599
// Canonicalize: first letter upper case
568600
// and upper case after each dash.
569601
// (Host, User-Agent, If-Modified-Since).
570602
// MIME headers are ASCII only, so no Unicode issues.
571-
if c == ' ' {
572-
c = '-'
573-
} else if upper && 'a' <= c && c <= 'z' {
603+
if upper && 'a' <= c && c <= 'z' {
574604
c -= toLower
575605
} else if !upper && 'A' <= c && c <= 'Z' {
576606
c += toLower

src/net/textproto/reader_test.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,14 @@ var canonicalHeaderKeyTests = []canonicalHeaderKeyTest{
2424
{"uSER-aGENT", "User-Agent"},
2525
{"user-agent", "User-Agent"},
2626
{"USER-AGENT", "User-Agent"},
27-
{"üser-agenT", "üser-Agent"}, // non-ASCII unchanged
27+
28+
// Non-ASCII or anything with spaces or non-token chars is unchanged:
29+
{"üser-agenT", "üser-agenT"},
30+
{"a B", "a B"},
2831

2932
// This caused a panic due to mishandling of a space:
30-
{"C Ontent-Transfer-Encoding", "C-Ontent-Transfer-Encoding"},
31-
{"foo bar", "Foo-Bar"},
33+
{"C Ontent-Transfer-Encoding", "C Ontent-Transfer-Encoding"},
34+
{"foo bar", "foo bar"},
3235
}
3336

3437
func TestCanonicalMIMEHeaderKey(t *testing.T) {
@@ -185,7 +188,7 @@ func TestReadMIMEHeaderNonCompliant(t *testing.T) {
185188
"Foo": {"bar"},
186189
"Content-Language": {"en"},
187190
"Sid": {"0"},
188-
"Audio-Mode": {"None"},
191+
"Audio Mode": {"None"},
189192
"Privilege": {"127"},
190193
}
191194
if !reflect.DeepEqual(m, want) || err != nil {

0 commit comments

Comments
 (0)