Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

websocket server only needs to reply with a single subprotocol #2845

Merged
merged 4 commits into from
Feb 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion lib/web/fetch/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -1476,5 +1476,6 @@ module.exports = {
buildContentRange,
parseMetadata,
createInflate,
extractMimeType
extractMimeType,
getDecodeSplit
}
16 changes: 13 additions & 3 deletions lib/web/websocket/connection.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const { CloseEvent } = require('./events')
const { makeRequest } = require('../fetch/request')
const { fetching } = require('../fetch/index')
const { Headers } = require('../fetch/headers')
const { getDecodeSplit } = require('../fetch/util')
const { kHeadersList } = require('../../core/symbols')

/** @type {import('crypto')} */
Expand Down Expand Up @@ -176,9 +177,18 @@ function establishWebSocketConnection (url, protocols, ws, onEstablish, options)
// the WebSocket Connection_.
const secProtocol = response.headersList.get('Sec-WebSocket-Protocol')

if (secProtocol !== null && secProtocol !== request.headersList.get('Sec-WebSocket-Protocol')) {
failWebsocketConnection(ws, 'Protocol was not set in the opening handshake.')
return
if (secProtocol !== null) {
KhafraDev marked this conversation as resolved.
Show resolved Hide resolved
const requestProtocols = getDecodeSplit('sec-websocket-protocol', request.headersList)

// The client can request that the server use a specific subprotocol by
// including the |Sec-WebSocket-Protocol| field in its handshake. If it
// is specified, the server needs to include the same field and one of
// the selected subprotocol values in its response for the connection to
// be established.
if (!requestProtocols.includes(secProtocol)) {
failWebsocketConnection(ws, 'Protocol was not set in the opening handshake.')
return
}
}

response.socket.on('data', onSocketData)
Expand Down
73 changes: 73 additions & 0 deletions test/websocket/issue-2844.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
'use strict'

const { test } = require('node:test')
const { once } = require('node:events')
const { WebSocketServer } = require('ws')
const { WebSocket } = require('../..')
const { tspl } = require('@matteo.collina/tspl')

test('The server must reply with at least one subprotocol the client sends', async (t) => {
const { completed, deepStrictEqual, fail } = tspl(t, { plan: 2 })

const wss = new WebSocketServer({
handleProtocols: (protocols) => {
deepStrictEqual(protocols, new Set(['msgpack', 'json']))

return protocols.values().next().value
},
port: 0
})

wss.on('connection', (ws) => {
ws.on('error', fail)
ws.send('something')
})

await once(wss, 'listening')

const ws = new WebSocket(`ws://localhost:${wss.address().port}`, {
protocols: ['msgpack', 'json']
KhafraDev marked this conversation as resolved.
Show resolved Hide resolved
})

ws.onerror = fail
ws.onopen = () => deepStrictEqual(ws.protocol, 'msgpack')

t.after(() => {
wss.close()
ws.close()
})

await completed
})

test('The connection fails when the client sends subprotocols that the server does not responc with', async (t) => {
const { completed, fail, ok } = tspl(t, { plan: 1 })

const wss = new WebSocketServer({
handleProtocols: () => false,
port: 0
})

wss.on('connection', (ws) => {
ws.on('error', fail)
ws.send('something')
})

await once(wss, 'listening')

const ws = new WebSocket(`ws://localhost:${wss.address().port}`, {
protocols: ['json']
})

ws.onerror = ok.bind(null, true)
// The server will try to send 'something', this ensures that the connection
// fails during the handshake and doesn't receive any messages.
ws.onmessage = fail

t.after(() => {
wss.close()
ws.close()
})

await completed
})
2 changes: 1 addition & 1 deletion test/wpt/server/websocket.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { server } from './server.mjs'

const wss = new WebSocketServer({
server,
handleProtocols: (protocols) => [...protocols].join(', ')
handleProtocols: (protocols) => protocols.values().next().value
})

wss.on('connection', (ws, request) => {
Expand Down