Skip to content

Commit

Permalink
handshake
Browse files Browse the repository at this point in the history
  • Loading branch information
KhafraDev committed May 15, 2024
1 parent e2dc4da commit 98a904b
Show file tree
Hide file tree
Showing 5 changed files with 59 additions and 14 deletions.
1 change: 1 addition & 0 deletions lib/web/fetch/data-url.js
Original file line number Diff line number Diff line change
Expand Up @@ -737,6 +737,7 @@ module.exports = {
collectAnHTTPQuotedString,
serializeAMimeType,
removeChars,
removeHTTPWhitespace,
minimizeSupportedMimeType,
HTTP_TOKEN_CODEPOINTS,
isomorphicDecode
Expand Down
21 changes: 13 additions & 8 deletions lib/web/websocket/connection.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const {
kReceivedClose,
kResponse
} = require('./symbols')
const { fireEvent, failWebsocketConnection, isClosing, isClosed, isEstablished } = require('./util')
const { fireEvent, failWebsocketConnection, isClosing, isClosed, isEstablished, parseExtensions } = require('./util')
const { channels } = require('../../core/diagnostics')
const { CloseEvent } = require('./events')
const { makeRequest } = require('../fetch/request')
Expand All @@ -32,7 +32,7 @@ try {
* @param {URL} url
* @param {string|string[]} protocols
* @param {import('./websocket').WebSocket} ws
* @param {(response: any) => void} onEstablish
* @param {(response: any, extensions: string[] | undefined) => void} onEstablish
* @param {Partial<import('../../types/websocket').WebSocketInit>} options
*/
function establishWebSocketConnection (url, protocols, client, ws, onEstablish, options) {
Expand Down Expand Up @@ -93,11 +93,11 @@ function establishWebSocketConnection (url, protocols, client, ws, onEstablish,
// "permessage-deflate" extension header value.
// https://github.com/mozilla/gecko-dev/blob/ce78234f5e653a5d3916813ff990f053510227bc/netwerk/protocol/websocket/WebSocketChannel.cpp#L2673
// TODO: enable once permessage-deflate is supported
const permessageDeflate = '' // 'permessage-deflate; 15'
const permessageDeflate = 'permessage-deflate' // 'permessage-deflate; 15'

// 10. Append (`Sec-WebSocket-Extensions`, permessageDeflate) to
// request’s header list.
// request.headersList.append('sec-websocket-extensions', permessageDeflate)
request.headersList.set('sec-websocket-extensions', permessageDeflate)

// 11. Fetch request with useParallelQueue set to true, and
// processResponse given response being these steps:
Expand Down Expand Up @@ -168,10 +168,15 @@ function establishWebSocketConnection (url, protocols, client, ws, onEstablish,
// header field to determine which extensions are requested is
// discussed in Section 9.1.)
const secExtension = response.headersList.get('Sec-WebSocket-Extensions')
let extensions

if (secExtension !== null && secExtension !== permessageDeflate) {
failWebsocketConnection(ws, 'Received different permessage-deflate than the one set.')
return
if (secExtension !== null) {
extensions = parseExtensions(secExtension)

if (!extensions.has(permessageDeflate)) {
failWebsocketConnection(ws, 'Sec-WebSocket-Extensions header does not match.')
return
}
}

// 6. If the response includes a |Sec-WebSocket-Protocol| header field
Expand Down Expand Up @@ -207,7 +212,7 @@ function establishWebSocketConnection (url, protocols, client, ws, onEstablish,
})
}

onEstablish(response)
onEstablish(response, extensions)
}
})

Expand Down
17 changes: 15 additions & 2 deletions lib/web/websocket/receiver.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,14 @@ class ByteParser extends Writable {
#info = {}
#fragments = []

constructor (ws) {
/** @type {Set<string>} */
#extensions

constructor (ws, extensions) {
super()

this.ws = ws
this.#extensions = new Set(extensions)
}

/**
Expand Down Expand Up @@ -91,7 +95,16 @@ class ByteParser extends Writable {
// the negotiated extensions defines the meaning of such a nonzero
// value, the receiving endpoint MUST _Fail the WebSocket
// Connection_.
if (rsv1 !== 0 || rsv2 !== 0 || rsv3 !== 0) {
// This document allocates the RSV1 bit of the WebSocket header for
// PMCEs and calls the bit the "Per-Message Compressed" bit. On a
// WebSocket connection where a PMCE is in use, this bit indicates
// whether a message is compressed or not.
if (rsv1 !== 0 && !this.#extensions.has('permessage-deflate')) {
failWebsocketConnection(this.ws, 'Expected RSV1 to be clear.')
return
}

if (rsv2 !== 0 || rsv3 !== 0) {
failWebsocketConnection(this.ws, 'RSV1, RSV2, RSV3 must be clear')
return
}
Expand Down
28 changes: 27 additions & 1 deletion lib/web/websocket/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const { kReadyState, kController, kResponse, kBinaryType, kWebSocketURL } = requ
const { states, opcodes } = require('./constants')
const { ErrorEvent, createFastMessageEvent } = require('./events')
const { isUtf8 } = require('node:buffer')
const { collectASequenceOfCodePointsFast, removeHTTPWhitespace } = require('../fetch/data-url')

/* globals Blob */

Expand Down Expand Up @@ -234,6 +235,30 @@ function isValidOpcode (opcode) {
return isTextBinaryFrame(opcode) || isContinuationFrame(opcode) || isControlFrame(opcode)
}

/**
* Parses a Sec-WebSocket-Extensions header value.
* @param {string} extensions
* @returns {Map<string, string>}
*/
function parseExtensions (extensions) {
const position = { position: 0 }
const extensionList = new Map()

while (position.position < extensions.length) {
const pair = collectASequenceOfCodePointsFast(';', extensions, position)
const [name, value = ''] = pair.split('=')

extensionList.set(
removeHTTPWhitespace(name, true, false),
removeHTTPWhitespace(value, false, true)
)

position.position++
}

return extensionList
}

// https://nodejs.org/api/intl.html#detecting-internationalization-support
const hasIntl = typeof process.versions.icu === 'string'
const fatalDecoder = hasIntl ? new TextDecoder('utf-8', { fatal: true }) : undefined
Expand Down Expand Up @@ -265,5 +290,6 @@ module.exports = {
isControlFrame,
isContinuationFrame,
isTextBinaryFrame,
isValidOpcode
isValidOpcode,
parseExtensions
}
6 changes: 3 additions & 3 deletions lib/web/websocket/websocket.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ class WebSocket extends EventTarget {
protocols,
client,
this,
(response) => this.#onConnectionEstablished(response),
(response, extensions) => this.#onConnectionEstablished(response, extensions),
options
)

Expand Down Expand Up @@ -458,12 +458,12 @@ class WebSocket extends EventTarget {
/**
* @see https://websockets.spec.whatwg.org/#feedback-from-the-protocol
*/
#onConnectionEstablished (response) {
#onConnectionEstablished (response, parsedExtensions) {
// processResponse is called when the "response’s header list has been received and initialized."
// once this happens, the connection is open
this[kResponse] = response

const parser = new ByteParser(this)
const parser = new ByteParser(this, parsedExtensions)
parser.on('drain', onParserDrain)
parser.on('error', onParserError.bind(this))

Expand Down

0 comments on commit 98a904b

Please sign in to comment.