Skip to content

Commit 57da3db

Browse files
nodejs-github-botRafaelGSS
authored andcommitted
deps: update undici to 5.9.1
PR-URL: #44319 Reviewed-By: Tobias Nießen <tniessen@tnie.de> Reviewed-By: Rich Trott <rtrott@gmail.com> Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> Reviewed-By: Mohammed Keyvanzadeh <mohammadkeyvanzade94@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
1 parent c4a45a9 commit 57da3db

File tree

13 files changed

+335
-62
lines changed

13 files changed

+335
-62
lines changed

deps/undici/src/docs/api/Client.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ Returns: `Client`
1818
### Parameter: `ClientOptions`
1919

2020
* **bodyTimeout** `number | null` (optional) - Default: `30e3` - The timeout after which a request will time out, in milliseconds. Monitors time between receiving body data. Use `0` to disable it entirely. Defaults to 30 seconds.
21-
* **headersTimeout** `number | null` (optional) - Default: `30e3` - The amount of time the parser will wait to receive the complete HTTP headers. Defaults to 30 seconds.
21+
* **headersTimeout** `number | null` (optional) - Default: `30e3` - The amount of time the parser will wait to receive the complete HTTP headers while not sending the request. Defaults to 30 seconds.
2222
* **keepAliveMaxTimeout** `number | null` (optional) - Default: `600e3` - The maximum allowed `keepAliveTimeout` when overridden by *keep-alive* hints from the server. Defaults to 10 minutes.
2323
* **keepAliveTimeout** `number | null` (optional) - Default: `4e3` - The timeout after which a socket without active requests will time out. Monitors time between activity on a connected socket. This value may be overridden by *keep-alive* hints from the server. See [MDN: HTTP - Headers - Keep-Alive directives](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Keep-Alive#directives) for more details. Defaults to 4 seconds.
2424
* **keepAliveTimeoutThreshold** `number | null` (optional) - Default: `1e3` - A number subtracted from server *keep-alive* hints when overriding `keepAliveTimeout` to account for timing inaccuracies caused by e.g. transport latency. Defaults to 1 second.

deps/undici/src/docs/api/Dispatcher.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ Returns: `Boolean` - `false` if dispatcher is busy and further dispatch calls wo
199199
* **blocking** `boolean` (optional) - Default: `false` - Whether the response is expected to take a long time and would end up blocking the pipeline. When this is set to `true` further pipelining will be avoided on the same connection until headers have been received.
200200
* **upgrade** `string | null` (optional) - Default: `null` - Upgrade the request. Should be used to specify the kind of upgrade i.e. `'Websocket'`.
201201
* **bodyTimeout** `number | null` (optional) - The timeout after which a request will time out, in milliseconds. Monitors time between receiving body data. Use `0` to disable it entirely. Defaults to 30 seconds.
202-
* **headersTimeout** `number | null` (optional) - The amount of time the parser will wait to receive the complete HTTP headers. Defaults to 30 seconds.
202+
* **headersTimeout** `number | null` (optional) - The amount of time the parser will wait to receive the complete HTTP headers while not sending the request. Defaults to 30 seconds.
203203
* **throwOnError** `boolean` (optional) - Default: `false` - Whether Undici should throw an error upon receiving a 4xx or 5xx response from the server.
204204

205205
#### Parameter: `DispatchHandler`

deps/undici/src/lib/api/readable.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ module.exports = class BodyReadable extends Readable {
9393
}
9494

9595
push (chunk) {
96-
if (this[kConsume] && chunk !== null) {
96+
if (this[kConsume] && chunk !== null && this.readableLength === 0) {
9797
consumePush(this[kConsume], chunk)
9898
return this[kReading] ? super.push(chunk) : true
9999
}

deps/undici/src/lib/client.js

+15-2
Original file line numberDiff line numberDiff line change
@@ -889,8 +889,10 @@ function onParserTimeout (parser) {
889889

890890
/* istanbul ignore else */
891891
if (timeoutType === TIMEOUT_HEADERS) {
892-
assert(!parser.paused, 'cannot be paused while waiting for headers')
893-
util.destroy(socket, new HeadersTimeoutError())
892+
if (!socket[kWriting] || socket.writableNeedDrain || client[kRunning] > 1) {
893+
assert(!parser.paused, 'cannot be paused while waiting for headers')
894+
util.destroy(socket, new HeadersTimeoutError())
895+
}
894896
} else if (timeoutType === TIMEOUT_BODY) {
895897
if (!parser.paused) {
896898
util.destroy(socket, new BodyTimeoutError())
@@ -1641,7 +1643,18 @@ class AsyncWriter {
16411643
this.bytesWritten += len
16421644

16431645
const ret = socket.write(chunk)
1646+
16441647
request.onBodySent(chunk)
1648+
1649+
if (!ret) {
1650+
if (socket[kParser].timeout && socket[kParser].timeoutType === TIMEOUT_HEADERS) {
1651+
// istanbul ignore else: only for jest
1652+
if (socket[kParser].timeout.refresh) {
1653+
socket[kParser].timeout.refresh()
1654+
}
1655+
}
1656+
}
1657+
16451658
return ret
16461659
}
16471660

deps/undici/src/lib/core/util.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,11 @@ function parseHeaders (headers, obj = {}) {
244244
const key = headers[i].toString().toLowerCase()
245245
let val = obj[key]
246246
if (!val) {
247-
obj[key] = headers[i + 1].toString()
247+
if (Array.isArray(headers[i + 1])) {
248+
obj[key] = headers[i + 1]
249+
} else {
250+
obj[key] = headers[i + 1].toString()
251+
}
248252
} else {
249253
if (!Array.isArray(val)) {
250254
val = [val]

deps/undici/src/lib/fetch/body.js

+7-7
Original file line numberDiff line numberDiff line change
@@ -57,16 +57,16 @@ function extractBody (object, keepalive = false) {
5757

5858
// Set Content-Type to `application/x-www-form-urlencoded;charset=UTF-8`.
5959
contentType = 'application/x-www-form-urlencoded;charset=UTF-8'
60-
} else if (isArrayBuffer(object) || ArrayBuffer.isView(object)) {
61-
// BufferSource
60+
} else if (isArrayBuffer(object)) {
61+
// BufferSource/ArrayBuffer
6262

63-
if (object instanceof DataView) {
64-
// TODO: Blob doesn't seem to work with DataView?
65-
object = object.buffer
66-
}
63+
// Set source to a copy of the bytes held by object.
64+
source = new Uint8Array(object.slice())
65+
} else if (ArrayBuffer.isView(object)) {
66+
// BufferSource/ArrayBufferView
6767

6868
// Set source to a copy of the bytes held by object.
69-
source = new Uint8Array(object)
69+
source = new Uint8Array(object.buffer.slice(object.byteOffset, object.byteOffset + object.byteLength))
7070
} else if (util.isFormDataLike(object)) {
7171
const boundary = '----formdata-undici-' + Math.random()
7272
const prefix = `--${boundary}\r\nContent-Disposition: form-data`

deps/undici/src/lib/fetch/file.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,9 @@ function processBlobParts (parts, options) {
278278
if (!element.buffer) { // ArrayBuffer
279279
bytes.push(new Uint8Array(element))
280280
} else {
281-
bytes.push(element.buffer)
281+
bytes.push(
282+
new Uint8Array(element.buffer, element.byteOffset, element.byteLength)
283+
)
282284
}
283285
} else if (isBlobLike(element)) {
284286
// 3. If element is a Blob, append the bytes it represents

deps/undici/src/lib/fetch/index.js

+6-13
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ const { Headers } = require('./headers')
1313
const { Request, makeRequest } = require('./request')
1414
const zlib = require('zlib')
1515
const {
16-
matchRequestIntegrity,
16+
bytesMatch,
1717
makePolicyContainer,
1818
clonePolicyContainer,
1919
requestBadPort,
@@ -34,7 +34,8 @@ const {
3434
sameOrigin,
3535
isCancelled,
3636
isAborted,
37-
isErrorLike
37+
isErrorLike,
38+
fullyReadBody
3839
} = require('./util')
3940
const { kState, kHeaders, kGuard, kRealm } = require('./symbols')
4041
const assert = require('assert')
@@ -724,7 +725,7 @@ async function mainFetch (fetchParams, recursive = false) {
724725
const processBody = (bytes) => {
725726
// 1. If bytes do not match request’s integrity metadata,
726727
// then run processBodyError and abort these steps. [SRI]
727-
if (!matchRequestIntegrity(request, bytes)) {
728+
if (!bytesMatch(bytes, request.integrity)) {
728729
processBodyError('integrity mismatch')
729730
return
730731
}
@@ -738,11 +739,7 @@ async function mainFetch (fetchParams, recursive = false) {
738739
}
739740

740741
// 4. Fully read response’s body given processBody and processBodyError.
741-
try {
742-
processBody(await response.arrayBuffer())
743-
} catch (err) {
744-
processBodyError(err)
745-
}
742+
await fullyReadBody(response.body, processBody, processBodyError)
746743
} else {
747744
// 21. Otherwise, run fetch finale given fetchParams and response.
748745
fetchFinale(fetchParams, response)
@@ -974,11 +971,7 @@ async function fetchFinale (fetchParams, response) {
974971
} else {
975972
// 4. Otherwise, fully read response’s body given processBody, processBodyError,
976973
// and fetchParams’s task destination.
977-
try {
978-
processBody(await response.body.stream.arrayBuffer())
979-
} catch (err) {
980-
processBodyError(err)
981-
}
974+
await fullyReadBody(response.body, processBody, processBodyError)
982975
}
983976
}
984977
}

deps/undici/src/lib/fetch/request.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
const { extractBody, mixinBody, cloneBody } = require('./body')
66
const { Headers, fill: fillHeaders, HeadersList } = require('./headers')
7+
const { FinalizationRegistry } = require('../compat/dispatcher-weakref')()
78
const util = require('../core/util')
89
const {
910
isValidHTTPToken,
@@ -914,7 +915,10 @@ webidl.converters.RequestInit = webidl.dictionaryConverter([
914915
{
915916
key: 'signal',
916917
converter: webidl.nullableConverter(
917-
webidl.converters.AbortSignal
918+
(signal) => webidl.converters.AbortSignal(
919+
signal,
920+
{ strict: false }
921+
)
918922
)
919923
},
920924
{

deps/undici/src/lib/fetch/util.js

+168-3
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,20 @@ const { redirectStatus } = require('./constants')
44
const { performance } = require('perf_hooks')
55
const { isBlobLike, toUSVString, ReadableStreamFrom } = require('../core/util')
66
const assert = require('assert')
7+
const { isUint8Array } = require('util/types')
78

89
let File
910

11+
// https://nodejs.org/api/crypto.html#determining-if-crypto-support-is-unavailable
12+
/** @type {import('crypto')|undefined} */
13+
let crypto
14+
15+
try {
16+
crypto = require('crypto')
17+
} catch {
18+
19+
}
20+
1021
// https://fetch.spec.whatwg.org/#block-bad-port
1122
const badPorts = [
1223
'1', '7', '9', '11', '13', '15', '17', '19', '20', '21', '22', '23', '25', '37', '42', '43', '53', '69', '77', '79',
@@ -339,10 +350,116 @@ function determineRequestsReferrer (request) {
339350
return 'no-referrer'
340351
}
341352

342-
function matchRequestIntegrity (request, bytes) {
353+
/**
354+
* @see https://w3c.github.io/webappsec-subresource-integrity/#does-response-match-metadatalist
355+
* @param {Uint8Array} bytes
356+
* @param {string} metadataList
357+
*/
358+
function bytesMatch (bytes, metadataList) {
359+
// If node is not built with OpenSSL support, we cannot check
360+
// a request's integrity, so allow it by default (the spec will
361+
// allow requests if an invalid hash is given, as precedence).
362+
/* istanbul ignore if: only if node is built with --without-ssl */
363+
if (crypto === undefined) {
364+
return true
365+
}
366+
367+
// 1. Let parsedMetadata be the result of parsing metadataList.
368+
const parsedMetadata = parseMetadata(metadataList)
369+
370+
// 2. If parsedMetadata is no metadata, return true.
371+
if (parsedMetadata === 'no metadata') {
372+
return true
373+
}
374+
375+
// 3. If parsedMetadata is the empty set, return true.
376+
if (parsedMetadata.length === 0) {
377+
return true
378+
}
379+
380+
// 4. Let metadata be the result of getting the strongest
381+
// metadata from parsedMetadata.
382+
// Note: this will only work for SHA- algorithms and it's lazy *at best*.
383+
const metadata = parsedMetadata.sort((c, d) => d.algo.localeCompare(c.algo))
384+
385+
// 5. For each item in metadata:
386+
for (const item of metadata) {
387+
// 1. Let algorithm be the alg component of item.
388+
const algorithm = item.algo
389+
390+
// 2. Let expectedValue be the val component of item.
391+
const expectedValue = item.hash
392+
393+
// 3. Let actualValue be the result of applying algorithm to bytes.
394+
// Note: "applying algorithm to bytes" converts the result to base64
395+
const actualValue = crypto.createHash(algorithm).update(bytes).digest('base64')
396+
397+
// 4. If actualValue is a case-sensitive match for expectedValue,
398+
// return true.
399+
if (actualValue === expectedValue) {
400+
return true
401+
}
402+
}
403+
404+
// 6. Return false.
343405
return false
344406
}
345407

408+
// https://w3c.github.io/webappsec-subresource-integrity/#grammardef-hash-with-options
409+
// hash-algo is defined in Content Security Policy 2 Section 4.2
410+
// base64-value is similary defined there
411+
// VCHAR is defined https://www.rfc-editor.org/rfc/rfc5234#appendix-B.1
412+
const parseHashWithOptions = /((?<algo>sha256|sha384|sha512)-(?<hash>[A-z0-9+/]{1}.*={1,2}))( +[\x21-\x7e]?)?/i
413+
414+
/**
415+
* @see https://w3c.github.io/webappsec-subresource-integrity/#parse-metadata
416+
* @param {string} metadata
417+
*/
418+
function parseMetadata (metadata) {
419+
// 1. Let result be the empty set.
420+
/** @type {{ algo: string, hash: string }[]} */
421+
const result = []
422+
423+
// 2. Let empty be equal to true.
424+
let empty = true
425+
426+
const supportedHashes = crypto.getHashes()
427+
428+
// 3. For each token returned by splitting metadata on spaces:
429+
for (const token of metadata.split(' ')) {
430+
// 1. Set empty to false.
431+
empty = false
432+
433+
// 2. Parse token as a hash-with-options.
434+
const parsedToken = parseHashWithOptions.exec(token)
435+
436+
// 3. If token does not parse, continue to the next token.
437+
if (parsedToken === null || parsedToken.groups === undefined) {
438+
// Note: Chromium blocks the request at this point, but Firefox
439+
// gives a warning that an invalid integrity was given. The
440+
// correct behavior is to ignore these, and subsequently not
441+
// check the integrity of the resource.
442+
continue
443+
}
444+
445+
// 4. Let algorithm be the hash-algo component of token.
446+
const algorithm = parsedToken.groups.algo
447+
448+
// 5. If algorithm is a hash function recognized by the user
449+
// agent, add the parsed token to result.
450+
if (supportedHashes.includes(algorithm.toLowerCase())) {
451+
result.push(parsedToken.groups)
452+
}
453+
}
454+
455+
// 4. Return no metadata if empty is true, otherwise return result.
456+
if (empty === true) {
457+
return 'no metadata'
458+
}
459+
460+
return result
461+
}
462+
346463
// https://w3c.github.io/webappsec-upgrade-insecure-requests/#upgrade-request
347464
function tryUpgradeRequestToAPotentiallyTrustworthyURL (request) {
348465
// TODO
@@ -438,6 +555,53 @@ function makeIterator (iterator, name) {
438555
return Object.setPrototypeOf({}, i)
439556
}
440557

558+
/**
559+
* @see https://fetch.spec.whatwg.org/#body-fully-read
560+
*/
561+
async function fullyReadBody (body, processBody, processBodyError) {
562+
// 1. If taskDestination is null, then set taskDestination to
563+
// the result of starting a new parallel queue.
564+
565+
// 2. Let promise be the result of fully reading body as promise
566+
// given body.
567+
try {
568+
/** @type {Uint8Array[]} */
569+
const chunks = []
570+
let length = 0
571+
572+
const reader = body.stream.getReader()
573+
574+
while (true) {
575+
const { done, value } = await reader.read()
576+
577+
if (done === true) {
578+
break
579+
}
580+
581+
// read-loop chunk steps
582+
assert(isUint8Array(value))
583+
584+
chunks.push(value)
585+
length += value.byteLength
586+
}
587+
588+
// 3. Let fulfilledSteps given a byte sequence bytes be to queue
589+
// a fetch task to run processBody given bytes, with
590+
// taskDestination.
591+
const fulfilledSteps = (bytes) => queueMicrotask(() => {
592+
processBody(bytes)
593+
})
594+
595+
fulfilledSteps(Buffer.concat(chunks, length))
596+
} catch (err) {
597+
// 4. Let rejectedSteps be to queue a fetch task to run
598+
// processBodyError, with taskDestination.
599+
queueMicrotask(() => processBodyError(err))
600+
}
601+
602+
// 5. React to promise with fulfilledSteps and rejectedSteps.
603+
}
604+
441605
/**
442606
* Fetch supports node >= 16.8.0, but Object.hasOwn was added in v16.9.0.
443607
*/
@@ -451,7 +615,6 @@ module.exports = {
451615
toUSVString,
452616
tryUpgradeRequestToAPotentiallyTrustworthyURL,
453617
coarsenedSharedCurrentTime,
454-
matchRequestIntegrity,
455618
determineRequestsReferrer,
456619
makePolicyContainer,
457620
clonePolicyContainer,
@@ -477,5 +640,7 @@ module.exports = {
477640
isValidHeaderName,
478641
isValidHeaderValue,
479642
hasOwn,
480-
isErrorLike
643+
isErrorLike,
644+
fullyReadBody,
645+
bytesMatch
481646
}

0 commit comments

Comments
 (0)