2 Commits
v14 ... v15

Author SHA1 Message Date
Dawid Dziurla
4758fd2f0e node_modules: update (#276)
Co-authored-by: dawidd6 <9713907+dawidd6@users.noreply.github.com>
2026-03-14 19:11:45 +01:00
dependabot[bot]
512815fe9c build(deps): bump undici from 6.23.0 to 6.24.1 (#275)
Bumps [undici](https://github.com/nodejs/undici) from 6.23.0 to 6.24.1.
- [Release notes](https://github.com/nodejs/undici/releases)
- [Commits](https://github.com/nodejs/undici/compare/v6.23.0...v6.24.1)

---
updated-dependencies:
- dependency-name: undici
  dependency-version: 6.24.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-14 19:10:39 +01:00
12 changed files with 123 additions and 20 deletions

6
node_modules/.package-lock.json generated vendored
View File

@@ -319,9 +319,9 @@
} }
}, },
"node_modules/undici": { "node_modules/undici": {
"version": "6.23.0", "version": "6.24.1",
"resolved": "https://registry.npmjs.org/undici/-/undici-6.23.0.tgz", "resolved": "https://registry.npmjs.org/undici/-/undici-6.24.1.tgz",
"integrity": "sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g==", "integrity": "sha512-sC+b0tB1whOCzbtlx20fx3WgCXwkW627p4EA9uM+/tNNPkSS+eSEld6pAs9nDv7WbY1UUljBMYPtu9BCOrCWKA==",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=18.17" "node": ">=18.17"

View File

@@ -27,6 +27,7 @@ import { errors } from 'undici'
| `InformationalError` | `UND_ERR_INFO` | expected error with reason | | `InformationalError` | `UND_ERR_INFO` | expected error with reason |
| `ResponseExceededMaxSizeError` | `UND_ERR_RES_EXCEEDED_MAX_SIZE` | response body exceed the max size allowed | | `ResponseExceededMaxSizeError` | `UND_ERR_RES_EXCEEDED_MAX_SIZE` | response body exceed the max size allowed |
| `SecureProxyConnectionError` | `UND_ERR_PRX_TLS` | tls connection to a proxy failed | | `SecureProxyConnectionError` | `UND_ERR_PRX_TLS` | tls connection to a proxy failed |
| `MessageSizeExceededError` | `UND_ERR_WS_MESSAGE_SIZE_EXCEEDED` | WebSocket decompressed message exceeded the maximum allowed size |
### `SocketError` ### `SocketError`

View File

@@ -13,6 +13,14 @@ Arguments:
* **url** `URL | string` - The url's protocol *must* be `ws` or `wss`. * **url** `URL | string` - The url's protocol *must* be `ws` or `wss`.
* **protocol** `string | string[] | WebSocketInit` (optional) - Subprotocol(s) to request the server use, or a [`Dispatcher`](./Dispatcher.md). * **protocol** `string | string[] | WebSocketInit` (optional) - Subprotocol(s) to request the server use, or a [`Dispatcher`](./Dispatcher.md).
### WebSocketInit
When passing an object as the second argument, the following options are available:
* **protocols** `string | string[]` (optional) - Subprotocol(s) to request the server use.
* **dispatcher** `Dispatcher` (optional) - A custom [`Dispatcher`](/docs/docs/api/Dispatcher.md) to use for the connection.
* **headers** `HeadersInit` (optional) - Custom headers to include in the WebSocket handshake request.
### Example: ### Example:
This example will not work in browsers or other platforms that don't allow passing an object. This example will not work in browsers or other platforms that don't allow passing an object.

View File

@@ -379,6 +379,24 @@ class SecureProxyConnectionError extends UndiciError {
[kSecureProxyConnectionError] = true [kSecureProxyConnectionError] = true
} }
const kMessageSizeExceededError = Symbol.for('undici.error.UND_ERR_WS_MESSAGE_SIZE_EXCEEDED')
class MessageSizeExceededError extends UndiciError {
constructor (message) {
super(message)
this.name = 'MessageSizeExceededError'
this.message = message || 'Max decompressed message size exceeded'
this.code = 'UND_ERR_WS_MESSAGE_SIZE_EXCEEDED'
}
static [Symbol.hasInstance] (instance) {
return instance && instance[kMessageSizeExceededError] === true
}
get [kMessageSizeExceededError] () {
return true
}
}
module.exports = { module.exports = {
AbortError, AbortError,
HTTPParserError, HTTPParserError,
@@ -402,5 +420,6 @@ module.exports = {
ResponseExceededMaxSizeError, ResponseExceededMaxSizeError,
RequestRetryError, RequestRetryError,
ResponseError, ResponseError,
SecureProxyConnectionError SecureProxyConnectionError,
MessageSizeExceededError
} }

View File

@@ -66,6 +66,10 @@ class Request {
throw new InvalidArgumentError('upgrade must be a string') throw new InvalidArgumentError('upgrade must be a string')
} }
if (upgrade && !isValidHeaderValue(upgrade)) {
throw new InvalidArgumentError('invalid upgrade header')
}
if (headersTimeout != null && (!Number.isFinite(headersTimeout) || headersTimeout < 0)) { if (headersTimeout != null && (!Number.isFinite(headersTimeout) || headersTimeout < 0)) {
throw new InvalidArgumentError('invalid headersTimeout') throw new InvalidArgumentError('invalid headersTimeout')
} }
@@ -360,13 +364,19 @@ function processHeader (request, key, val) {
val = `${val}` val = `${val}`
} }
if (request.host === null && headerName === 'host') { if (headerName === 'host') {
if (request.host !== null) {
throw new InvalidArgumentError('duplicate host header')
}
if (typeof val !== 'string') { if (typeof val !== 'string') {
throw new InvalidArgumentError('invalid host header') throw new InvalidArgumentError('invalid host header')
} }
// Consumed by Client // Consumed by Client
request.host = val request.host = val
} else if (request.contentLength === null && headerName === 'content-length') { } else if (headerName === 'content-length') {
if (request.contentLength !== null) {
throw new InvalidArgumentError('duplicate content-length header')
}
request.contentLength = parseInt(val, 10) request.contentLength = parseInt(val, 10)
if (!Number.isFinite(request.contentLength)) { if (!Number.isFinite(request.contentLength)) {
throw new InvalidArgumentError('invalid content-length header') throw new InvalidArgumentError('invalid content-length header')

View File

@@ -2,17 +2,30 @@
const { createInflateRaw, Z_DEFAULT_WINDOWBITS } = require('node:zlib') const { createInflateRaw, Z_DEFAULT_WINDOWBITS } = require('node:zlib')
const { isValidClientWindowBits } = require('./util') const { isValidClientWindowBits } = require('./util')
const { MessageSizeExceededError } = require('../../core/errors')
const tail = Buffer.from([0x00, 0x00, 0xff, 0xff]) const tail = Buffer.from([0x00, 0x00, 0xff, 0xff])
const kBuffer = Symbol('kBuffer') const kBuffer = Symbol('kBuffer')
const kLength = Symbol('kLength') const kLength = Symbol('kLength')
// Default maximum decompressed message size: 4 MB
const kDefaultMaxDecompressedSize = 4 * 1024 * 1024
class PerMessageDeflate { class PerMessageDeflate {
/** @type {import('node:zlib').InflateRaw} */ /** @type {import('node:zlib').InflateRaw} */
#inflate #inflate
#options = {} #options = {}
/** @type {boolean} */
#aborted = false
/** @type {Function|null} */
#currentCallback = null
/**
* @param {Map<string, string>} extensions
*/
constructor (extensions) { constructor (extensions) {
this.#options.serverNoContextTakeover = extensions.has('server_no_context_takeover') this.#options.serverNoContextTakeover = extensions.has('server_no_context_takeover')
this.#options.serverMaxWindowBits = extensions.get('server_max_window_bits') this.#options.serverMaxWindowBits = extensions.get('server_max_window_bits')
@@ -24,6 +37,11 @@ class PerMessageDeflate {
// payload of the message. // payload of the message.
// 2. Decompress the resulting data using DEFLATE. // 2. Decompress the resulting data using DEFLATE.
if (this.#aborted) {
callback(new MessageSizeExceededError())
return
}
if (!this.#inflate) { if (!this.#inflate) {
let windowBits = Z_DEFAULT_WINDOWBITS let windowBits = Z_DEFAULT_WINDOWBITS
@@ -36,13 +54,37 @@ class PerMessageDeflate {
windowBits = Number.parseInt(this.#options.serverMaxWindowBits) windowBits = Number.parseInt(this.#options.serverMaxWindowBits)
} }
try {
this.#inflate = createInflateRaw({ windowBits }) this.#inflate = createInflateRaw({ windowBits })
} catch (err) {
callback(err)
return
}
this.#inflate[kBuffer] = [] this.#inflate[kBuffer] = []
this.#inflate[kLength] = 0 this.#inflate[kLength] = 0
this.#inflate.on('data', (data) => { this.#inflate.on('data', (data) => {
this.#inflate[kBuffer].push(data) if (this.#aborted) {
return
}
this.#inflate[kLength] += data.length this.#inflate[kLength] += data.length
if (this.#inflate[kLength] > kDefaultMaxDecompressedSize) {
this.#aborted = true
this.#inflate.removeAllListeners()
this.#inflate.destroy()
this.#inflate = null
if (this.#currentCallback) {
const cb = this.#currentCallback
this.#currentCallback = null
cb(new MessageSizeExceededError())
}
return
}
this.#inflate[kBuffer].push(data)
}) })
this.#inflate.on('error', (err) => { this.#inflate.on('error', (err) => {
@@ -51,16 +93,22 @@ class PerMessageDeflate {
}) })
} }
this.#currentCallback = callback
this.#inflate.write(chunk) this.#inflate.write(chunk)
if (fin) { if (fin) {
this.#inflate.write(tail) this.#inflate.write(tail)
} }
this.#inflate.flush(() => { this.#inflate.flush(() => {
if (this.#aborted || !this.#inflate) {
return
}
const full = Buffer.concat(this.#inflate[kBuffer], this.#inflate[kLength]) const full = Buffer.concat(this.#inflate[kBuffer], this.#inflate[kLength])
this.#inflate[kBuffer].length = 0 this.#inflate[kBuffer].length = 0
this.#inflate[kLength] = 0 this.#inflate[kLength] = 0
this.#currentCallback = null
callback(null, full) callback(null, full)
}) })

View File

@@ -37,6 +37,10 @@ class ByteParser extends Writable {
/** @type {Map<string, PerMessageDeflate>} */ /** @type {Map<string, PerMessageDeflate>} */
#extensions #extensions
/**
* @param {import('./websocket').WebSocket} ws
* @param {Map<string, string>|null} extensions
*/
constructor (ws, extensions) { constructor (ws, extensions) {
super() super()
@@ -179,6 +183,7 @@ class ByteParser extends Writable {
const buffer = this.consume(8) const buffer = this.consume(8)
const upper = buffer.readUInt32BE(0) const upper = buffer.readUInt32BE(0)
const lower = buffer.readUInt32BE(4)
// 2^31 is the maximum bytes an arraybuffer can contain // 2^31 is the maximum bytes an arraybuffer can contain
// on 32-bit systems. Although, on 64-bit systems, this is // on 32-bit systems. Although, on 64-bit systems, this is
@@ -186,14 +191,12 @@ class ByteParser extends Writable {
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Invalid_array_length // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Invalid_array_length
// https://source.chromium.org/chromium/chromium/src/+/main:v8/src/common/globals.h;drc=1946212ac0100668f14eb9e2843bdd846e510a1e;bpv=1;bpt=1;l=1275 // https://source.chromium.org/chromium/chromium/src/+/main:v8/src/common/globals.h;drc=1946212ac0100668f14eb9e2843bdd846e510a1e;bpv=1;bpt=1;l=1275
// https://source.chromium.org/chromium/chromium/src/+/main:v8/src/objects/js-array-buffer.h;l=34;drc=1946212ac0100668f14eb9e2843bdd846e510a1e // https://source.chromium.org/chromium/chromium/src/+/main:v8/src/objects/js-array-buffer.h;l=34;drc=1946212ac0100668f14eb9e2843bdd846e510a1e
if (upper > 2 ** 31 - 1) { if (upper !== 0 || lower > 2 ** 31 - 1) {
failWebsocketConnection(this.ws, 'Received payload length > 2^31 bytes.') failWebsocketConnection(this.ws, 'Received payload length > 2^31 bytes.')
return return
} }
const lower = buffer.readUInt32BE(4) this.#info.payloadLength = lower
this.#info.payloadLength = (upper << 8) + lower
this.#state = parserStates.READ_DATA this.#state = parserStates.READ_DATA
} else if (this.#state === parserStates.READ_DATA) { } else if (this.#state === parserStates.READ_DATA) {
if (this.#byteOffset < this.#info.payloadLength) { if (this.#byteOffset < this.#info.payloadLength) {
@@ -223,7 +226,7 @@ class ByteParser extends Writable {
} else { } else {
this.#extensions.get('permessage-deflate').decompress(body, this.#info.fin, (error, data) => { this.#extensions.get('permessage-deflate').decompress(body, this.#info.fin, (error, data) => {
if (error) { if (error) {
closeWebSocketConnection(this.ws, 1007, error.message, error.message.length) failWebsocketConnection(this.ws, error.message)
return return
} }

View File

@@ -266,6 +266,12 @@ function parseExtensions (extensions) {
* @param {string} value * @param {string} value
*/ */
function isValidClientWindowBits (value) { function isValidClientWindowBits (value) {
// Must have at least one character
if (value.length === 0) {
return false
}
// Check all characters are ASCII digits
for (let i = 0; i < value.length; i++) { for (let i = 0; i < value.length; i++) {
const byte = value.charCodeAt(i) const byte = value.charCodeAt(i)
@@ -274,7 +280,9 @@ function isValidClientWindowBits (value) {
} }
} }
return true // Check numeric range: zlib requires windowBits in range 8-15
const num = Number.parseInt(value, 10)
return num >= 8 && num <= 15
} }
// https://nodejs.org/api/intl.html#detecting-internationalization-support // https://nodejs.org/api/intl.html#detecting-internationalization-support

View File

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

4
node_modules/undici/package.json generated vendored
View File

@@ -1,6 +1,6 @@
{ {
"name": "undici", "name": "undici",
"version": "6.23.0", "version": "6.24.1",
"description": "An HTTP/1.1 client, written from scratch for Node.js", "description": "An HTTP/1.1 client, written from scratch for Node.js",
"homepage": "https://undici.nodejs.org", "homepage": "https://undici.nodejs.org",
"bugs": { "bugs": {
@@ -107,6 +107,7 @@
"devDependencies": { "devDependencies": {
"@fastify/busboy": "2.1.1", "@fastify/busboy": "2.1.1",
"@matteo.collina/tspl": "^0.1.1", "@matteo.collina/tspl": "^0.1.1",
"@metcoder95/https-pem": "^1.0.0",
"@sinonjs/fake-timers": "^11.1.0", "@sinonjs/fake-timers": "^11.1.0",
"@types/node": "~18.19.50", "@types/node": "~18.19.50",
"abort-controller": "^3.0.0", "abort-controller": "^3.0.0",
@@ -117,7 +118,6 @@
"fast-check": "^3.17.1", "fast-check": "^3.17.1",
"form-data": "^4.0.0", "form-data": "^4.0.0",
"formdata-node": "^6.0.3", "formdata-node": "^6.0.3",
"https-pem": "^3.0.0",
"husky": "^9.0.7", "husky": "^9.0.7",
"jest": "^29.0.2", "jest": "^29.0.2",
"jsdom": "^24.0.0", "jsdom": "^24.0.0",

View File

@@ -146,4 +146,10 @@ declare namespace Errors {
name: 'SecureProxyConnectionError'; name: 'SecureProxyConnectionError';
code: 'UND_ERR_PRX_TLS'; code: 'UND_ERR_PRX_TLS';
} }
/** WebSocket decompressed message exceeded maximum size. */
export class MessageSizeExceededError extends UndiciError {
name: 'MessageSizeExceededError'
code: 'UND_ERR_WS_MESSAGE_SIZE_EXCEEDED'
}
} }

6
package-lock.json generated
View File

@@ -328,9 +328,9 @@
} }
}, },
"node_modules/undici": { "node_modules/undici": {
"version": "6.23.0", "version": "6.24.1",
"resolved": "https://registry.npmjs.org/undici/-/undici-6.23.0.tgz", "resolved": "https://registry.npmjs.org/undici/-/undici-6.24.1.tgz",
"integrity": "sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g==", "integrity": "sha512-sC+b0tB1whOCzbtlx20fx3WgCXwkW627p4EA9uM+/tNNPkSS+eSEld6pAs9nDv7WbY1UUljBMYPtu9BCOrCWKA==",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=18.17" "node": ">=18.17"