From 802cb5927f1e163749331c9f6cfb414cb0c753b9 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Tue, 13 Sep 2016 18:51:16 +0000 Subject: [PATCH] net/http: update bundled x/net/http2 Updates x/net/http2 (and x/net/lex/httplex) to git rev 749a502 for: http2: don't sniff first Request.Body byte in Transport until we have a conn https://golang.org/cl/29074 Fixes #17071 http2: add Transport support for unicode domain names https://golang.org/cl/29071 Updates #13835 http2: don't send bogus :path pseudo headers if Request.URL.Opaque is set https://golang.org/cl/27632 + http2: fix bug where '*' as a valid :path value in Transport https://golang.org/cl/29070 Updates #16847 http2: fix all vet warnings https://golang.org/cl/28344 Updates #16228 Updates #11041 Also uses the new -underscore flag to x/tools/cmd/bundle from https://golang.org/cl/29086 Change-Id: Ica0f6bf6e33266237e37527a166a783d78c059c4 Reviewed-on: https://go-review.googlesource.com/29110 Run-TryBot: Brad Fitzpatrick TryBot-Result: Gobot Gobot Reviewed-by: Chris Broadfoot --- src/go/build/deps_test.go | 1 + src/net/http/h2_bundle.go | 64 +++++++++++++++---- .../golang_org/x/net/lex/httplex/httplex.go | 39 +++++++++++ .../x/net/lex/httplex/httplex_test.go | 18 ++++++ 4 files changed, 110 insertions(+), 12 deletions(-) diff --git a/src/go/build/deps_test.go b/src/go/build/deps_test.go index cb8f95fb1de..b8ee601f50c 100644 --- a/src/go/build/deps_test.go +++ b/src/go/build/deps_test.go @@ -379,6 +379,7 @@ var pkgDeps = map[string][]string{ "mime/multipart", "runtime/debug", "net/http/internal", "golang_org/x/net/http2/hpack", + "golang_org/x/net/idna", "golang_org/x/net/lex/httplex", "internal/nettrace", "net/http/httptrace", diff --git a/src/net/http/h2_bundle.go b/src/net/http/h2_bundle.go index a41e3ca07f1..33b13db91f3 100644 --- a/src/net/http/h2_bundle.go +++ b/src/net/http/h2_bundle.go @@ -1,5 +1,5 @@ // Code generated by golang.org/x/tools/cmd/bundle. -//go:generate bundle -o h2_bundle.go -prefix http2 golang.org/x/net/http2 +//go:generate bundle -o h2_bundle.go -prefix http2 -underscore golang.org/x/net/http2 // Package http2 implements the HTTP/2 protocol. // @@ -43,6 +43,7 @@ import ( "time" "golang_org/x/net/http2/hpack" + "golang_org/x/net/idna" "golang_org/x/net/lex/httplex" ) @@ -2145,6 +2146,8 @@ func http2requestTrace(req *Request) *http2clientTrace { return (*http2clientTrace)(trace) } +func http2cloneTLSConfig(c *tls.Config) *tls.Config { return c.Clone() } + var http2DebugGoroutines = os.Getenv("DEBUG_HTTP2_GOROUTINES") == "1" type http2goroutineLock uint64 @@ -2686,6 +2689,19 @@ func (s *http2sorter) SortStrings(ss []string) { s.v = save } +// validPseudoPath reports whether v is a valid :path pseudo-header +// value. It must be either: +// +// *) a non-empty string starting with '/', but not with with "//", +// *) the string '*', for OPTIONS requests. +// +// For now this is only used a quick check for deciding when to clean +// up Opaque URLs before sending requests from the Transport. +// See golang.org/issue/16847 +func http2validPseudoPath(v string) bool { + return (len(v) > 0 && v[0] == '/' && (len(v) == 1 || v[1] != '/')) || v == "*" +} + // pipe is a goroutine-safe io.Reader/io.Writer pair. It's like // io.Pipe except there are no PipeReader/PipeWriter halves, and the // underlying buffer is an interface. (io.Pipe is always unbuffered) @@ -5133,14 +5149,18 @@ func (t *http2Transport) RoundTrip(req *Request) (*Response, error) { // authorityAddr returns a given authority (a host/IP, or host:port / ip:port) // and returns a host:port. The port 443 is added if needed. func http2authorityAddr(scheme string, authority string) (addr string) { - if _, _, err := net.SplitHostPort(authority); err == nil { - return authority + host, port, err := net.SplitHostPort(authority) + if err != nil { + port = "443" + if scheme == "http" { + port = "80" + } + host = authority } - port := "443" - if scheme == "http" { - port = "80" + if a, err := idna.ToASCII(host); err == nil { + host = a } - return net.JoinHostPort(authority, port) + return net.JoinHostPort(host, port) } // RoundTripOpt is like RoundTrip, but takes options. @@ -5203,7 +5223,7 @@ func (t *http2Transport) dialClientConn(addr string, singleUse bool) (*http2Clie func (t *http2Transport) newTLSConfig(host string) *tls.Config { cfg := new(tls.Config) if t.TLSClientConfig != nil { - *cfg = *t.TLSClientConfig + *cfg = *http2cloneTLSConfig(t.TLSClientConfig) } if !http2strSliceContains(cfg.NextProtos, http2NextProtoTLS) { cfg.NextProtos = append([]string{http2NextProtoTLS}, cfg.NextProtos...) @@ -5486,9 +5506,6 @@ func (cc *http2ClientConn) RoundTrip(req *Request) (*Response, error) { } hasTrailers := trailers != "" - body, contentLen := http2bodyAndLength(req) - hasBody := body != nil - cc.mu.Lock() cc.lastActive = time.Now() if cc.closed || !cc.canTakeNewRequestLocked() { @@ -5496,6 +5513,9 @@ func (cc *http2ClientConn) RoundTrip(req *Request) (*Response, error) { return nil, http2errClientConnUnusable } + body, contentLen := http2bodyAndLength(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() && @@ -5792,6 +5812,26 @@ func (cc *http2ClientConn) encodeHeaders(req *Request, addGzipHeader bool, trail if host == "" { host = req.URL.Host } + host, err := httplex.PunycodeHostPort(host) + if err != nil { + return nil, err + } + + var path string + if req.Method != "CONNECT" { + path = req.URL.RequestURI() + if !http2validPseudoPath(path) { + orig := path + path = strings.TrimPrefix(path, req.URL.Scheme+"://"+host) + if !http2validPseudoPath(path) { + if req.URL.Opaque != "" { + return nil, fmt.Errorf("invalid request :path %q from URL.Opaque = %q", orig, req.URL.Opaque) + } else { + return nil, fmt.Errorf("invalid request :path %q", orig) + } + } + } + } for k, vv := range req.Header { if !httplex.ValidHeaderFieldName(k) { @@ -5807,7 +5847,7 @@ func (cc *http2ClientConn) encodeHeaders(req *Request, addGzipHeader bool, trail cc.writeHeader(":authority", host) cc.writeHeader(":method", req.Method) if req.Method != "CONNECT" { - cc.writeHeader(":path", req.URL.RequestURI()) + cc.writeHeader(":path", path) cc.writeHeader(":scheme", "https") } if trailers != "" { diff --git a/src/vendor/golang_org/x/net/lex/httplex/httplex.go b/src/vendor/golang_org/x/net/lex/httplex/httplex.go index bd0ec24f444..b6493f045a2 100644 --- a/src/vendor/golang_org/x/net/lex/httplex/httplex.go +++ b/src/vendor/golang_org/x/net/lex/httplex/httplex.go @@ -10,8 +10,11 @@ package httplex import ( + "net" "strings" "unicode/utf8" + + "golang_org/x/net/idna" ) var isTokenTable = [127]bool{ @@ -310,3 +313,39 @@ func ValidHeaderFieldValue(v string) bool { } return true } + +func isASCII(s string) bool { + for i := 0; i < len(s); i++ { + if s[i] >= utf8.RuneSelf { + return false + } + } + return true +} + +// PunycodeHostPort returns the IDNA Punycode version +// of the provided "host" or "host:port" string. +func PunycodeHostPort(v string) (string, error) { + if isASCII(v) { + return v, nil + } + + host, port, err := net.SplitHostPort(v) + if err != nil { + // The input 'v' argument was just a "host" argument, + // without a port. This error should not be returned + // to the caller. + host = v + port = "" + } + host, err = idna.ToASCII(host) + if err != nil { + // Non-UTF-8? Not representable in Punycode, in any + // case. + return "", err + } + if port == "" { + return host, nil + } + return net.JoinHostPort(host, port), nil +} diff --git a/src/vendor/golang_org/x/net/lex/httplex/httplex_test.go b/src/vendor/golang_org/x/net/lex/httplex/httplex_test.go index c4ace1991b3..f47adc939fe 100644 --- a/src/vendor/golang_org/x/net/lex/httplex/httplex_test.go +++ b/src/vendor/golang_org/x/net/lex/httplex/httplex_test.go @@ -99,3 +99,21 @@ func TestHeaderValuesContainsToken(t *testing.T) { } } } + +func TestPunycodeHostPort(t *testing.T) { + tests := []struct { + in, want string + }{ + {"www.google.com", "www.google.com"}, + {"гофер.рф", "xn--c1ae0ajs.xn--p1ai"}, + {"bücher.de", "xn--bcher-kva.de"}, + {"bücher.de:8080", "xn--bcher-kva.de:8080"}, + {"[1::6]:8080", "[1::6]:8080"}, + } + for _, tt := range tests { + got, err := PunycodeHostPort(tt.in) + if tt.want != got || err != nil { + t.Errorf("PunycodeHostPort(%q) = %q, %v, want %q, nil", tt.in, got, err, tt.want) + } + } +}