From 1b61eb0b3360ef904f841825492bd637189721cd Mon Sep 17 00:00:00 2001 From: swithek Date: Tue, 22 Sep 2020 13:55:08 +0300 Subject: [PATCH 1/7] Copy http client --- dial.go | 14 +++++++++----- dial_test.go | 9 --------- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/dial.go b/dial.go index 2b25e351..35516943 100644 --- a/dial.go +++ b/dial.go @@ -8,7 +8,6 @@ import ( "context" "crypto/rand" "encoding/base64" - "errors" "fmt" "io" "io/ioutil" @@ -26,6 +25,7 @@ type DialOptions struct { // HTTPClient is used for the connection. // Its Transport must return writable bodies for WebSocket handshakes. // http.Transport does beginning with Go 1.12. + // Non-zero timeout will be ignored, see https://github.com/nhooyr/websocket/issues/67. HTTPClient *http.Client // HTTPHeader specifies the HTTP headers included in the handshake request. @@ -74,7 +74,15 @@ func dial(ctx context.Context, urls string, opts *DialOptions, rand io.Reader) ( opts = &*opts if opts.HTTPClient == nil { opts.HTTPClient = http.DefaultClient + } else if opts.HTTPClient.Timeout > 0 { + // remove timeout + opts.HTTPClient = &http.Client{ + Transport: opts.HTTPClient.Transport, + CheckRedirect: opts.HTTPClient.CheckRedirect, + Jar: opts.HTTPClient.Jar, + } } + if opts.HTTPHeader == nil { opts.HTTPHeader = http.Header{} } @@ -133,10 +141,6 @@ func dial(ctx context.Context, urls string, opts *DialOptions, rand io.Reader) ( } func handshakeRequest(ctx context.Context, urls string, opts *DialOptions, copts *compressionOptions, secWebSocketKey string) (*http.Response, error) { - if opts.HTTPClient.Timeout > 0 { - return nil, errors.New("use context for cancellation instead of http.Client.Timeout; see https://github.com/nhooyr/websocket/issues/67") - } - u, err := url.Parse(urls) if err != nil { return nil, fmt.Errorf("failed to parse url: %w", err) diff --git a/dial_test.go b/dial_test.go index 7f13a934..28c255c6 100644 --- a/dial_test.go +++ b/dial_test.go @@ -36,15 +36,6 @@ func TestBadDials(t *testing.T) { name: "badURLScheme", url: "ftp://nhooyr.io", }, - { - name: "badHTTPClient", - url: "ws://nhooyr.io", - opts: &DialOptions{ - HTTPClient: &http.Client{ - Timeout: time.Minute, - }, - }, - }, { name: "badTLS", url: "wss://totallyfake.nhooyr.io", From dbaf6f8f37bc74b7176f9cada3cbf454e2fcf148 Mon Sep 17 00:00:00 2001 From: swithek Date: Thu, 24 Sep 2020 10:33:22 +0300 Subject: [PATCH 2/7] Create context with http client timeout --- dial.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/dial.go b/dial.go index 35516943..849515e8 100644 --- a/dial.go +++ b/dial.go @@ -25,7 +25,6 @@ type DialOptions struct { // HTTPClient is used for the connection. // Its Transport must return writable bodies for WebSocket handshakes. // http.Transport does beginning with Go 1.12. - // Non-zero timeout will be ignored, see https://github.com/nhooyr/websocket/issues/67. HTTPClient *http.Client // HTTPHeader specifies the HTTP headers included in the handshake request. @@ -75,7 +74,11 @@ func dial(ctx context.Context, urls string, opts *DialOptions, rand io.Reader) ( if opts.HTTPClient == nil { opts.HTTPClient = http.DefaultClient } else if opts.HTTPClient.Timeout > 0 { - // remove timeout + var cancel context.CancelFunc + + ctx, cancel = context.WithTimeout(ctx, opts.HTTPClient.Timeout) + defer cancel() + opts.HTTPClient = &http.Client{ Transport: opts.HTTPClient.Transport, CheckRedirect: opts.HTTPClient.CheckRedirect, From f67b03bef8172d988704e0a9fc15bd6415b3940b Mon Sep 17 00:00:00 2001 From: swithek Date: Fri, 25 Sep 2020 09:48:19 +0300 Subject: [PATCH 3/7] Improve http client copying --- dial.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/dial.go b/dial.go index 849515e8..509882e0 100644 --- a/dial.go +++ b/dial.go @@ -79,11 +79,9 @@ func dial(ctx context.Context, urls string, opts *DialOptions, rand io.Reader) ( ctx, cancel = context.WithTimeout(ctx, opts.HTTPClient.Timeout) defer cancel() - opts.HTTPClient = &http.Client{ - Transport: opts.HTTPClient.Transport, - CheckRedirect: opts.HTTPClient.CheckRedirect, - Jar: opts.HTTPClient.Jar, - } + newClient := *opts.HTTPClient + newClient.Timeout = 0 + opts.HTTPClient = &newClient } if opts.HTTPHeader == nil { From c9f314abd11b749d43bb61fd214171f8bb4e4173 Mon Sep 17 00:00:00 2001 From: James <5511220+Zamiell@users.noreply.github.com> Date: Thu, 26 Nov 2020 17:52:31 -0500 Subject: [PATCH 4/7] fmt: Remove unnecessary lines --- examples/echo/server.go | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/echo/server.go b/examples/echo/server.go index 308c4a5e..e9f70f03 100644 --- a/examples/echo/server.go +++ b/examples/echo/server.go @@ -16,7 +16,6 @@ import ( // It ensures the client speaks the echo subprotocol and // only allows one message every 100ms with a 10 message burst. type echoServer struct { - // logf controls where logs are sent. logf func(f string, v ...interface{}) } From e4c3b0f8168d619c279822ab882b8f15717041af Mon Sep 17 00:00:00 2001 From: Egor Gorbunov Date: Wed, 23 Dec 2020 16:30:26 +0300 Subject: [PATCH 5/7] Do not lower header tokens in headerTokens() (#273) HTTP header values, as opposed to header keys, are case sensitive, but implementation of headerTokens() before this patch would return lowered values always. This old behavior could lead to chromium (v87) WebSocket rejecting connnection because negotiated subprotocol, returned in Sec-WebSocket-Protocol header (lowered be headerToken() function) would not match one sent by client, in case client specified value with capital letters. --- accept.go | 11 ++++------- accept_test.go | 6 ++++++ dial.go | 4 ++-- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/accept.go b/accept.go index 66379b5d..18536bdb 100644 --- a/accept.go +++ b/accept.go @@ -159,13 +159,13 @@ func verifyClientRequest(w http.ResponseWriter, r *http.Request) (errCode int, _ return http.StatusUpgradeRequired, fmt.Errorf("WebSocket protocol violation: handshake request must be at least HTTP/1.1: %q", r.Proto) } - if !headerContainsToken(r.Header, "Connection", "Upgrade") { + if !headerContainsTokenIgnoreCase(r.Header, "Connection", "Upgrade") { w.Header().Set("Connection", "Upgrade") w.Header().Set("Upgrade", "websocket") return http.StatusUpgradeRequired, fmt.Errorf("WebSocket protocol violation: Connection header %q does not contain Upgrade", r.Header.Get("Connection")) } - if !headerContainsToken(r.Header, "Upgrade", "websocket") { + if !headerContainsTokenIgnoreCase(r.Header, "Upgrade", "websocket") { w.Header().Set("Connection", "Upgrade") w.Header().Set("Upgrade", "websocket") return http.StatusUpgradeRequired, fmt.Errorf("WebSocket protocol violation: Upgrade header %q does not contain websocket", r.Header.Get("Upgrade")) @@ -309,11 +309,9 @@ func acceptWebkitDeflate(w http.ResponseWriter, ext websocketExtension, mode Com return copts, nil } -func headerContainsToken(h http.Header, key, token string) bool { - token = strings.ToLower(token) - +func headerContainsTokenIgnoreCase(h http.Header, key, token string) bool { for _, t := range headerTokens(h, key) { - if t == token { + if strings.EqualFold(t, token) { return true } } @@ -354,7 +352,6 @@ func headerTokens(h http.Header, key string) []string { for _, v := range h[key] { v = strings.TrimSpace(v) for _, t := range strings.Split(v, ",") { - t = strings.ToLower(t) t = strings.TrimSpace(t) tokens = append(tokens, t) } diff --git a/accept_test.go b/accept_test.go index 9b18d8e1..e114d1ad 100644 --- a/accept_test.go +++ b/accept_test.go @@ -224,6 +224,12 @@ func Test_selectSubprotocol(t *testing.T) { serverProtocols: []string{"echo2", "echo3"}, negotiated: "echo3", }, + { + name: "clientCasePresered", + clientProtocols: []string{"Echo1"}, + serverProtocols: []string{"echo1"}, + negotiated: "Echo1", + }, } for _, tc := range testCases { diff --git a/dial.go b/dial.go index 509882e0..7a7787ff 100644 --- a/dial.go +++ b/dial.go @@ -194,11 +194,11 @@ func verifyServerResponse(opts *DialOptions, copts *compressionOptions, secWebSo return nil, fmt.Errorf("expected handshake response status code %v but got %v", http.StatusSwitchingProtocols, resp.StatusCode) } - if !headerContainsToken(resp.Header, "Connection", "Upgrade") { + if !headerContainsTokenIgnoreCase(resp.Header, "Connection", "Upgrade") { return nil, fmt.Errorf("WebSocket protocol violation: Connection header %q does not contain Upgrade", resp.Header.Get("Connection")) } - if !headerContainsToken(resp.Header, "Upgrade", "WebSocket") { + if !headerContainsTokenIgnoreCase(resp.Header, "Upgrade", "WebSocket") { return nil, fmt.Errorf("WebSocket protocol violation: Upgrade header %q does not contain websocket", resp.Header.Get("Upgrade")) } From fe1020d9fa5d2a910ac04df301eec6fa1e9aab58 Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Sat, 9 Jan 2021 07:33:26 -0500 Subject: [PATCH 6/7] Fix incorrect &*var clones Thank you @icholy for identifying these in https://github.com/nhooyr/websocket/pull/259#issuecomment-702279421 --- dial.go | 3 ++- internal/test/wstest/pipe.go | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/dial.go b/dial.go index 7c959bff..a79b55e6 100644 --- a/dial.go +++ b/dial.go @@ -250,7 +250,8 @@ func verifyServerExtensions(copts *compressionOptions, h http.Header) (*compress return nil, fmt.Errorf("WebSocket protcol violation: unsupported extensions from server: %+v", exts[1:]) } - copts = &*copts + _copts := *copts + copts = &_copts for _, p := range ext.params { switch p { diff --git a/internal/test/wstest/pipe.go b/internal/test/wstest/pipe.go index 1534f316..f3d4c517 100644 --- a/internal/test/wstest/pipe.go +++ b/internal/test/wstest/pipe.go @@ -24,7 +24,8 @@ func Pipe(dialOpts *websocket.DialOptions, acceptOpts *websocket.AcceptOptions) if dialOpts == nil { dialOpts = &websocket.DialOptions{} } - dialOpts = &*dialOpts + _dialOpts := *dialOpts + dialOpts = &_dialOpts dialOpts.HTTPClient = &http.Client{ Transport: tt, } From e4fee52874b402afcb4cc7aa5cebddc393618800 Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Sat, 9 Jan 2021 07:30:29 -0500 Subject: [PATCH 7/7] ci/test.sh: Work with BSD sed --- ci/test.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ci/test.sh b/ci/test.sh index 95ef7101..bd68b80e 100755 --- a/ci/test.sh +++ b/ci/test.sh @@ -5,9 +5,9 @@ main() { cd "$(dirname "$0")/.." go test -timeout=30m -covermode=atomic -coverprofile=ci/out/coverage.prof -coverpkg=./... "$@" ./... - sed -i '/stringer\.go/d' ci/out/coverage.prof - sed -i '/nhooyr.io\/websocket\/internal\/test/d' ci/out/coverage.prof - sed -i '/examples/d' ci/out/coverage.prof + sed -i.bak '/stringer\.go/d' ci/out/coverage.prof + sed -i.bak '/nhooyr.io\/websocket\/internal\/test/d' ci/out/coverage.prof + sed -i.bak '/examples/d' ci/out/coverage.prof # Last line is the total coverage. go tool cover -func ci/out/coverage.prof | tail -n1