diff options
author | Ian Lance Taylor <iant@golang.org> | 2020-04-03 15:01:34 -0700 |
---|---|---|
committer | Ian Lance Taylor <iant@golang.org> | 2020-04-03 15:01:34 -0700 |
commit | 00eb71c43c74cc5143b60d470450c3981037ed3c (patch) | |
tree | 66833e46153e7869903229adb39ebb1a86c86169 /libgo/go/net | |
parent | 213caedb0104ed919b67b3446a53f06054d62fec (diff) | |
parent | ff229375721d1763a18ec76403aa1215b2932fb3 (diff) | |
download | gcc-00eb71c43c74cc5143b60d470450c3981037ed3c.zip gcc-00eb71c43c74cc5143b60d470450c3981037ed3c.tar.gz gcc-00eb71c43c74cc5143b60d470450c3981037ed3c.tar.bz2 |
Merge from trunk revision ff229375721d1763a18ec76403aa1215b2932fb3
Diffstat (limited to 'libgo/go/net')
-rw-r--r-- | libgo/go/net/dial_test.go | 2 | ||||
-rw-r--r-- | libgo/go/net/dnsclient_unix_test.go | 2 | ||||
-rw-r--r-- | libgo/go/net/http/cgi/integration_test.go (renamed from libgo/go/net/http/cgi/matryoshka_test.go) | 0 | ||||
-rw-r--r-- | libgo/go/net/http/client.go | 17 | ||||
-rw-r--r-- | libgo/go/net/http/httputil/reverseproxy.go | 8 | ||||
-rw-r--r-- | libgo/go/net/http/omithttp2.go | 4 | ||||
-rw-r--r-- | libgo/go/net/http/request.go | 12 | ||||
-rw-r--r-- | libgo/go/net/http/transfer.go | 125 | ||||
-rw-r--r-- | libgo/go/net/http/transfer_test.go | 284 | ||||
-rw-r--r-- | libgo/go/net/http/transport.go | 27 | ||||
-rw-r--r-- | libgo/go/net/http/transport_test.go | 70 | ||||
-rw-r--r-- | libgo/go/net/lookup_test.go | 13 | ||||
-rw-r--r-- | libgo/go/net/net.go | 1 |
13 files changed, 143 insertions, 422 deletions
diff --git a/libgo/go/net/dial_test.go b/libgo/go/net/dial_test.go index ae40079..493cdfc 100644 --- a/libgo/go/net/dial_test.go +++ b/libgo/go/net/dial_test.go @@ -174,7 +174,7 @@ func dialClosedPort(t *testing.T) (actual, expected time.Duration) { } addr := l.Addr().String() l.Close() - // On OpenBSD, interference from TestSelfConnect is mysteriously + // On OpenBSD, interference from TestTCPSelfConnect is mysteriously // causing the first attempt to hang for a few seconds, so we throw // away the first result and keep the second. for i := 1; ; i++ { diff --git a/libgo/go/net/dnsclient_unix_test.go b/libgo/go/net/dnsclient_unix_test.go index 6d72817..e8f81e8 100644 --- a/libgo/go/net/dnsclient_unix_test.go +++ b/libgo/go/net/dnsclient_unix_test.go @@ -173,7 +173,7 @@ func TestAvoidDNSName(t *testing.T) { // Without stuff before onion/local, they're fine to // use DNS. With a search path, - // "onion.vegegtables.com" can use DNS. Without a + // "onion.vegetables.com" can use DNS. Without a // search path (or with a trailing dot), the queries // are just kinda useless, but don't reveal anything // private. diff --git a/libgo/go/net/http/cgi/matryoshka_test.go b/libgo/go/net/http/cgi/integration_test.go index 32d59c0..32d59c0 100644 --- a/libgo/go/net/http/cgi/matryoshka_test.go +++ b/libgo/go/net/http/cgi/integration_test.go diff --git a/libgo/go/net/http/client.go b/libgo/go/net/http/client.go index 6a8c59a..a496f1c 100644 --- a/libgo/go/net/http/client.go +++ b/libgo/go/net/http/client.go @@ -288,10 +288,17 @@ func timeBeforeContextDeadline(t time.Time, ctx context.Context) bool { // knownRoundTripperImpl reports whether rt is a RoundTripper that's // maintained by the Go team and known to implement the latest -// optional semantics (notably contexts). -func knownRoundTripperImpl(rt RoundTripper) bool { - switch rt.(type) { - case *Transport, *http2Transport: +// optional semantics (notably contexts). The Request is used +// to check whether this particular request is using an alternate protocol, +// in which case we need to check the RoundTripper for that protocol. +func knownRoundTripperImpl(rt RoundTripper, req *Request) bool { + switch t := rt.(type) { + case *Transport: + if altRT := t.alternateRoundTripper(req); altRT != nil { + return knownRoundTripperImpl(altRT, req) + } + return true + case *http2Transport, http2noDialH2RoundTripper: return true } // There's a very minor chance of a false positive with this. @@ -319,7 +326,7 @@ func setRequestCancel(req *Request, rt RoundTripper, deadline time.Time) (stopTi if deadline.IsZero() { return nop, alwaysFalse } - knownTransport := knownRoundTripperImpl(rt) + knownTransport := knownRoundTripperImpl(rt, req) oldCtx := req.Context() if req.Cancel == nil && knownTransport { diff --git a/libgo/go/net/http/httputil/reverseproxy.go b/libgo/go/net/http/httputil/reverseproxy.go index e8f7df2..4d6a085 100644 --- a/libgo/go/net/http/httputil/reverseproxy.go +++ b/libgo/go/net/http/httputil/reverseproxy.go @@ -24,6 +24,14 @@ import ( // ReverseProxy is an HTTP Handler that takes an incoming request and // sends it to another server, proxying the response back to the // client. +// +// ReverseProxy automatically sets the client IP as the value of the +// X-Forwarded-For header. +// If an X-Forwarded-For header already exists, the client IP is +// appended to the existing values. +// To prevent IP spoofing, be sure to delete any pre-existing +// X-Forwarded-For header coming from the client or +// an untrusted proxy. type ReverseProxy struct { // Director must be a function which modifies // the request into a new request to be sent diff --git a/libgo/go/net/http/omithttp2.go b/libgo/go/net/http/omithttp2.go index a0b33e9..307d93a 100644 --- a/libgo/go/net/http/omithttp2.go +++ b/libgo/go/net/http/omithttp2.go @@ -36,6 +36,10 @@ type http2erringRoundTripper struct{} func (http2erringRoundTripper) RoundTrip(*Request) (*Response, error) { panic(noHTTP2) } +type http2noDialH2RoundTripper struct{} + +func (http2noDialH2RoundTripper) RoundTrip(*Request) (*Response, error) { panic(noHTTP2) } + type http2noDialClientConnPool struct { http2clientConnPool http2clientConnPool } diff --git a/libgo/go/net/http/request.go b/libgo/go/net/http/request.go index 8dd9fe1..88fa093 100644 --- a/libgo/go/net/http/request.go +++ b/libgo/go/net/http/request.go @@ -1223,17 +1223,17 @@ func parsePostForm(r *Request) (vs url.Values, err error) { // For all requests, ParseForm parses the raw query from the URL and updates // r.Form. // -// For POST, PUT, and PATCH requests, it also parses the request body as a form -// and puts the results into both r.PostForm and r.Form. Request body parameters -// take precedence over URL query string values in r.Form. +// For POST, PUT, and PATCH requests, it also reads the request body, parses it +// as a form and puts the results into both r.PostForm and r.Form. Request body +// parameters take precedence over URL query string values in r.Form. +// +// If the request Body's size has not already been limited by MaxBytesReader, +// the size is capped at 10MB. // // For other HTTP methods, or when the Content-Type is not // application/x-www-form-urlencoded, the request Body is not read, and // r.PostForm is initialized to a non-nil, empty value. // -// If the request Body's size has not already been limited by MaxBytesReader, -// the size is capped at 10MB. -// // ParseMultipartForm calls ParseForm automatically. // ParseForm is idempotent. func (r *Request) ParseForm() error { diff --git a/libgo/go/net/http/transfer.go b/libgo/go/net/http/transfer.go index 1d6a987..2e01a07 100644 --- a/libgo/go/net/http/transfer.go +++ b/libgo/go/net/http/transfer.go @@ -7,7 +7,6 @@ package http import ( "bufio" "bytes" - "compress/gzip" "errors" "fmt" "io" @@ -467,34 +466,6 @@ func suppressedHeaders(status int) []string { return nil } -// proxyingReadCloser is a composite type that accepts and proxies -// io.Read and io.Close calls to its respective Reader and Closer. -// -// It is composed of: -// a) a top-level reader e.g. the result of decompression -// b) a symbolic Closer e.g. the result of decompression, the -// original body and the connection itself. -type proxyingReadCloser struct { - io.Reader - io.Closer -} - -// multiCloser implements io.Closer and allows a bunch of io.Closer values -// to all be closed once. -// Example usage is with proxyingReadCloser if we are decompressing a response -// body on the fly and would like to close both *gzip.Reader and underlying body. -type multiCloser []io.Closer - -func (mc multiCloser) Close() error { - var err error - for _, c := range mc { - if err1 := c.Close(); err1 != nil && err == nil { - err = err1 - } - } - return err -} - // msg is *Request or *Response. func readTransfer(msg interface{}, r *bufio.Reader) (err error) { t := &transferReader{RequestMethod: "GET"} @@ -572,7 +543,7 @@ func readTransfer(msg interface{}, r *bufio.Reader) (err error) { // Prepare body reader. ContentLength < 0 means chunked encoding // or close connection when finished, since multipart is not supported yet switch { - case chunked(t.TransferEncoding) || implicitlyChunked(t.TransferEncoding): + case chunked(t.TransferEncoding): if noResponseBodyExpected(t.RequestMethod) || !bodyAllowedForStatus(t.StatusCode) { t.Body = NoBody } else { @@ -593,21 +564,6 @@ func readTransfer(msg interface{}, r *bufio.Reader) (err error) { } } - // Finally if "gzip" was one of the requested transfer-encodings, - // we'll unzip the concatenated body/payload of the request. - // TODO: As we support more transfer-encodings, extract - // this code and apply the un-codings in reverse. - if t.Body != NoBody && gzipped(t.TransferEncoding) { - zr, err := gzip.NewReader(t.Body) - if err != nil { - return fmt.Errorf("http: failed to gunzip body: %v", err) - } - t.Body = &proxyingReadCloser{ - Reader: zr, - Closer: multiCloser{zr, t.Body}, - } - } - // Unify output switch rr := msg.(type) { case *Request: @@ -627,41 +583,8 @@ func readTransfer(msg interface{}, r *bufio.Reader) (err error) { return nil } -// Checks whether chunked is the last part of the encodings stack -func chunked(te []string) bool { return len(te) > 0 && te[len(te)-1] == "chunked" } - -// implicitlyChunked is a helper to check for implicity of chunked, because -// RFC 7230 Section 3.3.1 says that the sender MUST apply chunked as the final -// payload body to ensure that the message is framed for both the request -// and the body. Since "identity" is incompatible with any other transformational -// encoding cannot co-exist, the presence of "identity" will cause implicitlyChunked -// to return false. -func implicitlyChunked(te []string) bool { - if len(te) == 0 { // No transfer-encodings passed in, so not implicitly chunked. - return false - } - for _, tei := range te { - if tei == "identity" { - return false - } - } - return true -} - -func isGzipTransferEncoding(tei string) bool { - // RFC 7230 4.2.3 requests that "x-gzip" SHOULD be considered the same as "gzip". - return tei == "gzip" || tei == "x-gzip" -} - -// Checks where either of "gzip" or "x-gzip" are contained in transfer encodings. -func gzipped(te []string) bool { - for _, tei := range te { - if isGzipTransferEncoding(tei) { - return true - } - } - return false -} +// Checks whether chunked is part of the encodings stack +func chunked(te []string) bool { return len(te) > 0 && te[0] == "chunked" } // Checks whether the encoding is explicitly "identity". func isIdentity(te []string) bool { return len(te) == 1 && te[0] == "identity" } @@ -697,47 +620,25 @@ func (t *transferReader) fixTransferEncoding() error { encodings := strings.Split(raw[0], ",") te := make([]string, 0, len(encodings)) - - // When adding new encodings, please maintain the invariant: - // if chunked encoding is present, it must always - // come last and it must be applied only once. - // See RFC 7230 Section 3.3.1 Transfer-Encoding. - for i, encoding := range encodings { + // TODO: Even though we only support "identity" and "chunked" + // encodings, the loop below is designed with foresight. One + // invariant that must be maintained is that, if present, + // chunked encoding must always come first. + for _, encoding := range encodings { encoding = strings.ToLower(strings.TrimSpace(encoding)) - + // "identity" encoding is not recorded if encoding == "identity" { - // "identity" should not be mixed with other transfer-encodings/compressions - // because it means "no compression, no transformation". - if len(encodings) != 1 { - return &badStringError{`"identity" when present must be the only transfer encoding`, strings.Join(encodings, ",")} - } - // "identity" is not recorded. break } - - switch { - case encoding == "chunked": - // "chunked" MUST ALWAYS be the last - // encoding as per the loop invariant. - // That is: - // Invalid: [chunked, gzip] - // Valid: [gzip, chunked] - if i+1 != len(encodings) { - return &badStringError{"chunked must be applied only once, as the last encoding", strings.Join(encodings, ",")} - } - // Supported otherwise. - - case isGzipTransferEncoding(encoding): - // Supported - - default: + if encoding != "chunked" { return &unsupportedTEError{fmt.Sprintf("unsupported transfer encoding: %q", encoding)} } - te = te[0 : len(te)+1] te[len(te)-1] = encoding } - + if len(te) > 1 { + return &badStringError{"too many transfer encodings", strings.Join(te, ",")} + } if len(te) > 0 { // RFC 7230 3.3.2 says "A sender MUST NOT send a // Content-Length header field in any message that diff --git a/libgo/go/net/http/transfer_test.go b/libgo/go/net/http/transfer_test.go index a8ce2d3..65009ee 100644 --- a/libgo/go/net/http/transfer_test.go +++ b/libgo/go/net/http/transfer_test.go @@ -7,7 +7,6 @@ package http import ( "bufio" "bytes" - "compress/gzip" "crypto/rand" "fmt" "io" @@ -62,6 +61,7 @@ func TestFinalChunkedBodyReadEOF(t *testing.T) { buf := make([]byte, len(want)) n, err := res.Body.Read(buf) if n != len(want) || err != io.EOF { + t.Logf("body = %#v", res.Body) t.Errorf("Read = %v, %v; want %d, EOF", n, err, len(want)) } if string(buf) != want { @@ -290,7 +290,7 @@ func TestFixTransferEncoding(t *testing.T) { }, { hdr: Header{"Transfer-Encoding": {"chunked, chunked", "identity", "chunked"}}, - wantErr: &badStringError{"chunked must be applied only once, as the last encoding", "chunked, chunked"}, + wantErr: &badStringError{"too many transfer encodings", "chunked,chunked"}, }, { hdr: Header{"Transfer-Encoding": {"chunked"}}, @@ -310,283 +310,3 @@ func TestFixTransferEncoding(t *testing.T) { } } } - -func gzipIt(s string) string { - buf := new(bytes.Buffer) - gw := gzip.NewWriter(buf) - gw.Write([]byte(s)) - gw.Close() - return buf.String() -} - -func TestUnitTestProxyingReadCloserClosesBody(t *testing.T) { - var checker closeChecker - buf := new(bytes.Buffer) - buf.WriteString("Hello, Gophers!") - prc := &proxyingReadCloser{ - Reader: buf, - Closer: &checker, - } - prc.Close() - - read, err := ioutil.ReadAll(prc) - if err != nil { - t.Fatalf("Read error: %v", err) - } - if g, w := string(read), "Hello, Gophers!"; g != w { - t.Errorf("Read mismatch: got %q want %q", g, w) - } - - if checker.closed != true { - t.Fatal("closeChecker.Close was never invoked") - } -} - -func TestGzipTransferEncoding_request(t *testing.T) { - helloWorldGzipped := gzipIt("Hello, World!") - - tests := []struct { - payload string - wantErr string - wantBody string - }{ - - { - // The case of "chunked" properly applied as the last encoding - // and a gzipped request payload that is streamed in 3 parts. - payload: `POST / HTTP/1.1 -Host: golang.org -Transfer-Encoding: gzip, chunked -Content-Type: text/html; charset=UTF-8 - -` + fmt.Sprintf("%02x\r\n%s\r\n%02x\r\n%s\r\n%02x\r\n%s\r\n0\r\n\r\n", - 3, helloWorldGzipped[:3], - 5, helloWorldGzipped[3:8], - len(helloWorldGzipped)-8, helloWorldGzipped[8:]), - wantBody: `Hello, World!`, - }, - - { - // The request specifies "Transfer-Encoding: chunked" so its body must be left untouched. - payload: `PUT / HTTP/1.1 -Host: golang.org -Transfer-Encoding: chunked -Connection: close -Content-Type: text/html; charset=UTF-8 - -` + fmt.Sprintf("%0x\r\n%s\r\n0\r\n\r\n", len(helloWorldGzipped), helloWorldGzipped), - // We want that payload as it was sent. - wantBody: helloWorldGzipped, - }, - - { - // Valid request, the body doesn't have "Transfer-Encoding: chunked" but implicitly encoded - // for chunking as per the advisory from RFC 7230 3.3.1 which advises for cases where. - payload: `POST / HTTP/1.1 -Host: localhost -Transfer-Encoding: gzip -Content-Type: text/html; charset=UTF-8 - -` + fmt.Sprintf("%0x\r\n%s\r\n0\r\n\r\n", len(helloWorldGzipped), helloWorldGzipped), - wantBody: `Hello, World!`, - }, - - { - // Invalid request, the body isn't chunked nor is the connection terminated immediately - // hence invalid as per the advisory from RFC 7230 3.3.1 which advises for cases where - // a Transfer-Encoding that isn't finally chunked is provided. - payload: `PUT / HTTP/1.1 -Host: golang.org -Transfer-Encoding: gzip -Content-Length: 0 -Connection: close -Content-Type: text/html; charset=UTF-8 - -`, - wantErr: `EOF`, - }, - - { - // The case of chunked applied before another encoding. - payload: `PUT / HTTP/1.1 -Location: golang.org -Transfer-Encoding: chunked, gzip -Content-Length: 0 -Connection: close -Content-Type: text/html; charset=UTF-8 - -`, - wantErr: `chunked must be applied only once, as the last encoding "chunked, gzip"`, - }, - - { - // The case of chunked properly applied as the - // last encoding BUT with a bad "Content-Length". - payload: `POST / HTTP/1.1 -Host: golang.org -Transfer-Encoding: gzip, chunked -Content-Length: 10 -Connection: close -Content-Type: text/html; charset=UTF-8 - -` + "0\r\n\r\n", - wantErr: "EOF", - }, - } - - for i, tt := range tests { - req, err := ReadRequest(bufio.NewReader(strings.NewReader(tt.payload))) - if tt.wantErr != "" { - if err == nil || !strings.Contains(err.Error(), tt.wantErr) { - t.Errorf("test %d. Error mismatch\nGot: %v\nWant: %s", i, err, tt.wantErr) - } - continue - } - - if err != nil { - t.Errorf("test %d. Unexpected ReadRequest error: %v\nPayload:\n%s", i, err, tt.payload) - continue - } - - got, err := ioutil.ReadAll(req.Body) - req.Body.Close() - if err != nil { - t.Errorf("test %d. Failed to read response body: %v", i, err) - } - if g, w := string(got), tt.wantBody; g != w { - t.Errorf("test %d. Request body mimsatch\nGot:\n%s\n\nWant:\n%s", i, g, w) - } - } -} - -func TestGzipTransferEncoding_response(t *testing.T) { - helloWorldGzipped := gzipIt("Hello, World!") - - tests := []struct { - payload string - wantErr string - wantBody string - }{ - - { - // The case of "chunked" properly applied as the last encoding - // and a gzipped payload that is streamed in 3 parts. - payload: `HTTP/1.1 302 Found -Location: https://golang.org/ -Transfer-Encoding: gzip, chunked -Connection: close -Content-Type: text/html; charset=UTF-8 - -` + fmt.Sprintf("%02x\r\n%s\r\n%02x\r\n%s\r\n%02x\r\n%s\r\n0\r\n\r\n", - 3, helloWorldGzipped[:3], - 5, helloWorldGzipped[3:8], - len(helloWorldGzipped)-8, helloWorldGzipped[8:]), - wantBody: `Hello, World!`, - }, - - { - // The response specifies "Transfer-Encoding: chunked" so response body must be left untouched. - payload: `HTTP/1.1 302 Found -Location: https://golang.org/ -Transfer-Encoding: chunked -Connection: close -Content-Type: text/html; charset=UTF-8 - -` + fmt.Sprintf("%0x\r\n%s\r\n0\r\n\r\n", len(helloWorldGzipped), helloWorldGzipped), - // We want that payload as it was sent. - wantBody: helloWorldGzipped, - }, - - { - // Valid response, the body doesn't have "Transfer-Encoding: chunked" but implicitly encoded - // for chunking as per the advisory from RFC 7230 3.3.1 which advises for cases where. - payload: `HTTP/1.1 302 Found -Location: https://golang.org/ -Transfer-Encoding: gzip -Connection: close -Content-Type: text/html; charset=UTF-8 - -` + fmt.Sprintf("%0x\r\n%s\r\n0\r\n\r\n", len(helloWorldGzipped), helloWorldGzipped), - wantBody: `Hello, World!`, - }, - - { - // Invalid response, the body isn't chunked nor is the connection terminated immediately - // hence invalid as per the advisory from RFC 7230 3.3.1 which advises for cases where - // a Transfer-Encoding that isn't finally chunked is provided. - payload: `HTTP/1.1 302 Found -Location: https://golang.org/ -Transfer-Encoding: gzip -Content-Length: 0 -Connection: close -Content-Type: text/html; charset=UTF-8 - -`, - wantErr: `EOF`, - }, - - { - // The case of chunked applied before another encoding. - payload: `HTTP/1.1 302 Found -Location: https://golang.org/ -Transfer-Encoding: chunked, gzip -Content-Length: 0 -Connection: close -Content-Type: text/html; charset=UTF-8 - -`, - wantErr: `chunked must be applied only once, as the last encoding "chunked, gzip"`, - }, - - { - // The case of chunked properly applied as the - // last encoding BUT with a bad "Content-Length". - payload: `HTTP/1.1 302 Found -Location: https://golang.org/ -Transfer-Encoding: gzip, chunked -Content-Length: 10 -Connection: close -Content-Type: text/html; charset=UTF-8 - -` + "0\r\n\r\n", - wantErr: "EOF", - }, - - { - // Including "identity" more than once. - payload: `HTTP/1.1 200 OK -Location: https://golang.org/ -Transfer-Encoding: identity, identity -Content-Length: 0 -Connection: close -Content-Type: text/html; charset=UTF-8 - -` + "0\r\n\r\n", - wantErr: `"identity" when present must be the only transfer encoding "identity, identity"`, - }, - } - - for i, tt := range tests { - res, err := ReadResponse(bufio.NewReader(strings.NewReader(tt.payload)), nil) - if tt.wantErr != "" { - if err == nil || !strings.Contains(err.Error(), tt.wantErr) { - t.Errorf("test %d. Error mismatch\nGot: %v\nWant: %s", i, err, tt.wantErr) - } - continue - } - - if err != nil { - t.Errorf("test %d. Unexpected ReadResponse error: %v\nPayload:\n%s", i, err, tt.payload) - continue - } - - got, err := ioutil.ReadAll(res.Body) - res.Body.Close() - if err != nil { - t.Errorf("test %d. Failed to read response body: %v", i, err) - } - if g, w := string(got), tt.wantBody; g != w { - t.Errorf("test %d. Response body mimsatch\nGot:\n%s\n\nWant:\n%s", i, g, w) - } - } -} diff --git a/libgo/go/net/http/transport.go b/libgo/go/net/http/transport.go index 64d8510..d0bfdb4 100644 --- a/libgo/go/net/http/transport.go +++ b/libgo/go/net/http/transport.go @@ -469,6 +469,17 @@ func (t *Transport) useRegisteredProtocol(req *Request) bool { return true } +// alternateRoundTripper returns the alternate RoundTripper to use +// for this request if the Request's URL scheme requires one, +// or nil for the normal case of using the Transport. +func (t *Transport) alternateRoundTripper(req *Request) RoundTripper { + if !t.useRegisteredProtocol(req) { + return nil + } + altProto, _ := t.altProto.Load().(map[string]RoundTripper) + return altProto[req.URL.Scheme] +} + // roundTrip implements a RoundTripper over HTTP. func (t *Transport) roundTrip(req *Request) (*Response, error) { t.nextProtoOnce.Do(t.onceSetNextProtoDefaults) @@ -500,12 +511,9 @@ func (t *Transport) roundTrip(req *Request) (*Response, error) { } } - if t.useRegisteredProtocol(req) { - altProto, _ := t.altProto.Load().(map[string]RoundTripper) - if altRT := altProto[scheme]; altRT != nil { - if resp, err := altRT.RoundTrip(req); err != ErrSkipAltProtocol { - return resp, err - } + if altRT := t.alternateRoundTripper(req); altRT != nil { + if resp, err := altRT.RoundTrip(req); err != ErrSkipAltProtocol { + return resp, err } } if !isHTTP { @@ -1559,15 +1567,16 @@ func (t *Transport) dialConn(ctx context.Context, cm connectMethod) (pconn *pers if hdr == nil { hdr = make(Header) } + if pa := cm.proxyAuth(); pa != "" { + hdr = hdr.Clone() + hdr.Set("Proxy-Authorization", pa) + } connectReq := &Request{ Method: "CONNECT", URL: &url.URL{Opaque: cm.targetAddr}, Host: cm.targetAddr, Header: hdr, } - if pa := cm.proxyAuth(); pa != "" { - connectReq.Header.Set("Proxy-Authorization", pa) - } // If there's no done channel (no deadline or cancellation // from the caller possible), at least set some (long) diff --git a/libgo/go/net/http/transport_test.go b/libgo/go/net/http/transport_test.go index 2256813..1e0334d 100644 --- a/libgo/go/net/http/transport_test.go +++ b/libgo/go/net/http/transport_test.go @@ -1550,6 +1550,44 @@ func TestTransportDialPreservesNetOpProxyError(t *testing.T) { } } +// Issue 36431: calls to RoundTrip should not mutate t.ProxyConnectHeader. +// +// (A bug caused dialConn to instead write the per-request Proxy-Authorization +// header through to the shared Header instance, introducing a data race.) +func TestTransportProxyDialDoesNotMutateProxyConnectHeader(t *testing.T) { + setParallel(t) + defer afterTest(t) + + proxy := httptest.NewTLSServer(NotFoundHandler()) + defer proxy.Close() + c := proxy.Client() + + tr := c.Transport.(*Transport) + tr.Proxy = func(*Request) (*url.URL, error) { + u, _ := url.Parse(proxy.URL) + u.User = url.UserPassword("aladdin", "opensesame") + return u, nil + } + h := tr.ProxyConnectHeader + if h == nil { + h = make(Header) + } + tr.ProxyConnectHeader = h.Clone() + + req, err := NewRequest("GET", "https://golang.fake.tld/", nil) + if err != nil { + t.Fatal(err) + } + _, err = c.Do(req) + if err == nil { + t.Errorf("unexpected Get success") + } + + if !reflect.DeepEqual(tr.ProxyConnectHeader, h) { + t.Errorf("tr.ProxyConnectHeader = %v; want %v", tr.ProxyConnectHeader, h) + } +} + // TestTransportGzipRecursive sends a gzip quine and checks that the // client gets the same value back. This is more cute than anything, // but checks that we don't recurse forever, and checks that @@ -6109,3 +6147,35 @@ func TestTransportDecrementConnWhenIdleConnRemoved(t *testing.T) { t.Errorf("error occurred: %v", err) } } + +// Issue 36820 +// Test that we use the older backward compatible cancellation protocol +// when a RoundTripper is registered via RegisterProtocol. +func TestAltProtoCancellation(t *testing.T) { + defer afterTest(t) + tr := &Transport{} + c := &Client{ + Transport: tr, + Timeout: time.Millisecond, + } + tr.RegisterProtocol("timeout", timeoutProto{}) + _, err := c.Get("timeout://bar.com/path") + if err == nil { + t.Error("request unexpectedly succeeded") + } else if !strings.Contains(err.Error(), timeoutProtoErr.Error()) { + t.Errorf("got error %q, does not contain expected string %q", err, timeoutProtoErr) + } +} + +var timeoutProtoErr = errors.New("canceled as expected") + +type timeoutProto struct{} + +func (timeoutProto) RoundTrip(req *Request) (*Response, error) { + select { + case <-req.Cancel: + return nil, timeoutProtoErr + case <-time.After(5 * time.Second): + return nil, errors.New("request was not canceled") + } +} diff --git a/libgo/go/net/lookup_test.go b/libgo/go/net/lookup_test.go index 8a41510..2bc5592 100644 --- a/libgo/go/net/lookup_test.go +++ b/libgo/go/net/lookup_test.go @@ -998,12 +998,16 @@ func TestConcurrentPreferGoResolversDial(t *testing.T) { defer wg.Done() _, err := r.LookupIPAddr(context.Background(), "google.com") if err != nil { - t.Fatalf("lookup failed for resolver %d: %q", index, err) + t.Errorf("lookup failed for resolver %d: %q", index, err) } }(resolver.Resolver, i) } wg.Wait() + if t.Failed() { + t.FailNow() + } + for i, resolver := range resolvers { if !resolver.dialed { t.Errorf("custom resolver %d not dialed during lookup", i) @@ -1175,12 +1179,9 @@ func TestWithUnexpiredValuesPreserved(t *testing.T) { } } -// Issue 31586: don't crash on null byte in name +// Issue 31597: don't panic on null byte in name func TestLookupNullByte(t *testing.T) { testenv.MustHaveExternalNetwork(t) testenv.SkipFlakyNet(t) - _, err := LookupHost("foo\x00bar") // used to crash on Windows - if err == nil { - t.Errorf("unexpected success") - } + LookupHost("foo\x00bar") // check that it doesn't panic; it used to on Windows } diff --git a/libgo/go/net/net.go b/libgo/go/net/net.go index 38c6b99..1d7e5e7 100644 --- a/libgo/go/net/net.go +++ b/libgo/go/net/net.go @@ -452,6 +452,7 @@ type OpError struct { Addr Addr // Err is the error that occurred during the operation. + // The Error method panics if the error is nil. Err error } |