Skip to content

Commit 74343a7

Browse files
nodejs-github-botRafaelGSS
authored andcommitted
deps: update undici to 6.11.1
PR-URL: #52328 Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com> Reviewed-By: Marco Ippolito <marcoippolito54@gmail.com.com>
1 parent 04e1646 commit 74343a7

14 files changed

+292
-172
lines changed

deps/undici/src/README.md

+4
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,12 @@ An HTTP/1.1 client, written from scratch for Node.js.
77
> Undici means eleven in Italian. 1.1 -> 11 -> Eleven -> Undici.
88
It is also a Stranger Things reference.
99

10+
## How to get involved
11+
1012
Have a question about using Undici? Open a [Q&A Discussion](https://github.com/nodejs/undici/discussions/new) or join our official OpenJS [Slack](https://openjs-foundation.slack.com/archives/C01QF9Q31QD) channel.
1113

14+
Looking to contribute? Start by reading the [contributing guide](./CONTRIBUTING.md)
15+
1216
## Install
1317

1418
```

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

-3
Original file line numberDiff line numberDiff line change
@@ -246,9 +246,6 @@ function bufferToLowerCasedHeaderName (value) {
246246
* @returns {Record<string, string | string[]>}
247247
*/
248248
function parseHeaders (headers, obj) {
249-
// For H2 support
250-
if (!Array.isArray(headers)) return headers
251-
252249
if (obj === undefined) obj = {}
253250
for (let i = 0; i < headers.length; i += 2) {
254251
const key = headerNameToString(headers[i])

deps/undici/src/lib/dispatcher/client-h2.js

+27-1
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,20 @@ const {
5454
}
5555
} = http2
5656

57+
function parseH2Headers (headers) {
58+
// set-cookie is always an array. Duplicates are added to the array.
59+
// For duplicate cookie headers, the values are joined together with '; '.
60+
headers = Object.entries(headers).flat(2)
61+
62+
const result = []
63+
64+
for (const header of headers) {
65+
result.push(Buffer.from(header))
66+
}
67+
68+
return result
69+
}
70+
5771
async function connectH2 (client, socket) {
5872
client[kSocket] = socket
5973

@@ -391,7 +405,19 @@ function writeH2 (client, request) {
391405
const { [HTTP2_HEADER_STATUS]: statusCode, ...realHeaders } = headers
392406
request.onResponseStarted()
393407

394-
if (request.onHeaders(Number(statusCode), realHeaders, stream.resume.bind(stream), '') === false) {
408+
// Due to the stream nature, it is possible we face a race condition
409+
// where the stream has been assigned, but the request has been aborted
410+
// the request remains in-flight and headers hasn't been received yet
411+
// for those scenarios, best effort is to destroy the stream immediately
412+
// as there's no value to keep it open.
413+
if (request.aborted || request.completed) {
414+
const err = new RequestAbortedError()
415+
errorRequest(client, request, err)
416+
util.destroy(stream, err)
417+
return
418+
}
419+
420+
if (request.onHeaders(Number(statusCode), parseH2Headers(realHeaders), stream.resume.bind(stream), '') === false) {
395421
stream.pause()
396422
}
397423

deps/undici/src/lib/handler/redirect-handler.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -201,9 +201,9 @@ function shouldRemoveHeader (header, removeContent, unknownOrigin) {
201201
if (removeContent && util.headerNameToString(header).startsWith('content-')) {
202202
return true
203203
}
204-
if (unknownOrigin && (header.length === 13 || header.length === 6)) {
204+
if (unknownOrigin && (header.length === 13 || header.length === 6 || header.length === 19)) {
205205
const name = util.headerNameToString(header)
206-
return name === 'authorization' || name === 'cookie'
206+
return name === 'authorization' || name === 'cookie' || name === 'proxy-authorization'
207207
}
208208
return false
209209
}

deps/undici/src/lib/mock/pending-interceptors-formatter.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
const { Transform } = require('node:stream')
44
const { Console } = require('node:console')
55

6+
const PERSISTENT = process.versions.icu ? '✅' : 'Y '
7+
const NOT_PERSISTENT = process.versions.icu ? '❌' : 'N '
8+
69
/**
710
* Gets the output of `console.table(…)` as a string.
811
*/
@@ -29,7 +32,7 @@ module.exports = class PendingInterceptorsFormatter {
2932
Origin: origin,
3033
Path: path,
3134
'Status code': statusCode,
32-
Persistent: persist ? '✅' : '❌',
35+
Persistent: persist ? PERSISTENT : NOT_PERSISTENT,
3336
Invocations: timesInvoked,
3437
Remaining: persist ? Infinity : times - timesInvoked
3538
}))

deps/undici/src/lib/web/fetch/data-url.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@ const encoder = new TextEncoder()
88
* @see https://mimesniff.spec.whatwg.org/#http-token-code-point
99
*/
1010
const HTTP_TOKEN_CODEPOINTS = /^[!#$%&'*+-.^_|~A-Za-z0-9]+$/
11-
const HTTP_WHITESPACE_REGEX = /[\u000A|\u000D|\u0009|\u0020]/ // eslint-disable-line
11+
const HTTP_WHITESPACE_REGEX = /[\u000A\u000D\u0009\u0020]/ // eslint-disable-line
1212
const ASCII_WHITESPACE_REPLACE_REGEX = /[\u0009\u000A\u000C\u000D\u0020]/g // eslint-disable-line
1313
/**
1414
* @see https://mimesniff.spec.whatwg.org/#http-quoted-string-token-code-point
1515
*/
16-
const HTTP_QUOTED_STRING_TOKENS = /[\u0009|\u0020-\u007E|\u0080-\u00FF]/ // eslint-disable-line
16+
const HTTP_QUOTED_STRING_TOKENS = /[\u0009\u0020-\u007E\u0080-\u00FF]/ // eslint-disable-line
1717

1818
// https://fetch.spec.whatwg.org/#data-url-processor
1919
/** @param {URL} dataURL */

deps/undici/src/lib/web/fetch/headers.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ const {
1212
} = require('./util')
1313
const { webidl } = require('./webidl')
1414
const assert = require('node:assert')
15-
const util = require('util')
15+
const util = require('node:util')
1616

1717
const kHeadersMap = Symbol('headers map')
1818
const kHeadersSortedMap = Symbol('headers map sorted')

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

-23
Original file line numberDiff line numberDiff line change
@@ -2141,29 +2141,6 @@ async function httpNetworkFetch (
21412141
codings = contentEncoding.toLowerCase().split(',').map((x) => x.trim())
21422142
}
21432143
location = headersList.get('location', true)
2144-
} else {
2145-
const keys = Object.keys(rawHeaders)
2146-
for (let i = 0; i < keys.length; ++i) {
2147-
// The header names are already in lowercase.
2148-
const key = keys[i]
2149-
const value = rawHeaders[key]
2150-
if (key === 'set-cookie') {
2151-
for (let j = 0; j < value.length; ++j) {
2152-
headersList.append(key, value[j], true)
2153-
}
2154-
} else {
2155-
headersList.append(key, value, true)
2156-
}
2157-
}
2158-
// For H2, The header names are already in lowercase,
2159-
// so we can avoid the `HeadersList#get` call here.
2160-
const contentEncoding = rawHeaders['content-encoding']
2161-
if (contentEncoding) {
2162-
// https://www.rfc-editor.org/rfc/rfc7231#section-3.1.2.1
2163-
// "All content-coding values are case-insensitive..."
2164-
codings = contentEncoding.toLowerCase().split(',').map((x) => x.trim()).reverse()
2165-
}
2166-
location = rawHeaders.location
21672144
}
21682145

21692146
this.body = new Readable({ read: resume })

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

+105-33
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,15 @@ const assert = require('node:assert')
1111
const { isUint8Array } = require('node:util/types')
1212
const { webidl } = require('./webidl')
1313

14+
let supportedHashes = []
15+
1416
// https://nodejs.org/api/crypto.html#determining-if-crypto-support-is-unavailable
1517
/** @type {import('crypto')} */
1618
let crypto
1719
try {
1820
crypto = require('node:crypto')
21+
const possibleRelevantHashes = ['sha256', 'sha384', 'sha512']
22+
supportedHashes = crypto.getHashes().filter((hash) => possibleRelevantHashes.includes(hash))
1923
/* c8 ignore next 3 */
2024
} catch {
2125

@@ -565,66 +569,56 @@ function bytesMatch (bytes, metadataList) {
565569
return true
566570
}
567571

568-
// 3. If parsedMetadata is the empty set, return true.
572+
// 3. If response is not eligible for integrity validation, return false.
573+
// TODO
574+
575+
// 4. If parsedMetadata is the empty set, return true.
569576
if (parsedMetadata.length === 0) {
570577
return true
571578
}
572579

573-
// 4. Let metadata be the result of getting the strongest
580+
// 5. Let metadata be the result of getting the strongest
574581
// metadata from parsedMetadata.
575-
const list = parsedMetadata.sort((c, d) => d.algo.localeCompare(c.algo))
576-
// get the strongest algorithm
577-
const strongest = list[0].algo
578-
// get all entries that use the strongest algorithm; ignore weaker
579-
const metadata = list.filter((item) => item.algo === strongest)
582+
const strongest = getStrongestMetadata(parsedMetadata)
583+
const metadata = filterMetadataListByAlgorithm(parsedMetadata, strongest)
580584

581-
// 5. For each item in metadata:
585+
// 6. For each item in metadata:
582586
for (const item of metadata) {
583587
// 1. Let algorithm be the alg component of item.
584588
const algorithm = item.algo
585589

586590
// 2. Let expectedValue be the val component of item.
587-
let expectedValue = item.hash
591+
const expectedValue = item.hash
588592

589593
// See https://github.com/web-platform-tests/wpt/commit/e4c5cc7a5e48093220528dfdd1c4012dc3837a0e
590594
// "be liberal with padding". This is annoying, and it's not even in the spec.
591595

592-
if (expectedValue.endsWith('==')) {
593-
expectedValue = expectedValue.slice(0, -2)
594-
}
595-
596596
// 3. Let actualValue be the result of applying algorithm to bytes.
597597
let actualValue = crypto.createHash(algorithm).update(bytes).digest('base64')
598598

599-
if (actualValue.endsWith('==')) {
600-
actualValue = actualValue.slice(0, -2)
599+
if (actualValue[actualValue.length - 1] === '=') {
600+
if (actualValue[actualValue.length - 2] === '=') {
601+
actualValue = actualValue.slice(0, -2)
602+
} else {
603+
actualValue = actualValue.slice(0, -1)
604+
}
601605
}
602606

603607
// 4. If actualValue is a case-sensitive match for expectedValue,
604608
// return true.
605-
if (actualValue === expectedValue) {
606-
return true
607-
}
608-
609-
let actualBase64URL = crypto.createHash(algorithm).update(bytes).digest('base64url')
610-
611-
if (actualBase64URL.endsWith('==')) {
612-
actualBase64URL = actualBase64URL.slice(0, -2)
613-
}
614-
615-
if (actualBase64URL === expectedValue) {
609+
if (compareBase64Mixed(actualValue, expectedValue)) {
616610
return true
617611
}
618612
}
619613

620-
// 6. Return false.
614+
// 7. Return false.
621615
return false
622616
}
623617

624618
// https://w3c.github.io/webappsec-subresource-integrity/#grammardef-hash-with-options
625619
// https://www.w3.org/TR/CSP2/#source-list-syntax
626620
// https://www.rfc-editor.org/rfc/rfc5234#appendix-B.1
627-
const parseHashWithOptions = /(?<algo>sha256|sha384|sha512)-(?<hash>[A-Za-z0-9+/]+={0,2}(?=\s|$))( +[!-~]*)?/i
621+
const parseHashWithOptions = /(?<algo>sha256|sha384|sha512)-((?<hash>[A-Za-z0-9+/]+|[A-Za-z0-9_-]+)={0,2}(?:\s|$)( +[!-~]*)?)?/i
628622

629623
/**
630624
* @see https://w3c.github.io/webappsec-subresource-integrity/#parse-metadata
@@ -638,8 +632,6 @@ function parseMetadata (metadata) {
638632
// 2. Let empty be equal to true.
639633
let empty = true
640634

641-
const supportedHashes = crypto.getHashes()
642-
643635
// 3. For each token returned by splitting metadata on spaces:
644636
for (const token of metadata.split(' ')) {
645637
// 1. Set empty to false.
@@ -649,7 +641,11 @@ function parseMetadata (metadata) {
649641
const parsedToken = parseHashWithOptions.exec(token)
650642

651643
// 3. If token does not parse, continue to the next token.
652-
if (parsedToken === null || parsedToken.groups === undefined) {
644+
if (
645+
parsedToken === null ||
646+
parsedToken.groups === undefined ||
647+
parsedToken.groups.algo === undefined
648+
) {
653649
// Note: Chromium blocks the request at this point, but Firefox
654650
// gives a warning that an invalid integrity was given. The
655651
// correct behavior is to ignore these, and subsequently not
@@ -658,11 +654,11 @@ function parseMetadata (metadata) {
658654
}
659655

660656
// 4. Let algorithm be the hash-algo component of token.
661-
const algorithm = parsedToken.groups.algo
657+
const algorithm = parsedToken.groups.algo.toLowerCase()
662658

663659
// 5. If algorithm is a hash function recognized by the user
664660
// agent, add the parsed token to result.
665-
if (supportedHashes.includes(algorithm.toLowerCase())) {
661+
if (supportedHashes.includes(algorithm)) {
666662
result.push(parsedToken.groups)
667663
}
668664
}
@@ -675,6 +671,82 @@ function parseMetadata (metadata) {
675671
return result
676672
}
677673

674+
/**
675+
* @param {{ algo: 'sha256' | 'sha384' | 'sha512' }[]} metadataList
676+
*/
677+
function getStrongestMetadata (metadataList) {
678+
// Let algorithm be the algo component of the first item in metadataList.
679+
// Can be sha256
680+
let algorithm = metadataList[0].algo
681+
// If the algorithm is sha512, then it is the strongest
682+
// and we can return immediately
683+
if (algorithm[3] === '5') {
684+
return algorithm
685+
}
686+
687+
for (let i = 1; i < metadataList.length; ++i) {
688+
const metadata = metadataList[i]
689+
// If the algorithm is sha512, then it is the strongest
690+
// and we can break the loop immediately
691+
if (metadata.algo[3] === '5') {
692+
algorithm = 'sha512'
693+
break
694+
// If the algorithm is sha384, then a potential sha256 or sha384 is ignored
695+
} else if (algorithm[3] === '3') {
696+
continue
697+
// algorithm is sha256, check if algorithm is sha384 and if so, set it as
698+
// the strongest
699+
} else if (metadata.algo[3] === '3') {
700+
algorithm = 'sha384'
701+
}
702+
}
703+
return algorithm
704+
}
705+
706+
function filterMetadataListByAlgorithm (metadataList, algorithm) {
707+
if (metadataList.length === 1) {
708+
return metadataList
709+
}
710+
711+
let pos = 0
712+
for (let i = 0; i < metadataList.length; ++i) {
713+
if (metadataList[i].algo === algorithm) {
714+
metadataList[pos++] = metadataList[i]
715+
}
716+
}
717+
718+
metadataList.length = pos
719+
720+
return metadataList
721+
}
722+
723+
/**
724+
* Compares two base64 strings, allowing for base64url
725+
* in the second string.
726+
*
727+
* @param {string} actualValue always base64
728+
* @param {string} expectedValue base64 or base64url
729+
* @returns {boolean}
730+
*/
731+
function compareBase64Mixed (actualValue, expectedValue) {
732+
if (actualValue.length !== expectedValue.length) {
733+
return false
734+
}
735+
for (let i = 0; i < actualValue.length; ++i) {
736+
if (actualValue[i] !== expectedValue[i]) {
737+
if (
738+
(actualValue[i] === '+' && expectedValue[i] === '-') ||
739+
(actualValue[i] === '/' && expectedValue[i] === '_')
740+
) {
741+
continue
742+
}
743+
return false
744+
}
745+
}
746+
747+
return true
748+
}
749+
678750
// https://w3c.github.io/webappsec-upgrade-insecure-requests/#upgrade-request
679751
function tryUpgradeRequestToAPotentiallyTrustworthyURL (request) {
680752
// TODO

0 commit comments

Comments
 (0)