Skip to content

Commit fc91f5f

Browse files
committed
Merge remote-tracking branch 'origin/master' into remove-net-http-wasm
2 parents db89d1b + 246891f commit fc91f5f

31 files changed

+701
-221
lines changed

.github/FUNDING.yml

Lines changed: 0 additions & 1 deletion
This file was deleted.

.github/dependabot.yml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
version: 2
2+
updates:
3+
# Track in case we ever add dependencies.
4+
- package-ecosystem: 'gomod'
5+
directory: '/'
6+
schedule:
7+
interval: 'weekly'
8+
commit-message:
9+
prefix: 'chore'
10+
11+
# Keep example and test/benchmark deps up-to-date.
12+
- package-ecosystem: 'gomod'
13+
directories:
14+
- '/internal/examples'
15+
- '/internal/thirdparty'
16+
schedule:
17+
interval: 'monthly'
18+
commit-message:
19+
prefix: 'chore'
20+
labels: []
21+
groups:
22+
internal-deps:
23+
patterns:
24+
- '*'

.github/workflows/ci.yml

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
name: ci
2-
on: [push, pull_request]
2+
on:
3+
push:
4+
branches:
5+
- master
6+
pull_request:
7+
branches:
8+
- master
39
concurrency:
410
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}
511
cancel-in-progress: true
@@ -12,25 +18,33 @@ jobs:
1218
- uses: actions/setup-go@v5
1319
with:
1420
go-version-file: ./go.mod
15-
- run: ./ci/fmt.sh
21+
- run: make fmt
1622

1723
lint:
1824
runs-on: ubuntu-latest
1925
steps:
2026
- uses: actions/checkout@v4
2127
- run: go version
2228
- uses: actions/setup-go@v5
23-
- run: ./ci/lint.sh
29+
with:
30+
go-version-file: ./go.mod
31+
- run: make lint
2432

2533
test:
2634
runs-on: ubuntu-latest
2735
steps:
36+
- name: Disable AppArmor
37+
if: runner.os == 'Linux'
38+
run: |
39+
# Disable AppArmor for Ubuntu 23.10+.
40+
# https://chromium.googlesource.com/chromium/src/+/main/docs/security/apparmor-userns-restrictions.md
41+
echo 0 | sudo tee /proc/sys/kernel/apparmor_restrict_unprivileged_userns
2842
- uses: actions/checkout@v4
2943
- uses: actions/setup-go@v5
3044
with:
3145
go-version-file: ./go.mod
32-
- run: ./ci/test.sh
33-
- uses: actions/upload-artifact@v3
46+
- run: make test
47+
- uses: actions/upload-artifact@v4
3448
with:
3549
name: coverage.html
3650
path: ./ci/out/coverage.html
@@ -42,4 +56,4 @@ jobs:
4256
- uses: actions/setup-go@v5
4357
with:
4458
go-version-file: ./go.mod
45-
- run: ./ci/bench.sh
59+
- run: make bench

.github/workflows/daily.yml

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,22 @@ jobs:
1515
- uses: actions/setup-go@v5
1616
with:
1717
go-version-file: ./go.mod
18-
- run: AUTOBAHN=1 ./ci/bench.sh
18+
- run: AUTOBAHN=1 make bench
1919
test:
2020
runs-on: ubuntu-latest
2121
steps:
22+
- name: Disable AppArmor
23+
if: runner.os == 'Linux'
24+
run: |
25+
# Disable AppArmor for Ubuntu 23.10+.
26+
# https://chromium.googlesource.com/chromium/src/+/main/docs/security/apparmor-userns-restrictions.md
27+
echo 0 | sudo tee /proc/sys/kernel/apparmor_restrict_unprivileged_userns
2228
- uses: actions/checkout@v4
2329
- uses: actions/setup-go@v5
2430
with:
2531
go-version-file: ./go.mod
26-
- run: AUTOBAHN=1 ./ci/test.sh
27-
- uses: actions/upload-artifact@v3
32+
- run: AUTOBAHN=1 make test
33+
- uses: actions/upload-artifact@v4
2834
with:
2935
name: coverage.html
3036
path: ./ci/out/coverage.html
@@ -37,18 +43,24 @@ jobs:
3743
- uses: actions/setup-go@v5
3844
with:
3945
go-version-file: ./go.mod
40-
- run: AUTOBAHN=1 ./ci/bench.sh
46+
- run: AUTOBAHN=1 make bench
4147
test-dev:
4248
runs-on: ubuntu-latest
4349
steps:
50+
- name: Disable AppArmor
51+
if: runner.os == 'Linux'
52+
run: |
53+
# Disable AppArmor for Ubuntu 23.10+.
54+
# https://chromium.googlesource.com/chromium/src/+/main/docs/security/apparmor-userns-restrictions.md
55+
echo 0 | sudo tee /proc/sys/kernel/apparmor_restrict_unprivileged_userns
4456
- uses: actions/checkout@v4
4557
with:
4658
ref: dev
4759
- uses: actions/setup-go@v5
4860
with:
4961
go-version-file: ./go.mod
50-
- run: AUTOBAHN=1 ./ci/test.sh
51-
- uses: actions/upload-artifact@v3
62+
- run: AUTOBAHN=1 make test
63+
- uses: actions/upload-artifact@v4
5264
with:
5365
name: coverage-dev.html
5466
path: ./ci/out/coverage.html

.github/workflows/static.yml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ jobs:
2222
url: ${{ steps.deployment.outputs.page_url }}
2323
runs-on: ubuntu-latest
2424
steps:
25+
- name: Disable AppArmor
26+
if: runner.os == 'Linux'
27+
run: |
28+
# Disable AppArmor for Ubuntu 23.10+.
29+
# https://chromium.googlesource.com/chromium/src/+/main/docs/security/apparmor-userns-restrictions.md
30+
echo 0 | sudo tee /proc/sys/kernel/apparmor_restrict_unprivileged_userns
2531
- name: Checkout
2632
uses: actions/checkout@v4
2733
- name: Setup Pages
@@ -32,7 +38,7 @@ jobs:
3238
go-version-file: ./go.mod
3339
- name: Generate coverage and badge
3440
run: |
35-
./ci/test.sh
41+
make test
3642
mkdir -p ./ci/out/static
3743
cp ./ci/out/coverage.html ./ci/out/static/coverage.html
3844
percent=$(go tool cover -func ./ci/out/coverage.prof | tail -n1 | awk '{print $3}' | tr -d '%')

Makefile

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
.PHONY: all
2+
all: fmt lint test
3+
4+
.PHONY: fmt
5+
fmt:
6+
./ci/fmt.sh
7+
8+
.PHONY: lint
9+
lint:
10+
./ci/lint.sh
11+
12+
.PHONY: test
13+
test:
14+
./ci/test.sh
15+
16+
.PHONY: bench
17+
bench:
18+
./ci/bench.sh

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,9 @@ http.HandlerFunc(func (w http.ResponseWriter, r *http.Request) {
6363
}
6464
defer c.CloseNow()
6565

66-
ctx, cancel := context.WithTimeout(r.Context(), time.Second*10)
66+
// Set the context as needed. Use of r.Context() is not recommended
67+
// to avoid surprising behavior (see http.Hijacker).
68+
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
6769
defer cancel()
6870

6971
var v interface{}

accept.go

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package websocket
55

66
import (
77
"bytes"
8+
"context"
89
"crypto/sha1"
910
"encoding/base64"
1011
"errors"
@@ -14,7 +15,7 @@ import (
1415
"net/http"
1516
"net/textproto"
1617
"net/url"
17-
"path/filepath"
18+
"path"
1819
"strings"
1920

2021
"github.com/coder/websocket/internal/errd"
@@ -41,8 +42,8 @@ type AcceptOptions struct {
4142
// One would set this field to []string{"example.com"} to authorize example.com to connect.
4243
//
4344
// Each pattern is matched case insensitively against the request origin host
44-
// with filepath.Match.
45-
// See https://golang.org/pkg/path/filepath/#Match
45+
// with path.Match.
46+
// See https://golang.org/pkg/path/#Match
4647
//
4748
// Please ensure you understand the ramifications of enabling this.
4849
// If used incorrectly your WebSocket server will be open to CSRF attacks.
@@ -62,6 +63,22 @@ type AcceptOptions struct {
6263
// Defaults to 512 bytes for CompressionNoContextTakeover and 128 bytes
6364
// for CompressionContextTakeover.
6465
CompressionThreshold int
66+
67+
// OnPingReceived is an optional callback invoked synchronously when a ping frame is received.
68+
//
69+
// The payload contains the application data of the ping frame.
70+
// If the callback returns false, the subsequent pong frame will not be sent.
71+
// To avoid blocking, any expensive processing should be performed asynchronously using a goroutine.
72+
OnPingReceived func(ctx context.Context, payload []byte) bool
73+
74+
// OnPongReceived is an optional callback invoked synchronously when a pong frame is received.
75+
//
76+
// The payload contains the application data of the pong frame.
77+
// To avoid blocking, any expensive processing should be performed asynchronously using a goroutine.
78+
//
79+
// Unlike OnPingReceived, this callback does not return a value because a pong frame
80+
// is a response to a ping and does not trigger any further frame transmission.
81+
OnPongReceived func(ctx context.Context, payload []byte)
6582
}
6683

6784
func (opts *AcceptOptions) cloneWithDefaults() *AcceptOptions {
@@ -79,6 +96,9 @@ func (opts *AcceptOptions) cloneWithDefaults() *AcceptOptions {
7996
// See the InsecureSkipVerify and OriginPatterns options to allow cross origin requests.
8097
//
8198
// Accept will write a response to w on all errors.
99+
//
100+
// Note that using the http.Request Context after Accept returns may lead to
101+
// unexpected behavior (see http.Hijacker).
82102
func Accept(w http.ResponseWriter, r *http.Request, opts *AcceptOptions) (*Conn, error) {
83103
return accept(w, r, opts)
84104
}
@@ -96,7 +116,7 @@ func accept(w http.ResponseWriter, r *http.Request, opts *AcceptOptions) (_ *Con
96116
if !opts.InsecureSkipVerify {
97117
err = authenticateOrigin(r, opts.OriginPatterns)
98118
if err != nil {
99-
if errors.Is(err, filepath.ErrBadPattern) {
119+
if errors.Is(err, path.ErrBadPattern) {
100120
log.Printf("websocket: %v", err)
101121
err = errors.New(http.StatusText(http.StatusForbidden))
102122
}
@@ -105,7 +125,7 @@ func accept(w http.ResponseWriter, r *http.Request, opts *AcceptOptions) (_ *Con
105125
}
106126
}
107127

108-
hj, ok := w.(http.Hijacker)
128+
hj, ok := hijacker(w)
109129
if !ok {
110130
err = errors.New("http.ResponseWriter does not implement http.Hijacker")
111131
http.Error(w, http.StatusText(http.StatusNotImplemented), http.StatusNotImplemented)
@@ -153,6 +173,8 @@ func accept(w http.ResponseWriter, r *http.Request, opts *AcceptOptions) (_ *Con
153173
client: false,
154174
copts: copts,
155175
flateThreshold: opts.CompressionThreshold,
176+
onPingReceived: opts.OnPingReceived,
177+
onPongReceived: opts.OnPongReceived,
156178

157179
br: brw.Reader,
158180
bw: brw.Writer,
@@ -221,7 +243,7 @@ func authenticateOrigin(r *http.Request, originHosts []string) error {
221243
for _, hostPattern := range originHosts {
222244
matched, err := match(hostPattern, u.Host)
223245
if err != nil {
224-
return fmt.Errorf("failed to parse filepath pattern %q: %w", hostPattern, err)
246+
return fmt.Errorf("failed to parse path pattern %q: %w", hostPattern, err)
225247
}
226248
if matched {
227249
return nil
@@ -234,7 +256,7 @@ func authenticateOrigin(r *http.Request, originHosts []string) error {
234256
}
235257

236258
func match(pattern, s string) (bool, error) {
237-
return filepath.Match(strings.ToLower(pattern), strings.ToLower(s))
259+
return path.Match(strings.ToLower(pattern), strings.ToLower(s))
238260
}
239261

240262
func selectSubprotocol(r *http.Request, subprotocols []string) string {

accept_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,33 @@ func TestAccept(t *testing.T) {
143143
_, err := Accept(w, r, nil)
144144
assert.Contains(t, err, `failed to hijack connection`)
145145
})
146+
147+
t.Run("wrapperHijackerIsUnwrapped", func(t *testing.T) {
148+
t.Parallel()
149+
150+
rr := httptest.NewRecorder()
151+
w := mockUnwrapper{
152+
ResponseWriter: rr,
153+
unwrap: func() http.ResponseWriter {
154+
return mockHijacker{
155+
ResponseWriter: rr,
156+
hijack: func() (conn net.Conn, writer *bufio.ReadWriter, err error) {
157+
return nil, nil, errors.New("haha")
158+
},
159+
}
160+
},
161+
}
162+
163+
r := httptest.NewRequest("GET", "/", nil)
164+
r.Header.Set("Connection", "Upgrade")
165+
r.Header.Set("Upgrade", "websocket")
166+
r.Header.Set("Sec-WebSocket-Version", "13")
167+
r.Header.Set("Sec-WebSocket-Key", xrand.Base64(16))
168+
169+
_, err := Accept(w, r, nil)
170+
assert.Contains(t, err, "failed to hijack connection")
171+
})
172+
146173
t.Run("closeRace", func(t *testing.T) {
147174
t.Parallel()
148175

@@ -534,3 +561,14 @@ var _ http.Hijacker = mockHijacker{}
534561
func (mj mockHijacker) Hijack() (net.Conn, *bufio.ReadWriter, error) {
535562
return mj.hijack()
536563
}
564+
565+
type mockUnwrapper struct {
566+
http.ResponseWriter
567+
unwrap func() http.ResponseWriter
568+
}
569+
570+
var _ rwUnwrapper = mockUnwrapper{}
571+
572+
func (mu mockUnwrapper) Unwrap() http.ResponseWriter {
573+
return mu.unwrap()
574+
}

autobahn_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ func TestAutobahn(t *testing.T) {
9292
}
9393
})
9494

95-
c, _, err := websocket.Dial(ctx, fmt.Sprintf(wstestURL+"/updateReports?agent=main"), nil)
95+
c, _, err := websocket.Dial(ctx, wstestURL+"/updateReports?agent=main", nil)
9696
assert.Success(t, err)
9797
c.Close(websocket.StatusNormalClosure, "")
9898

0 commit comments

Comments
 (0)