aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/node_modules/undici/lib
diff options
context:
space:
mode:
authorEgor Tensin <egor@tensin.name>2026-06-21 07:49:34 +0200
committerEgor Tensin <egor@tensin.name>2026-06-21 07:49:34 +0200
commit3421d2ed73f43159e8fa04943e13ca9c0f0404d3 (patch)
tree5302ee9ac0ea902e85ec8f600b4f58984576e02b /node_modules/undici/lib
parentMerge pull request #10 from egor-tensin/dependabot/github_actions/actions/che... (diff)
downloadcleanup-path-3421d2ed73f43159e8fa04943e13ca9c0f0404d3.tar.gz
cleanup-path-3421d2ed73f43159e8fa04943e13ca9c0f0404d3.zip
bump dependencies
Diffstat (limited to 'node_modules/undici/lib')
-rw-r--r--node_modules/undici/lib/dispatcher/agent.js1
-rw-r--r--node_modules/undici/lib/dispatcher/client-h1.js163
-rw-r--r--node_modules/undici/lib/dispatcher/dispatcher-base.js1
-rw-r--r--node_modules/undici/lib/web/cookies/parse.js39
-rw-r--r--node_modules/undici/lib/web/websocket/receiver.js39
-rw-r--r--node_modules/undici/lib/web/websocket/websocket.js5
6 files changed, 192 insertions, 56 deletions
diff --git a/node_modules/undici/lib/dispatcher/agent.js b/node_modules/undici/lib/dispatcher/agent.js
index db2f817..90b46fe 100644
--- a/node_modules/undici/lib/dispatcher/agent.js
+++ b/node_modules/undici/lib/dispatcher/agent.js
@@ -24,7 +24,6 @@ function defaultFactory (origin, opts) {
class Agent extends DispatcherBase {
constructor ({ factory = defaultFactory, maxRedirections = 0, connect, ...options } = {}) {
-
if (typeof factory !== 'function') {
throw new InvalidArgumentError('factory must be a function.')
}
diff --git a/node_modules/undici/lib/dispatcher/client-h1.js b/node_modules/undici/lib/dispatcher/client-h1.js
index 2b8fa05..9455517 100644
--- a/node_modules/undici/lib/dispatcher/client-h1.js
+++ b/node_modules/undici/lib/dispatcher/client-h1.js
@@ -57,6 +57,9 @@ const EMPTY_BUF = Buffer.alloc(0)
const FastBuffer = Buffer[Symbol.species]
const addListener = util.addListener
const removeAllListeners = util.removeAllListeners
+const kIdleSocketValidation = Symbol('kIdleSocketValidation')
+const kIdleSocketValidationTimeout = Symbol('kIdleSocketValidationTimeout')
+const kSocketUsed = Symbol('kSocketUsed')
let extractBody
@@ -279,29 +282,71 @@ class Parser {
const offset = llhttp.llhttp_get_error_pos(this.ptr) - currentBufferPtr
- if (ret === constants.ERROR.PAUSED_UPGRADE) {
- this.onUpgrade(data.slice(offset))
- } else if (ret === constants.ERROR.PAUSED) {
- this.paused = true
- socket.unshift(data.slice(offset))
- } else if (ret !== constants.ERROR.OK) {
- const ptr = llhttp.llhttp_get_error_reason(this.ptr)
- let message = ''
- /* istanbul ignore else: difficult to make a test case for */
- if (ptr) {
- const len = new Uint8Array(llhttp.memory.buffer, ptr).indexOf(0)
- message =
- 'Response does not match the HTTP/1.1 protocol (' +
- Buffer.from(llhttp.memory.buffer, ptr, len).toString() +
- ')'
+ if (ret !== constants.ERROR.OK) {
+ const body = data.subarray(offset)
+
+ if (ret === constants.ERROR.PAUSED_UPGRADE) {
+ this.onUpgrade(body)
+ } else if (ret === constants.ERROR.PAUSED) {
+ this.paused = true
+ socket.unshift(body)
+ } else {
+ throw this.createError(ret, body)
}
- throw new HTTPParserError(message, constants.ERROR[ret], data.slice(offset))
}
} catch (err) {
util.destroy(socket, err)
}
}
+ finish () {
+ assert(currentParser === null)
+ assert(this.ptr != null)
+ assert(!this.paused)
+
+ const { llhttp } = this
+
+ let ret
+
+ try {
+ currentParser = this
+ ret = llhttp.llhttp_finish(this.ptr)
+ } finally {
+ currentParser = null
+ }
+
+ if (ret === constants.ERROR.OK) {
+ return null
+ }
+
+ if (ret === constants.ERROR.PAUSED || ret === constants.ERROR.PAUSED_UPGRADE) {
+ this.paused = true
+ return null
+ }
+
+ return this.createError(ret, EMPTY_BUF)
+ }
+
+ createError (ret, data) {
+ const { llhttp, contentLength, bytesRead } = this
+
+ if (contentLength && bytesRead !== parseInt(contentLength, 10)) {
+ return new ResponseContentLengthMismatchError()
+ }
+
+ const ptr = llhttp.llhttp_get_error_reason(this.ptr)
+ let message = ''
+ if (ptr) {
+ const len = new Uint8Array(llhttp.memory.buffer, ptr).indexOf(0)
+ message =
+ 'Response does not match the HTTP/1.1 protocol (' +
+ Buffer.from(llhttp.memory.buffer, ptr, len).toString() +
+ ')'
+ }
+
+ return new HTTPParserError(message, constants.ERROR[ret], data)
+ }
+
destroy () {
assert(this.ptr != null)
assert(currentParser == null)
@@ -329,6 +374,11 @@ class Parser {
return -1
}
+ if (client[kRunning] === 0) {
+ util.destroy(socket, new SocketError('bad response', util.getSocketInfo(socket)))
+ return -1
+ }
+
const request = client[kQueue][client[kRunningIdx]]
if (!request) {
return -1
@@ -432,6 +482,11 @@ class Parser {
return -1
}
+ if (client[kRunning] === 0) {
+ util.destroy(socket, new SocketError('bad response', util.getSocketInfo(socket)))
+ return -1
+ }
+
const request = client[kQueue][client[kRunningIdx]]
/* istanbul ignore next: difficult to make a test case for */
@@ -605,6 +660,7 @@ class Parser {
request.onComplete(headers)
client[kQueue][client[kRunningIdx]++] = null
+ socket[kSocketUsed] = true
if (socket[kWriting]) {
assert(client[kRunning] === 0)
@@ -663,6 +719,9 @@ async function connectH1 (client, socket) {
socket[kWriting] = false
socket[kReset] = false
socket[kBlocking] = false
+ socket[kIdleSocketValidation] = 0
+ socket[kIdleSocketValidationTimeout] = null
+ socket[kSocketUsed] = false
socket[kParser] = new Parser(client, socket, llhttpInstance)
addListener(socket, 'error', function (err) {
@@ -673,8 +732,11 @@ async function connectH1 (client, socket) {
// On Mac OS, we get an ECONNRESET even if there is a full body to be forwarded
// to the user.
if (err.code === 'ECONNRESET' && parser.statusCode && !parser.shouldKeepAlive) {
- // We treat all incoming data so for as a valid response.
- parser.onMessageComplete()
+ const parserErr = parser.finish()
+ if (parserErr) {
+ this[kError] = parserErr
+ this[kClient][kOnError](parserErr)
+ }
return
}
@@ -693,8 +755,10 @@ async function connectH1 (client, socket) {
const parser = this[kParser]
if (parser.statusCode && !parser.shouldKeepAlive) {
- // We treat all incoming data so far as a valid response.
- parser.onMessageComplete()
+ const parserErr = parser.finish()
+ if (parserErr) {
+ util.destroy(this, parserErr)
+ }
return
}
@@ -704,10 +768,11 @@ async function connectH1 (client, socket) {
const client = this[kClient]
const parser = this[kParser]
+ clearIdleSocketValidation(this)
+
if (parser) {
if (!this[kError] && parser.statusCode && !parser.shouldKeepAlive) {
- // We treat all incoming data so far as a valid response.
- parser.onMessageComplete()
+ this[kError] = parser.finish() || this[kError]
}
this[kParser].destroy()
@@ -770,7 +835,7 @@ async function connectH1 (client, socket) {
return socket.destroyed
},
busy (request) {
- if (socket[kWriting] || socket[kReset] || socket[kBlocking]) {
+ if (socket[kWriting] || socket[kReset] || socket[kBlocking] || socket[kIdleSocketValidation] === 1) {
return true
}
@@ -808,6 +873,31 @@ async function connectH1 (client, socket) {
}
}
+function clearIdleSocketValidation (socket) {
+ if (socket[kIdleSocketValidationTimeout]) {
+ clearTimeout(socket[kIdleSocketValidationTimeout])
+ socket[kIdleSocketValidationTimeout] = null
+ }
+
+ socket[kIdleSocketValidation] = 0
+}
+
+function scheduleIdleSocketValidation (client, socket) {
+ socket[kIdleSocketValidation] = 1
+ socket[kIdleSocketValidationTimeout] = setTimeout(() => {
+ socket[kIdleSocketValidationTimeout] = null
+ socket[kIdleSocketValidation] = 2
+
+ if (client[kSocket] === socket && !socket.destroyed) {
+ client[kResume]()
+ }
+ }, 0)
+ socket[kIdleSocketValidationTimeout].unref?.()
+}
+
+/**
+ * @param {import('./client.js')} client
+ */
function resumeH1 (client) {
const socket = client[kSocket]
@@ -822,6 +912,32 @@ function resumeH1 (client) {
socket[kNoRef] = false
}
+ if (client[kRunning] === 0 && client[kPending] > 0 && socket[kSocketUsed]) {
+ if (socket[kIdleSocketValidation] === 0) {
+ scheduleIdleSocketValidation(client, socket)
+ socket[kParser].readMore()
+ if (socket.destroyed) {
+ return
+ }
+ return
+ }
+
+ if (socket[kIdleSocketValidation] === 1) {
+ socket[kParser].readMore()
+ if (socket.destroyed) {
+ return
+ }
+ return
+ }
+ }
+
+ if (client[kRunning] === 0) {
+ socket[kParser].readMore()
+ if (socket.destroyed) {
+ return
+ }
+ }
+
if (client[kSize] === 0) {
if (socket[kParser].timeoutType !== TIMEOUT_KEEP_ALIVE) {
socket[kParser].setTimeout(client[kKeepAliveTimeoutValue], TIMEOUT_KEEP_ALIVE)
@@ -915,6 +1031,7 @@ function writeH1 (client, request) {
}
const socket = client[kSocket]
+ clearIdleSocketValidation(socket)
const abort = (err) => {
if (request.aborted || request.completed) {
diff --git a/node_modules/undici/lib/dispatcher/dispatcher-base.js b/node_modules/undici/lib/dispatcher/dispatcher-base.js
index c999b2c..371a3ea 100644
--- a/node_modules/undici/lib/dispatcher/dispatcher-base.js
+++ b/node_modules/undici/lib/dispatcher/dispatcher-base.js
@@ -26,6 +26,7 @@ class DispatcherBase extends Dispatcher {
get webSocketOptions () {
return {
+ maxFragments: this[kWebSocketOptions].maxFragments ?? 131072,
maxPayloadSize: this[kWebSocketOptions].maxPayloadSize ?? 128 * 1024 * 1024
}
}
diff --git a/node_modules/undici/lib/web/cookies/parse.js b/node_modules/undici/lib/web/cookies/parse.js
index 3c48c26..b3c25fb 100644
--- a/node_modules/undici/lib/web/cookies/parse.js
+++ b/node_modules/undici/lib/web/cookies/parse.js
@@ -275,32 +275,25 @@ function parseUnparsedAttributes (unparsedAttributes, cookieAttributeList = {})
// If the attribute-name case-insensitively matches the string
// "SameSite", the user agent MUST process the cookie-av as follows:
- // 1. Let enforcement be "Default".
- let enforcement = 'Default'
-
const attributeValueLowercase = attributeValue.toLowerCase()
- // 2. If cookie-av's attribute-value is a case-insensitive match for
- // "None", set enforcement to "None".
- if (attributeValueLowercase.includes('none')) {
- enforcement = 'None'
- }
- // 3. If cookie-av's attribute-value is a case-insensitive match for
- // "Strict", set enforcement to "Strict".
- if (attributeValueLowercase.includes('strict')) {
- enforcement = 'Strict'
+ // 1. If cookie-av's attribute-value is a case-insensitive match for
+ // "None", append an attribute to the cookie-attribute-list with an
+ // attribute-name of "SameSite" and an attribute-value of "None".
+ if (attributeValueLowercase === 'none') {
+ cookieAttributeList.sameSite = 'None'
+ } else if (attributeValueLowercase === 'strict') {
+ // 2. If cookie-av's attribute-value is a case-insensitive match for
+ // "Strict", append an attribute to the cookie-attribute-list with
+ // an attribute-name of "SameSite" and an attribute-value of
+ // "Strict".
+ cookieAttributeList.sameSite = 'Strict'
+ } else if (attributeValueLowercase === 'lax') {
+ // 3. If cookie-av's attribute-value is a case-insensitive match for
+ // "Lax", append an attribute to the cookie-attribute-list with an
+ // attribute-name of "SameSite" and an attribute-value of "Lax".
+ cookieAttributeList.sameSite = 'Lax'
}
-
- // 4. If cookie-av's attribute-value is a case-insensitive match for
- // "Lax", set enforcement to "Lax".
- if (attributeValueLowercase.includes('lax')) {
- enforcement = 'Lax'
- }
-
- // 5. Append an attribute to the cookie-attribute-list with an
- // attribute-name of "SameSite" and an attribute-value of
- // enforcement.
- cookieAttributeList.sameSite = enforcement
} else {
cookieAttributeList.unparsed ??= []
diff --git a/node_modules/undici/lib/web/websocket/receiver.js b/node_modules/undici/lib/web/websocket/receiver.js
index 53e427e..a7dea7f 100644
--- a/node_modules/undici/lib/web/websocket/receiver.js
+++ b/node_modules/undici/lib/web/websocket/receiver.js
@@ -20,6 +20,11 @@ const { closeWebSocketConnection } = require('./connection')
const { PerMessageDeflate } = require('./permessage-deflate')
const { MessageSizeExceededError } = require('../../core/errors')
+function failWebsocketConnectionWithCode (ws, code, reason) {
+ closeWebSocketConnection(ws, code, reason, Buffer.byteLength(reason))
+ failWebsocketConnection(ws, reason)
+}
+
// This code was influenced by ws released under the MIT license.
// Copyright (c) 2011 Einar Otto Stangvik <einaros@gmail.com>
// Copyright (c) 2013 Arnout Kazemier and contributors
@@ -40,18 +45,22 @@ class ByteParser extends Writable {
#extensions
/** @type {number} */
+ #maxFragments
+
+ /** @type {number} */
#maxPayloadSize
/**
* @param {import('./websocket').WebSocket} ws
* @param {Map<string, string>|null} extensions
- * @param {{ maxPayloadSize?: number }} [options]
+ * @param {{ maxFragments?: number, maxPayloadSize?: number }} [options]
*/
constructor (ws, extensions, options = {}) {
super()
this.ws = ws
this.#extensions = extensions == null ? new Map() : extensions
+ this.#maxFragments = options.maxFragments ?? 0
this.#maxPayloadSize = options.maxPayloadSize ?? 0
if (this.#extensions.has('permessage-deflate')) {
@@ -75,9 +84,9 @@ class ByteParser extends Writable {
if (
this.#maxPayloadSize > 0 &&
!isControlFrame(this.#info.opcode) &&
- this.#info.payloadLength > this.#maxPayloadSize
+ this.#info.payloadLength + this.#fragmentsBytes > this.#maxPayloadSize
) {
- failWebsocketConnection(this.ws, 'Payload size exceeds maximum allowed size')
+ failWebsocketConnectionWithCode(this.ws, 1009, 'Payload size exceeds maximum allowed size')
return false
}
@@ -242,10 +251,12 @@ class ByteParser extends Writable {
this.#state = parserStates.INFO
} else {
if (!this.#info.compressed) {
- this.writeFragments(body)
+ if (!this.writeFragments(body)) {
+ return
+ }
if (this.#maxPayloadSize > 0 && this.#fragmentsBytes > this.#maxPayloadSize) {
- failWebsocketConnection(this.ws, new MessageSizeExceededError().message)
+ failWebsocketConnectionWithCode(this.ws, 1009, new MessageSizeExceededError().message)
return
}
@@ -264,14 +275,17 @@ class ByteParser extends Writable {
this.#info.fin,
(error, data) => {
if (error) {
- failWebsocketConnection(this.ws, error.message)
+ const code = error instanceof MessageSizeExceededError ? 1009 : 1007
+ failWebsocketConnectionWithCode(this.ws, code, error.message)
return
}
- this.writeFragments(data)
+ if (!this.writeFragments(data)) {
+ return
+ }
if (this.#maxPayloadSize > 0 && this.#fragmentsBytes > this.#maxPayloadSize) {
- failWebsocketConnection(this.ws, new MessageSizeExceededError().message)
+ failWebsocketConnectionWithCode(this.ws, 1009, new MessageSizeExceededError().message)
return
}
@@ -341,8 +355,17 @@ class ByteParser extends Writable {
}
writeFragments (fragment) {
+ if (
+ this.#maxFragments > 0 &&
+ this.#fragments.length === this.#maxFragments
+ ) {
+ failWebsocketConnectionWithCode(this.ws, 1008, 'Too many message fragments')
+ return false
+ }
+
this.#fragmentsBytes += fragment.length
this.#fragments.push(fragment)
+ return true
}
consumeFragments () {
diff --git a/node_modules/undici/lib/web/websocket/websocket.js b/node_modules/undici/lib/web/websocket/websocket.js
index ccedb79..80991e9 100644
--- a/node_modules/undici/lib/web/websocket/websocket.js
+++ b/node_modules/undici/lib/web/websocket/websocket.js
@@ -435,9 +435,12 @@ class WebSocket extends EventTarget {
// once this happens, the connection is open
this[kResponse] = response
- const maxPayloadSize = this[kController]?.dispatcher?.webSocketOptions?.maxPayloadSize
+ const webSocketOptions = this[kController]?.dispatcher?.webSocketOptions
+ const maxFragments = webSocketOptions?.maxFragments
+ const maxPayloadSize = webSocketOptions?.maxPayloadSize
const parser = new ByteParser(this, parsedExtensions, {
+ maxFragments,
maxPayloadSize
})
parser.on('drain', onParserDrain)