diff options
| author | Brad Fitzpatrick <bradfitz@golang.org> | 2016-05-19 02:13:36 +0000 |
|---|---|---|
| committer | Brad Fitzpatrick <bradfitz@golang.org> | 2016-05-19 03:51:11 +0000 |
| commit | 255e206b2bae9e7632043e08cf8cc0c7ce445c31 (patch) | |
| tree | da1ef5be81a3764104571e704ae28e72b59ff2ba /src/net | |
| parent | 8d428ed218d2b65dbb4abbd9be870c95439a2b14 (diff) | |
| download | go-255e206b2bae9e7632043e08cf8cc0c7ce445c31.tar.xz | |
net/http: update bundled http2
Updates x/net/http2 to git rev 5916dcb1 for:
* http2, lex/httplex: make Transport reject bogus headers before sending
https://golang.org/cl/23229
* http2: reject more trailer values
https://golang.org/cl/23230
Fixes #14048
Fixes #14188
Change-Id: Iaa8beca6e005267a3e849a10013eb424a882f2bb
Reviewed-on: https://go-review.googlesource.com/23234
Reviewed-by: Andrew Gerrand <adg@golang.org>
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Diffstat (limited to 'src/net')
| -rw-r--r-- | src/net/http/h2_bundle.go | 216 | ||||
| -rw-r--r-- | src/net/http/http.go | 6 | ||||
| -rw-r--r-- | src/net/http/lex.go | 277 | ||||
| -rw-r--r-- | src/net/http/lex_test.go | 101 | ||||
| -rw-r--r-- | src/net/http/server.go | 8 | ||||
| -rw-r--r-- | src/net/http/transfer.go | 6 | ||||
| -rw-r--r-- | src/net/http/transport.go | 6 |
7 files changed, 99 insertions, 521 deletions
diff --git a/src/net/http/h2_bundle.go b/src/net/http/h2_bundle.go index 22047c5826..6f7fd382ea 100644 --- a/src/net/http/h2_bundle.go +++ b/src/net/http/h2_bundle.go @@ -26,6 +26,7 @@ import ( "errors" "fmt" "golang.org/x/net/http2/hpack" + "golang.org/x/net/lex/httplex" "io" "io/ioutil" "log" @@ -1864,7 +1865,7 @@ func (fr *http2Framer) readMetaFrame(hf *http2HeadersFrame) (*http2MetaHeadersFr hdec.SetEmitEnabled(true) hdec.SetMaxStringLength(fr.maxHeaderStringLen()) hdec.SetEmitFunc(func(hf hpack.HeaderField) { - if !http2validHeaderFieldValue(hf.Value) { + if !httplex.ValidHeaderFieldValue(hf.Value) { invalid = http2headerFieldValueError(hf.Value) } isPseudo := strings.HasPrefix(hf.Name, ":") @@ -1874,7 +1875,7 @@ func (fr *http2Framer) readMetaFrame(hf *http2HeadersFrame) (*http2MetaHeadersFr } } else { sawRegular = true - if !http2validHeaderFieldName(hf.Name) { + if !http2validWireHeaderFieldName(hf.Name) { invalid = http2headerFieldNameError(hf.Name) } } @@ -2397,58 +2398,23 @@ var ( http2errInvalidHeaderFieldValue = errors.New("http2: invalid header field value") ) -// validHeaderFieldName reports whether v is a valid header field name (key). -// RFC 7230 says: -// header-field = field-name ":" OWS field-value OWS -// field-name = token -// token = 1*tchar -// tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." / -// "^" / "_" / "`" / "|" / "~" / DIGIT / ALPHA +// validWireHeaderFieldName reports whether v is a valid header field +// name (key). See httplex.ValidHeaderName for the base rules. +// // Further, http2 says: // "Just as in HTTP/1.x, header field names are strings of ASCII // characters that are compared in a case-insensitive // fashion. However, header field names MUST be converted to // lowercase prior to their encoding in HTTP/2. " -func http2validHeaderFieldName(v string) bool { +func http2validWireHeaderFieldName(v string) bool { if len(v) == 0 { return false } for _, r := range v { - if int(r) >= len(http2isTokenTable) || ('A' <= r && r <= 'Z') { + if !httplex.IsTokenRune(r) { return false } - if !http2isTokenTable[byte(r)] { - return false - } - } - return true -} - -// validHeaderFieldValue reports whether v is a valid header field value. -// -// RFC 7230 says: -// field-value = *( field-content / obs-fold ) -// obj-fold = N/A to http2, and deprecated -// field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ] -// field-vchar = VCHAR / obs-text -// obs-text = %x80-FF -// VCHAR = "any visible [USASCII] character" -// -// http2 further says: "Similarly, HTTP/2 allows header field values -// that are not valid. While most of the values that can be encoded -// will not alter header field parsing, carriage return (CR, ASCII -// 0xd), line feed (LF, ASCII 0xa), and the zero character (NUL, ASCII -// 0x0) might be exploited by an attacker if they are translated -// verbatim. Any request or response that contains a character not -// permitted in a header field value MUST be treated as malformed -// (Section 8.1.2.6). Valid characters are defined by the -// field-content ABNF rule in Section 3.2 of [RFC7230]." -// -// This function does not (yet?) properly handle the rejection of -// strings that begin or end with SP or HTAB. -func http2validHeaderFieldValue(v string) bool { - for i := 0; i < len(v); i++ { - if b := v[i]; b < ' ' && b != '\t' || b == 0x7f { + if 'A' <= r && r <= 'Z' { return false } } @@ -2579,86 +2545,6 @@ func (e *http2httpError) Temporary() bool { return true } var http2errTimeout error = &http2httpError{msg: "http2: timeout awaiting response headers", timeout: true} -var http2isTokenTable = [127]bool{ - '!': true, - '#': true, - '$': true, - '%': true, - '&': true, - '\'': true, - '*': true, - '+': true, - '-': true, - '.': true, - '0': true, - '1': true, - '2': true, - '3': true, - '4': true, - '5': true, - '6': true, - '7': true, - '8': true, - '9': true, - 'A': true, - 'B': true, - 'C': true, - 'D': true, - 'E': true, - 'F': true, - 'G': true, - 'H': true, - 'I': true, - 'J': true, - 'K': true, - 'L': true, - 'M': true, - 'N': true, - 'O': true, - 'P': true, - 'Q': true, - 'R': true, - 'S': true, - 'T': true, - 'U': true, - 'W': true, - 'V': true, - 'X': true, - 'Y': true, - 'Z': true, - '^': true, - '_': true, - '`': true, - 'a': true, - 'b': true, - 'c': true, - 'd': true, - 'e': true, - 'f': true, - 'g': true, - 'h': true, - 'i': true, - 'j': true, - 'k': true, - 'l': true, - 'm': true, - 'n': true, - 'o': true, - 'p': true, - 'q': true, - 'r': true, - 's': true, - 't': true, - 'u': true, - 'v': true, - 'w': true, - 'x': true, - 'y': true, - 'z': true, - '|': true, - '~': true, -} - type http2connectionStater interface { ConnectionState() tls.ConnectionState } @@ -4103,6 +3989,10 @@ func (st *http2stream) processTrailerHeaders(f *http2MetaHeadersFrame) error { if st.trailer != nil { for _, hf := range f.RegularFields() { key := sc.canonicalHeader(hf.Name) + if !http2ValidTrailerHeader(key) { + + return http2StreamError{st.id, http2ErrCodeProtocol} + } st.trailer[key] = append(st.trailer[key], hf.Value) } } @@ -4508,9 +4398,9 @@ func (rws *http2responseWriterState) hasTrailers() bool { return len(rws.trailer // written in the trailers at the end of the response. func (rws *http2responseWriterState) declareTrailer(k string) { k = CanonicalHeaderKey(k) - switch k { - case "Transfer-Encoding", "Content-Length", "Trailer": + if !http2ValidTrailerHeader(k) { + rws.conn.logf("ignoring invalid trailer %q", k) return } if !http2strSliceContains(rws.trailers, k) { @@ -4831,6 +4721,41 @@ func http2new400Handler(err error) HandlerFunc { } } +// ValidTrailerHeader reports whether name is a valid header field name to appear +// in trailers. +// See: http://tools.ietf.org/html/rfc7230#section-4.1.2 +func http2ValidTrailerHeader(name string) bool { + name = CanonicalHeaderKey(name) + if strings.HasPrefix(name, "If-") || http2badTrailer[name] { + return false + } + return true +} + +var http2badTrailer = map[string]bool{ + "Authorization": true, + "Cache-Control": true, + "Connection": true, + "Content-Encoding": true, + "Content-Length": true, + "Content-Range": true, + "Content-Type": true, + "Expect": true, + "Host": true, + "Keep-Alive": true, + "Max-Forwards": true, + "Pragma": true, + "Proxy-Authenticate": true, + "Proxy-Authorization": true, + "Proxy-Connection": true, + "Range": true, + "Realm": true, + "Te": true, + "Trailer": true, + "Transfer-Encoding": true, + "Www-Authenticate": true, +} + const ( // transportDefaultConnFlow is how many connection-level flow control // tokens we give the server at start-up, past the default 64k. @@ -5423,20 +5348,28 @@ func (cc *http2ClientConn) RoundTrip(req *Request) (*Response, error) { return nil, http2errClientConnUnusable } - cs := cc.newStream() - cs.req = req - cs.trace = http2requestTrace(req) - hasBody := body != nil - + // TODO(bradfitz): this is a copy of the logic in net/http. Unify somewhere? + var requestedGzip bool if !cc.t.disableCompression() && req.Header.Get("Accept-Encoding") == "" && req.Header.Get("Range") == "" && req.Method != "HEAD" { - cs.requestedGzip = true + requestedGzip = true } - hdrs := cc.encodeHeaders(req, cs.requestedGzip, trailers, contentLen) + hdrs, err := cc.encodeHeaders(req, requestedGzip, trailers, contentLen) + if err != nil { + cc.mu.Unlock() + return nil, err + } + + cs := cc.newStream() + cs.req = req + cs.trace = http2requestTrace(req) + hasBody := body != nil + cs.requestedGzip = requestedGzip + cc.wmu.Lock() endStream := !hasBody && !hasTrailers werr := cc.writeHeaders(cs.ID, endStream, hdrs) @@ -5689,7 +5622,7 @@ type http2badStringError struct { func (e *http2badStringError) Error() string { return fmt.Sprintf("%s %q", e.what, e.str) } // requires cc.mu be held. -func (cc *http2ClientConn) encodeHeaders(req *Request, addGzipHeader bool, trailers string, contentLength int64) []byte { +func (cc *http2ClientConn) encodeHeaders(req *Request, addGzipHeader bool, trailers string, contentLength int64) ([]byte, error) { cc.hbuf.Reset() host := req.Host @@ -5697,6 +5630,17 @@ func (cc *http2ClientConn) encodeHeaders(req *Request, addGzipHeader bool, trail host = req.URL.Host } + for k, vv := range req.Header { + if !httplex.ValidHeaderFieldName(k) { + return nil, fmt.Errorf("invalid HTTP header name %q", k) + } + for _, v := range vv { + if !httplex.ValidHeaderFieldValue(v) { + return nil, fmt.Errorf("invalid HTTP header value %q for header %q", v, k) + } + } + } + cc.writeHeader(":authority", host) cc.writeHeader(":method", req.Method) if req.Method != "CONNECT" { @@ -5741,7 +5685,7 @@ func (cc *http2ClientConn) encodeHeaders(req *Request, addGzipHeader bool, trail if !didUA { cc.writeHeader("user-agent", http2defaultUserAgent) } - return cc.hbuf.Bytes() + return cc.hbuf.Bytes(), nil } // shouldSendReqContentLength reports whether the http2.Transport should send @@ -6622,13 +6566,13 @@ func http2encodeHeaders(enc *hpack.Encoder, h Header, keys []string) { for _, k := range keys { vv := h[k] k = http2lowerHeader(k) - if !http2validHeaderFieldName(k) { + if !http2validWireHeaderFieldName(k) { continue } isTE := k == "transfer-encoding" for _, v := range vv { - if !http2validHeaderFieldValue(v) { + if !httplex.ValidHeaderFieldValue(v) { continue } diff --git a/src/net/http/http.go b/src/net/http/http.go index a121628632..4d088a5bb1 100644 --- a/src/net/http/http.go +++ b/src/net/http/http.go @@ -6,6 +6,8 @@ package http import ( "strings" + + "golang.org/x/net/lex/httplex" ) // maxInt64 is the effective "infinite" value for the Server and @@ -35,3 +37,7 @@ func removeEmptyPort(host string) string { } return host } + +func isNotToken(r rune) bool { + return !httplex.IsTokenRune(r) +} diff --git a/src/net/http/lex.go b/src/net/http/lex.go deleted file mode 100644 index 63d14ec2ec..0000000000 --- a/src/net/http/lex.go +++ /dev/null @@ -1,277 +0,0 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package http - -import ( - "strings" - "unicode/utf8" -) - -// This file deals with lexical matters of HTTP - -var isTokenTable = [127]bool{ - '!': true, - '#': true, - '$': true, - '%': true, - '&': true, - '\'': true, - '*': true, - '+': true, - '-': true, - '.': true, - '0': true, - '1': true, - '2': true, - '3': true, - '4': true, - '5': true, - '6': true, - '7': true, - '8': true, - '9': true, - 'A': true, - 'B': true, - 'C': true, - 'D': true, - 'E': true, - 'F': true, - 'G': true, - 'H': true, - 'I': true, - 'J': true, - 'K': true, - 'L': true, - 'M': true, - 'N': true, - 'O': true, - 'P': true, - 'Q': true, - 'R': true, - 'S': true, - 'T': true, - 'U': true, - 'W': true, - 'V': true, - 'X': true, - 'Y': true, - 'Z': true, - '^': true, - '_': true, - '`': true, - 'a': true, - 'b': true, - 'c': true, - 'd': true, - 'e': true, - 'f': true, - 'g': true, - 'h': true, - 'i': true, - 'j': true, - 'k': true, - 'l': true, - 'm': true, - 'n': true, - 'o': true, - 'p': true, - 'q': true, - 'r': true, - 's': true, - 't': true, - 'u': true, - 'v': true, - 'w': true, - 'x': true, - 'y': true, - 'z': true, - '|': true, - '~': true, -} - -func isToken(r rune) bool { - i := int(r) - return i < len(isTokenTable) && isTokenTable[i] -} - -func isNotToken(r rune) bool { - return !isToken(r) -} - -// headerValuesContainsToken reports whether any string in values -// contains the provided token, ASCII case-insensitively. -func headerValuesContainsToken(values []string, token string) bool { - for _, v := range values { - if headerValueContainsToken(v, token) { - return true - } - } - return false -} - -// isOWS reports whether b is an optional whitespace byte, as defined -// by RFC 7230 section 3.2.3. -func isOWS(b byte) bool { return b == ' ' || b == '\t' } - -// trimOWS returns x with all optional whitespace removes from the -// beginning and end. -func trimOWS(x string) string { - // TODO: consider using strings.Trim(x, " \t") instead, - // if and when it's fast enough. See issue 10292. - // But this ASCII-only code will probably always beat UTF-8 - // aware code. - for len(x) > 0 && isOWS(x[0]) { - x = x[1:] - } - for len(x) > 0 && isOWS(x[len(x)-1]) { - x = x[:len(x)-1] - } - return x -} - -// headerValueContainsToken reports whether v (assumed to be a -// 0#element, in the ABNF extension described in RFC 7230 section 7) -// contains token amongst its comma-separated tokens, ASCII -// case-insensitively. -func headerValueContainsToken(v string, token string) bool { - v = trimOWS(v) - if comma := strings.IndexByte(v, ','); comma != -1 { - return tokenEqual(trimOWS(v[:comma]), token) || headerValueContainsToken(v[comma+1:], token) - } - return tokenEqual(v, token) -} - -// lowerASCII returns the ASCII lowercase version of b. -func lowerASCII(b byte) byte { - if 'A' <= b && b <= 'Z' { - return b + ('a' - 'A') - } - return b -} - -// tokenEqual reports whether t1 and t2 are equal, ASCII case-insensitively. -func tokenEqual(t1, t2 string) bool { - if len(t1) != len(t2) { - return false - } - for i, b := range t1 { - if b >= utf8.RuneSelf { - // No UTF-8 or non-ASCII allowed in tokens. - return false - } - if lowerASCII(byte(b)) != lowerASCII(t2[i]) { - return false - } - } - return true -} - -// isLWS reports whether b is linear white space, according -// to http://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html#sec2.2 -// LWS = [CRLF] 1*( SP | HT ) -func isLWS(b byte) bool { return b == ' ' || b == '\t' } - -// isCTL reports whether b is a control byte, according -// to http://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html#sec2.2 -// CTL = <any US-ASCII control character -// (octets 0 - 31) and DEL (127)> -func isCTL(b byte) bool { - const del = 0x7f // a CTL - return b < ' ' || b == del -} - -func validHeaderName(v string) bool { - if len(v) == 0 { - return false - } - for _, r := range v { - if !isToken(r) { - return false - } - } - return true -} - -func validHostHeader(h string) bool { - // The latests spec is actually this: - // - // http://tools.ietf.org/html/rfc7230#section-5.4 - // Host = uri-host [ ":" port ] - // - // Where uri-host is: - // http://tools.ietf.org/html/rfc3986#section-3.2.2 - // - // But we're going to be much more lenient for now and just - // search for any byte that's not a valid byte in any of those - // expressions. - for i := 0; i < len(h); i++ { - if !validHostByte[h[i]] { - return false - } - } - return true -} - -// See the validHostHeader comment. -var validHostByte = [256]bool{ - '0': true, '1': true, '2': true, '3': true, '4': true, '5': true, '6': true, '7': true, - '8': true, '9': true, - - 'a': true, 'b': true, 'c': true, 'd': true, 'e': true, 'f': true, 'g': true, 'h': true, - 'i': true, 'j': true, 'k': true, 'l': true, 'm': true, 'n': true, 'o': true, 'p': true, - 'q': true, 'r': true, 's': true, 't': true, 'u': true, 'v': true, 'w': true, 'x': true, - 'y': true, 'z': true, - - 'A': true, 'B': true, 'C': true, 'D': true, 'E': true, 'F': true, 'G': true, 'H': true, - 'I': true, 'J': true, 'K': true, 'L': true, 'M': true, 'N': true, 'O': true, 'P': true, - 'Q': true, 'R': true, 'S': true, 'T': true, 'U': true, 'V': true, 'W': true, 'X': true, - 'Y': true, 'Z': true, - - '!': true, // sub-delims - '$': true, // sub-delims - '%': true, // pct-encoded (and used in IPv6 zones) - '&': true, // sub-delims - '(': true, // sub-delims - ')': true, // sub-delims - '*': true, // sub-delims - '+': true, // sub-delims - ',': true, // sub-delims - '-': true, // unreserved - '.': true, // unreserved - ':': true, // IPv6address + Host expression's optional port - ';': true, // sub-delims - '=': true, // sub-delims - '[': true, - '\'': true, // sub-delims - ']': true, - '_': true, // unreserved - '~': true, // unreserved -} - -// validHeaderValue reports whether v is a valid "field-value" according to -// http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2 : -// -// message-header = field-name ":" [ field-value ] -// field-value = *( field-content | LWS ) -// field-content = <the OCTETs making up the field-value -// and consisting of either *TEXT or combinations -// of token, separators, and quoted-string> -// -// http://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html#sec2.2 : -// -// TEXT = <any OCTET except CTLs, -// but including LWS> -// LWS = [CRLF] 1*( SP | HT ) -// CTL = <any US-ASCII control character -// (octets 0 - 31) and DEL (127)> -func validHeaderValue(v string) bool { - for i := 0; i < len(v); i++ { - b := v[i] - if isCTL(b) && !isLWS(b) { - return false - } - } - return true -} diff --git a/src/net/http/lex_test.go b/src/net/http/lex_test.go deleted file mode 100644 index 986fda17dc..0000000000 --- a/src/net/http/lex_test.go +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package http - -import ( - "testing" -) - -func isChar(c rune) bool { return c <= 127 } - -func isCtl(c rune) bool { return c <= 31 || c == 127 } - -func isSeparator(c rune) bool { - switch c { - case '(', ')', '<', '>', '@', ',', ';', ':', '\\', '"', '/', '[', ']', '?', '=', '{', '}', ' ', '\t': - return true - } - return false -} - -func TestIsToken(t *testing.T) { - for i := 0; i <= 130; i++ { - r := rune(i) - expected := isChar(r) && !isCtl(r) && !isSeparator(r) - if isToken(r) != expected { - t.Errorf("isToken(0x%x) = %v", r, !expected) - } - } -} - -func TestHeaderValuesContainsToken(t *testing.T) { - tests := []struct { - vals []string - token string - want bool - }{ - { - vals: []string{"foo"}, - token: "foo", - want: true, - }, - { - vals: []string{"bar", "foo"}, - token: "foo", - want: true, - }, - { - vals: []string{"foo"}, - token: "FOO", - want: true, - }, - { - vals: []string{"foo"}, - token: "bar", - want: false, - }, - { - vals: []string{" foo "}, - token: "FOO", - want: true, - }, - { - vals: []string{"foo,bar"}, - token: "FOO", - want: true, - }, - { - vals: []string{"bar,foo,bar"}, - token: "FOO", - want: true, - }, - { - vals: []string{"bar , foo"}, - token: "FOO", - want: true, - }, - { - vals: []string{"foo ,bar "}, - token: "FOO", - want: true, - }, - { - vals: []string{"bar, foo ,bar"}, - token: "FOO", - want: true, - }, - { - vals: []string{"bar , foo"}, - token: "FOO", - want: true, - }, - } - for _, tt := range tests { - got := headerValuesContainsToken(tt.vals, tt.token) - if got != tt.want { - t.Errorf("headerValuesContainsToken(%q, %q) = %v; want %v", tt.vals, tt.token, got, tt.want) - } - } -} diff --git a/src/net/http/server.go b/src/net/http/server.go index d4e38b6ad0..1a8c0fc6cc 100644 --- a/src/net/http/server.go +++ b/src/net/http/server.go @@ -27,6 +27,8 @@ import ( "sync" "sync/atomic" "time" + + "golang.org/x/net/lex/httplex" ) // Errors used by the HTTP server. @@ -783,15 +785,15 @@ func (c *conn) readRequest(ctx context.Context) (w *response, err error) { if len(hosts) > 1 { return nil, badRequestError("too many Host headers") } - if len(hosts) == 1 && !validHostHeader(hosts[0]) { + if len(hosts) == 1 && !httplex.ValidHostHeader(hosts[0]) { return nil, badRequestError("malformed Host header") } for k, vv := range req.Header { - if !validHeaderName(k) { + if !httplex.ValidHeaderFieldName(k) { return nil, badRequestError("invalid header name") } for _, v := range vv { - if !validHeaderValue(v) { + if !httplex.ValidHeaderFieldValue(v) { return nil, badRequestError("invalid header value") } } diff --git a/src/net/http/transfer.go b/src/net/http/transfer.go index 501e4be08c..b27ace638a 100644 --- a/src/net/http/transfer.go +++ b/src/net/http/transfer.go @@ -17,6 +17,8 @@ import ( "strconv" "strings" "sync" + + "golang.org/x/net/lex/httplex" ) // ErrLineTooLong is returned when reading request or response bodies @@ -561,9 +563,9 @@ func shouldClose(major, minor int, header Header, removeCloseHeader bool) bool { } conv := header["Connection"] - hasClose := headerValuesContainsToken(conv, "close") + hasClose := httplex.HeaderValuesContainsToken(conv, "close") if major == 1 && minor == 0 { - return hasClose || !headerValuesContainsToken(conv, "keep-alive") + return hasClose || !httplex.HeaderValuesContainsToken(conv, "keep-alive") } if hasClose && removeCloseHeader { diff --git a/src/net/http/transport.go b/src/net/http/transport.go index 17e6270151..777501f5bd 100644 --- a/src/net/http/transport.go +++ b/src/net/http/transport.go @@ -26,6 +26,8 @@ import ( "strings" "sync" "time" + + "golang.org/x/net/lex/httplex" ) // DefaultTransport is the default implementation of Transport and is @@ -324,11 +326,11 @@ func (t *Transport) RoundTrip(req *Request) (*Response, error) { isHTTP := scheme == "http" || scheme == "https" if isHTTP { for k, vv := range req.Header { - if !validHeaderName(k) { + if !httplex.ValidHeaderFieldName(k) { return nil, fmt.Errorf("net/http: invalid header field name %q", k) } for _, v := range vv { - if !validHeaderValue(v) { + if !httplex.ValidHeaderFieldValue(v) { return nil, fmt.Errorf("net/http: invalid header field value %q for key %v", v, k) } } |
