node_modules: update (#358)

Co-authored-by: dawidd6 <9713907+dawidd6@users.noreply.github.com>
This commit is contained in:
Dawid Dziurlaanddawidd6 authored and GitHub committed 2026-10-29 12:32:12 +01:00
1 parent 0d58990203
commit b6b2177ec4
13 files changed
+152 -32

No files matched your search

+3 -3
View File
@@ -128,9 +128,9 @@
} }
}, },
"node_modules/undici": { "node_modules/undici": {
"version": "6.29.0", "version": "6.29.1",
"resolved": "https://registry.npmjs.org/undici/-/undici-6.29.0.tgz", "resolved": "https://registry.npmjs.org/undici/-/undici-6.29.1.tgz",
"integrity": "sha512-R+RODBqp6i2pPflGdq+xIOUkl+RNfGgHwoinecKu/JCuf2uO06cOKoDbI2P7Dn6KcswdKwrczbU6IYJ6K8X+wg==", "integrity": "sha512-EvRlUt/QqAEvXdQeRMYUfwOQsfzpDztykrG2owGEE/rBOBoYv9uF39ZzsNDh4lTFHhTr2XOZjKkYgueD12ZmGA==",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=18.17" "node": ">=18.17"
+2
View File
@@ -29,6 +29,8 @@ Returns: `Client`
* **webSocket** `WebSocketOptions` (optional) - WebSocket-specific configuration options. * **webSocket** `WebSocketOptions` (optional) - WebSocket-specific configuration options.
* **maxFragments** `number` (optional) - Default: `131072` - Maximum number of fragments in a message. Set to 0 to disable the limit. * **maxFragments** `number` (optional) - Default: `131072` - Maximum number of fragments in a message. Set to 0 to disable the limit.
* **maxPayloadSize** `number` (optional) - Default: `134217728` (128 MB) - Maximum allowed payload size in bytes for WebSocket messages. Applied to uncompressed messages, compressed frame payloads, and decompressed (permessage-deflate) messages. Set to 0 to disable the limit. * **maxPayloadSize** `number` (optional) - Default: `134217728` (128 MB) - Maximum allowed payload size in bytes for WebSocket messages. Applied to uncompressed messages, compressed frame payloads, and decompressed (permessage-deflate) messages. Set to 0 to disable the limit.
* **eventSource** `EventSourceOptions` (optional) - EventSource-specific configuration options.
* **maxEventSize** `number` (optional) - The maximum total bytes of data and metadata field values retained for a pending EventSource event. Each unfinished line is separately limited to this value plus seven syntax bytes. The number of `data:` lines per event and input chunks buffered for an unfinished line are each limited to `min(131072, max(1024, floor(maxEventSize / 128)))` to bound object overhead. Set to `0` to disable these limits. Default: `buffer.kStringMaxLength`.
* **pipelining** `number | null` (optional) - Default: `1` - The amount of concurrent requests to be sent over the single TCP/TLS connection according to [RFC7230](https://tools.ietf.org/html/rfc7230#section-6.3.2). Carefully consider your workload and environment before enabling concurrent requests as pipelining may reduce performance if used incorrectly. Pipelining is sensitive to network stack settings as well as head of line blocking caused by e.g. long running requests. Set to `0` to disable keep-alive connections. * **pipelining** `number | null` (optional) - Default: `1` - The amount of concurrent requests to be sent over the single TCP/TLS connection according to [RFC7230](https://tools.ietf.org/html/rfc7230#section-6.3.2). Carefully consider your workload and environment before enabling concurrent requests as pipelining may reduce performance if used incorrectly. Pipelining is sensitive to network stack settings as well as head of line blocking caused by e.g. long running requests. Set to `0` to disable keep-alive connections.
* **connect** `ConnectOptions | Function | null` (optional) - Default: `null`. * **connect** `ConnectOptions | Function | null` (optional) - Default: `null`.
* **strictContentLength** `Boolean` (optional) - Default: `true` - Whether to treat request content length mismatches as errors. If true, an error is thrown when the request content-length header doesn't match the length of the request body. * **strictContentLength** `Boolean` (optional) - Default: `true` - Whether to treat request content length mismatches as errors. If true, an error is thrown when the request content-length header doesn't match the length of the request body.
+6 -2
View File
@@ -23,7 +23,7 @@ function defaultFactory (origin, opts) {
} }
class Agent extends DispatcherBase { class Agent extends DispatcherBase {
constructor ({ factory = defaultFactory, maxRedirections = 0, connect, ...options } = {}) { constructor ({ factory = defaultFactory, maxRedirections = 0, connect, tls, ...options } = {}) {
if (typeof factory !== 'function') { if (typeof factory !== 'function') {
throw new InvalidArgumentError('factory must be a function.') throw new InvalidArgumentError('factory must be a function.')
} }
@@ -42,11 +42,15 @@ class Agent extends DispatcherBase {
connect = { ...connect } connect = { ...connect }
} }
if (tls && typeof tls !== 'function') {
tls = { ...tls }
}
this[kInterceptors] = options.interceptors?.Agent && Array.isArray(options.interceptors.Agent) this[kInterceptors] = options.interceptors?.Agent && Array.isArray(options.interceptors.Agent)
? options.interceptors.Agent ? options.interceptors.Agent
: [createRedirectInterceptor({ maxRedirections })] : [createRedirectInterceptor({ maxRedirections })]
this[kOptions] = { ...util.deepClone(options), connect } this[kOptions] = { ...util.deepClone(options), connect, tls }
this[kOptions].interceptors = options.interceptors this[kOptions].interceptors = options.interceptors
? { ...options.interceptors } ? { ...options.interceptors }
: undefined : undefined
-2
View File
@@ -727,8 +727,6 @@ async function connectH1 (client, socket) {
socket[kParser] = new Parser(client, socket, llhttpInstance) socket[kParser] = new Parser(client, socket, llhttpInstance)
addListener(socket, 'error', function (err) { addListener(socket, 'error', function (err) {
assert(err.code !== 'ERR_TLS_CERT_ALTNAME_INVALID')
const parser = this[kParser] const parser = this[kParser]
// On Mac OS, we get an ECONNRESET even if there is a full body to be forwarded // On Mac OS, we get an ECONNRESET even if there is a full body to be forwarded
-4
View File
@@ -136,8 +136,6 @@ async function connectH2 (client, socket) {
socket[kHTTP2Session] = session socket[kHTTP2Session] = session
util.addListener(socket, 'error', function (err) { util.addListener(socket, 'error', function (err) {
assert(err.code !== 'ERR_TLS_CERT_ALTNAME_INVALID')
this[kError] = err this[kError] = err
this[kClient][kOnError](err) this[kClient][kOnError](err)
@@ -211,8 +209,6 @@ function resumeH2 (client) {
} }
function onHttp2SessionError (err) { function onHttp2SessionError (err) {
assert(err.code !== 'ERR_TLS_CERT_ALTNAME_INVALID')
this[kSocket][kError] = err this[kSocket][kError] = err
this[kClient][kOnError](err) this[kClient][kOnError](err)
} }
+3 -2
View File
@@ -107,9 +107,10 @@ class Client extends DispatcherBase {
// h2 // h2
maxConcurrentStreams, maxConcurrentStreams,
allowH2, allowH2,
webSocket webSocket,
eventSource
} = {}) { } = {}) {
super({ webSocket }) super({ webSocket, eventSource })
if (keepAlive !== undefined) { if (keepAlive !== undefined) {
throw new InvalidArgumentError('unsupported keepAlive, use pipelining=0 instead') throw new InvalidArgumentError('unsupported keepAlive, use pipelining=0 instead')
+9
View File
@@ -1,5 +1,6 @@
'use strict' 'use strict'
const buffer = require('node:buffer')
const Dispatcher = require('./dispatcher') const Dispatcher = require('./dispatcher')
const { const {
ClientDestroyedError, ClientDestroyedError,
@@ -12,6 +13,7 @@ const kOnDestroyed = Symbol('onDestroyed')
const kOnClosed = Symbol('onClosed') const kOnClosed = Symbol('onClosed')
const kInterceptedDispatch = Symbol('Intercepted Dispatch') const kInterceptedDispatch = Symbol('Intercepted Dispatch')
const kWebSocketOptions = Symbol('webSocketOptions') const kWebSocketOptions = Symbol('webSocketOptions')
const kEventSourceOptions = Symbol('eventSourceOptions')
class DispatcherBase extends Dispatcher { class DispatcherBase extends Dispatcher {
constructor (opts) { constructor (opts) {
@@ -22,6 +24,7 @@ class DispatcherBase extends Dispatcher {
this[kClosed] = false this[kClosed] = false
this[kOnClosed] = [] this[kOnClosed] = []
this[kWebSocketOptions] = opts?.webSocket ?? {} this[kWebSocketOptions] = opts?.webSocket ?? {}
this[kEventSourceOptions] = opts?.eventSource ?? {}
} }
get webSocketOptions () { get webSocketOptions () {
@@ -31,6 +34,12 @@ class DispatcherBase extends Dispatcher {
} }
} }
get eventSourceOptions () {
return {
maxEventSize: this[kEventSourceOptions].maxEventSize ?? buffer.kStringMaxLength
}
}
get destroyed () { get destroyed () {
return this[kDestroyed] return this[kDestroyed]
} }
+90 -9
View File
@@ -1,4 +1,5 @@
'use strict' 'use strict'
const buffer = require('node:buffer')
const { Transform } = require('node:stream') const { Transform } = require('node:stream')
const { isASCIINumber, isValidLastEventId } = require('./util') const { isASCIINumber, isValidLastEventId } = require('./util')
@@ -23,11 +24,19 @@ const COLON = 0x3A
*/ */
const SPACE = 0x20 const SPACE = 0x20
const defaultMaxEventSize = buffer.kStringMaxLength
const DATA = Buffer.from('data') const DATA = Buffer.from('data')
const EVENT = Buffer.from('event') const EVENT = Buffer.from('event')
const ID = Buffer.from('id') const ID = Buffer.from('id')
const RETRY = Buffer.from('retry') const RETRY = Buffer.from('retry')
// Each data field creates a string node, and each unconsumed input chunk keeps
// a Buffer wrapper. The absolute ceiling protects the large default byte limit.
const minParserItems = 1024
const maxParserItems = 131072
const bytesPerParserItem = 128
function isASCIINumberBytes (buffer, start) { function isASCIINumberBytes (buffer, start) {
if (start >= buffer.length) { if (start >= buffer.length) {
return false return false
@@ -66,6 +75,12 @@ function isFieldName (line, length, field) {
return true return true
} }
function createMaxEventSizeExceededError () {
const error = new Error('EventSource message size exceeded')
error.aborted = false
return error
}
/** /**
* @typedef {object} EventSourceStreamEvent * @typedef {object} EventSourceStreamEvent
* @type {object} * @type {object}
@@ -114,6 +129,13 @@ class EventSourceStream extends Transform {
pos = 0 pos = 0
lineChunkIndex = 0 lineChunkIndex = 0
linePos = 0 linePos = 0
lineLength = 0
eventSize = 0
eventFieldSizes = { data: 0, event: 0, id: 0, retry: 0 }
dataLineCount = 0
maxEventSize
maxDataLines
maxBufferedChunks
event = { event = {
data: undefined, data: undefined,
@@ -124,6 +146,7 @@ class EventSourceStream extends Transform {
/** /**
* @param {object} options * @param {object} options
* @param {number} [options.maxEventSize]
* @param {eventSourceSettings} options.eventSourceSettings * @param {eventSourceSettings} options.eventSourceSettings
* @param {Function} [options.push] * @param {Function} [options.push]
*/ */
@@ -135,6 +158,12 @@ class EventSourceStream extends Transform {
super(options) super(options)
this.state = options.eventSourceSettings || {} this.state = options.eventSourceSettings || {}
this.maxEventSize = options.maxEventSize ?? defaultMaxEventSize
const parserItemLimit = this.maxEventSize > 0
? Math.min(maxParserItems, Math.max(minParserItems, Math.floor(this.maxEventSize / bytesPerParserItem)))
: 0
this.maxDataLines = parserItemLimit
this.maxBufferedChunks = parserItemLimit
if (options.push) { if (options.push) {
this.push = options.push this.push = options.push
} }
@@ -152,6 +181,14 @@ class EventSourceStream extends Transform {
return return
} }
// A leading line ending can immediately release the currently buffered
// line, even when it uses the final allowed chunk slot.
if (this.maxEventSize > 0 &&
this.chunks.length >= this.maxBufferedChunks &&
chunk[0] !== LF && chunk[0] !== CR) {
callback(createMaxEventSizeExceededError())
return
}
this.chunks.push(chunk) this.chunks.push(chunk)
// Strip leading byte-order-mark if we opened the stream and started // Strip leading byte-order-mark if we opened the stream and started
@@ -230,7 +267,12 @@ class EventSourceStream extends Transform {
// In any case, we can process the line as we reached an // In any case, we can process the line as we reached an
// end-of-line character // end-of-line character
try {
this.parseLine(this.readLine(), this.event) this.parseLine(this.readLine(), this.event)
} catch (error) {
callback(error)
return
}
this.consumeCurrentByte() this.consumeCurrentByte()
// A line was processed and this could be the end of the event. We need // A line was processed and this could be the end of the event. We need
// to check if the next line is empty to determine if the event is // to check if the next line is empty to determine if the event is
@@ -239,15 +281,28 @@ class EventSourceStream extends Transform {
continue continue
} }
// readLine() is only called after CR or LF. Reject an unfinished line
// before it can retain an unbounded number of decoded chunks. The seven
// extra bytes allow the longest field prefix ("event: " or "retry: ").
if (this.maxEventSize > 0 && ++this.lineLength > this.maxEventSize + 7) {
callback(createMaxEventSizeExceededError())
return
}
this.advanceCursor() this.advanceCursor()
} }
callback() callback()
} }
_destroy (error, callback) {
this.chunks.length = 0
this.clearEvent()
super._destroy(error, callback)
}
/** /**
* @param {Buffer} line * @param {Buffer} line
* @param {EventStreamEvent} event * @param {EventSourceStreamEvent} event
*/ */
parseLine (line, event) { parseLine (line, event) {
// If the line is empty (a blank line) // If the line is empty (a blank line)
@@ -281,6 +336,13 @@ class EventSourceStream extends Transform {
} }
if (isFieldName(line, fieldLength, DATA)) { if (isFieldName(line, fieldLength, DATA)) {
if (this.maxEventSize > 0 && this.dataLineCount >= this.maxDataLines) {
throw createMaxEventSizeExceededError()
}
const valueBytes = line.length - valueStart
const dataSize = this.eventFieldSizes.data + (event.data === undefined ? 0 : 1) + valueBytes
this.setEventFieldSize('data', dataSize)
const value = line.toString('utf8', valueStart) const value = line.toString('utf8', valueStart)
if (event.data === undefined) { if (event.data === undefined) {
@@ -288,11 +350,13 @@ class EventSourceStream extends Transform {
} else { } else {
event.data += `\n${value}` event.data += `\n${value}`
} }
this.dataLineCount++
return return
} }
if (isFieldName(line, fieldLength, RETRY)) { if (isFieldName(line, fieldLength, RETRY)) {
if (isASCIINumberBytes(line, valueStart)) { if (isASCIINumberBytes(line, valueStart)) {
this.setEventFieldSize('retry', line.length - valueStart)
event.retry = line.toString('utf8', valueStart) event.retry = line.toString('utf8', valueStart)
} }
return return
@@ -300,18 +364,27 @@ class EventSourceStream extends Transform {
if (isFieldName(line, fieldLength, ID)) { if (isFieldName(line, fieldLength, ID)) {
if (isValidLastEventIdBytes(line, valueStart)) { if (isValidLastEventIdBytes(line, valueStart)) {
this.setEventFieldSize('id', line.length - valueStart)
event.id = line.toString('utf8', valueStart) event.id = line.toString('utf8', valueStart)
} }
return return
} }
if (isFieldName(line, fieldLength, EVENT)) { if (isFieldName(line, fieldLength, EVENT)) {
const value = line.toString('utf8', valueStart) if (valueStart < line.length) {
this.setEventFieldSize('event', line.length - valueStart)
event.event = line.toString('utf8', valueStart)
}
}
}
if (value.length > 0) { setEventFieldSize (field, size) {
event.event = value const eventSize = this.eventSize - this.eventFieldSizes[field] + size
} if (this.maxEventSize > 0 && eventSize > this.maxEventSize) {
throw createMaxEventSizeExceededError()
} }
this.eventSize = eventSize
this.eventFieldSizes[field] = size
} }
/** /**
@@ -344,6 +417,12 @@ class EventSourceStream extends Transform {
this.event.event = undefined this.event.event = undefined
this.event.id = undefined this.event.id = undefined
this.event.retry = undefined this.event.retry = undefined
this.eventSize = 0
this.dataLineCount = 0
this.eventFieldSizes.data = 0
this.eventFieldSizes.event = 0
this.eventFieldSizes.id = 0
this.eventFieldSizes.retry = 0
} }
hasPendingEvent () { hasPendingEvent () {
@@ -379,14 +458,16 @@ class EventSourceStream extends Transform {
syncLineStartToCursor () { syncLineStartToCursor () {
this.lineChunkIndex = this.chunkIndex this.lineChunkIndex = this.chunkIndex
this.linePos = this.pos this.linePos = this.pos
this.lineLength = 0
this.dropConsumedChunks() this.dropConsumedChunks()
} }
dropConsumedChunks () { dropConsumedChunks () {
while (this.lineChunkIndex > 0) { if (this.lineChunkIndex > 0) {
this.chunks.shift() const consumed = this.lineChunkIndex
this.lineChunkIndex-- this.chunks.splice(0, consumed)
this.chunkIndex-- this.lineChunkIndex = 0
this.chunkIndex -= consumed
} }
if (this.chunkIndex === this.chunks.length) { if (this.chunkIndex === this.chunks.length) {
+4 -1
View File
@@ -11,6 +11,7 @@ const { isNetworkError } = require('../fetch/response')
const { delay } = require('./util') const { delay } = require('./util')
const { kEnumerableProperty } = require('../../core/util') const { kEnumerableProperty } = require('../../core/util')
const { environmentSettingsObject } = require('../fetch/util') const { environmentSettingsObject } = require('../fetch/util')
const { getGlobalDispatcher } = require('../../global')
let experimentalWarned = false let experimentalWarned = false
@@ -292,6 +293,7 @@ class EventSource extends EventTarget {
const eventSourceStream = new EventSourceStream({ const eventSourceStream = new EventSourceStream({
eventSourceSettings: this.#state, eventSourceSettings: this.#state,
maxEventSize: this.#dispatcher.eventSourceOptions?.maxEventSize,
push: (event) => { push: (event) => {
this.dispatchEvent(createFastMessageEvent( this.dispatchEvent(createFastMessageEvent(
event.type, event.type,
@@ -470,7 +472,8 @@ webidl.converters.EventSourceInitDict = webidl.dictionaryConverter([
}, },
{ {
key: 'dispatcher', // undici only key: 'dispatcher', // undici only
converter: webidl.converters.any converter: webidl.converters.any,
defaultValue: () => getGlobalDispatcher()
} }
]) ])
+6 -8
View File
@@ -1950,8 +1950,10 @@ async function httpNetworkFetch (
// 19. Run these steps in parallel: // 19. Run these steps in parallel:
// 1. Run these steps, but abort when fetchParams is canceled: // 1. Run these steps, but abort when fetchParams is canceled:
fetchParams.controller.onAborted = onAborted if (!fetchParams.controller.resume) {
fetchParams.controller.on('terminated', onAborted) fetchParams.controller.on('terminated', onAborted)
}
fetchParams.controller.resume = async () => { fetchParams.controller.resume = async () => {
// 1. While true // 1. While true
while (true) { while (true) {
@@ -2120,8 +2122,6 @@ async function httpNetworkFetch (
} }
location = headersList.get('location', true) location = headersList.get('location', true)
this.body = new Readable({ read: resume })
const decoders = [] const decoders = []
const willFollow = location && request.redirect === 'follow' && const willFollow = location && request.redirect === 'follow' &&
@@ -2139,7 +2139,7 @@ async function httpNetworkFetch (
// CVE fix similar to urllib3 (GHSA-gm62-xv2j-4w53) and curl (CVE-2022-32206). // CVE fix similar to urllib3 (GHSA-gm62-xv2j-4w53) and curl (CVE-2022-32206).
const maxContentEncodings = 5 const maxContentEncodings = 5
if (codings.length > maxContentEncodings) { if (codings.length > maxContentEncodings) {
reject(new Error(`too many content-encodings in response: ${codings.length}, maximum allowed is ${maxContentEncodings}`)) this.abort(new Error(`too many content-encodings in response: ${codings.length}, maximum allowed is ${maxContentEncodings}`))
return true return true
} }
@@ -2172,6 +2172,8 @@ async function httpNetworkFetch (
} }
} }
this.body = new Readable({ read: resume })
const onError = this.onError.bind(this) const onError = this.onError.bind(this)
resolve({ resolve({
@@ -2218,10 +2220,6 @@ async function httpNetworkFetch (
fetchParams.controller.off('terminated', this.abort) fetchParams.controller.off('terminated', this.abort)
} }
if (fetchParams.controller.onAborted) {
fetchParams.controller.off('terminated', fetchParams.controller.onAborted)
}
fetchParams.controller.ended = true fetchParams.controller.ended = true
this.body.push(null) this.body.push(null)
+14
View File
@@ -249,6 +249,20 @@ class ByteParser extends Writable {
if (isControlFrame(this.#info.opcode)) { if (isControlFrame(this.#info.opcode)) {
this.#loop = this.parseControlFrame(body) this.#loop = this.parseControlFrame(body)
this.#state = parserStates.INFO this.#state = parserStates.INFO
// If the automatic PONG/CLOSE reply filled the socket's write buffer,
// stop parsing and resume on drain so a PING flood cannot grow the
// outbound queue without bound.
const socket = this.ws[kResponse].socket
if (this.#loop && socket.writableNeedDrain) {
this.#loop = false
socket.once('drain', () => {
if (this.destroyed) return
this.#loop = true
this.run(callback)
})
return
}
} else { } else {
if (!this.#info.compressed) { if (!this.#info.compressed) {
if (!this.writeFragments(body)) { if (!this.writeFragments(body)) {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "undici", "name": "undici",
"version": "6.29.0", "version": "6.29.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": {
+14
View File
@@ -80,6 +80,8 @@ export declare namespace Client {
maxResponseSize?: number; maxResponseSize?: number;
/** WebSocket-specific options */ /** WebSocket-specific options */
webSocket?: Client.WebSocketOptions; webSocket?: Client.WebSocketOptions;
/** EventSource-specific options */
eventSource?: Client.EventSourceOptions;
/** Enables a family autodetection algorithm that loosely implements section 5 of RFC 8305. */ /** Enables a family autodetection algorithm that loosely implements section 5 of RFC 8305. */
autoSelectFamily?: boolean; autoSelectFamily?: boolean;
/** The amount of time in milliseconds to wait for a connection attempt to finish before trying the next address when using the `autoSelectFamily` option. */ /** The amount of time in milliseconds to wait for a connection attempt to finish before trying the next address when using the `autoSelectFamily` option. */
@@ -120,6 +122,18 @@ export declare namespace Client {
*/ */
maxPayloadSize?: number; maxPayloadSize?: number;
} }
export interface EventSourceOptions {
/**
* Maximum total bytes of data and metadata field values retained for a
* pending EventSource event. Each unfinished line is separately bounded
* to this value plus seven syntax bytes. The number of data lines per
* event and buffered input chunks are each limited to
* min(131072, max(1024, floor(maxEventSize / 128))).
* Set to 0 to disable these limits.
* @default buffer.kStringMaxLength
*/
maxEventSize?: number;
}
} }
export default Client; export default Client;