Skip to content

Commit

Permalink
deps: update undici to 6.11.1
Browse files Browse the repository at this point in the history
PR-URL: #52328
Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com>
Reviewed-By: Marco Ippolito <marcoippolito54@gmail.com.com>
  • Loading branch information
nodejs-github-bot authored and marco-ippolito committed Apr 2, 2024
1 parent aceea1c commit 3dfc10c
Show file tree
Hide file tree
Showing 14 changed files with 292 additions and 172 deletions.
4 changes: 4 additions & 0 deletions deps/undici/src/README.md
Expand Up @@ -7,8 +7,12 @@ An HTTP/1.1 client, written from scratch for Node.js.
> Undici means eleven in Italian. 1.1 -> 11 -> Eleven -> Undici.
It is also a Stranger Things reference.

## How to get involved

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.

Looking to contribute? Start by reading the [contributing guide](./CONTRIBUTING.md)

## Install

```
Expand Down
3 changes: 0 additions & 3 deletions deps/undici/src/lib/core/util.js
Expand Up @@ -246,9 +246,6 @@ function bufferToLowerCasedHeaderName (value) {
* @returns {Record<string, string | string[]>}
*/
function parseHeaders (headers, obj) {
// For H2 support
if (!Array.isArray(headers)) return headers

if (obj === undefined) obj = {}
for (let i = 0; i < headers.length; i += 2) {
const key = headerNameToString(headers[i])
Expand Down
28 changes: 27 additions & 1 deletion deps/undici/src/lib/dispatcher/client-h2.js
Expand Up @@ -54,6 +54,20 @@ const {
}
} = http2

function parseH2Headers (headers) {
// set-cookie is always an array. Duplicates are added to the array.
// For duplicate cookie headers, the values are joined together with '; '.
headers = Object.entries(headers).flat(2)

const result = []

for (const header of headers) {
result.push(Buffer.from(header))
}

return result
}

async function connectH2 (client, socket) {
client[kSocket] = socket

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

if (request.onHeaders(Number(statusCode), realHeaders, stream.resume.bind(stream), '') === false) {
// Due to the stream nature, it is possible we face a race condition
// where the stream has been assigned, but the request has been aborted
// the request remains in-flight and headers hasn't been received yet
// for those scenarios, best effort is to destroy the stream immediately
// as there's no value to keep it open.
if (request.aborted || request.completed) {
const err = new RequestAbortedError()
errorRequest(client, request, err)
util.destroy(stream, err)
return
}

if (request.onHeaders(Number(statusCode), parseH2Headers(realHeaders), stream.resume.bind(stream), '') === false) {
stream.pause()
}

Expand Down
4 changes: 2 additions & 2 deletions deps/undici/src/lib/handler/redirect-handler.js
Expand Up @@ -201,9 +201,9 @@ function shouldRemoveHeader (header, removeContent, unknownOrigin) {
if (removeContent && util.headerNameToString(header).startsWith('content-')) {
return true
}
if (unknownOrigin && (header.length === 13 || header.length === 6)) {
if (unknownOrigin && (header.length === 13 || header.length === 6 || header.length === 19)) {
const name = util.headerNameToString(header)
return name === 'authorization' || name === 'cookie'
return name === 'authorization' || name === 'cookie' || name === 'proxy-authorization'
}
return false
}
Expand Down
5 changes: 4 additions & 1 deletion deps/undici/src/lib/mock/pending-interceptors-formatter.js
Expand Up @@ -3,6 +3,9 @@
const { Transform } = require('node:stream')
const { Console } = require('node:console')

const PERSISTENT = process.versions.icu ? '✅' : 'Y '
const NOT_PERSISTENT = process.versions.icu ? '❌' : 'N '

/**
* Gets the output of `console.table(…)` as a string.
*/
Expand All @@ -29,7 +32,7 @@ module.exports = class PendingInterceptorsFormatter {
Origin: origin,
Path: path,
'Status code': statusCode,
Persistent: persist ? '✅' : '❌',
Persistent: persist ? PERSISTENT : NOT_PERSISTENT,
Invocations: timesInvoked,
Remaining: persist ? Infinity : times - timesInvoked
}))
Expand Down
4 changes: 2 additions & 2 deletions deps/undici/src/lib/web/fetch/data-url.js
Expand Up @@ -8,12 +8,12 @@ const encoder = new TextEncoder()
* @see https://mimesniff.spec.whatwg.org/#http-token-code-point
*/
const HTTP_TOKEN_CODEPOINTS = /^[!#$%&'*+-.^_|~A-Za-z0-9]+$/
const HTTP_WHITESPACE_REGEX = /[\u000A|\u000D|\u0009|\u0020]/ // eslint-disable-line
const HTTP_WHITESPACE_REGEX = /[\u000A\u000D\u0009\u0020]/ // eslint-disable-line
const ASCII_WHITESPACE_REPLACE_REGEX = /[\u0009\u000A\u000C\u000D\u0020]/g // eslint-disable-line
/**
* @see https://mimesniff.spec.whatwg.org/#http-quoted-string-token-code-point
*/
const HTTP_QUOTED_STRING_TOKENS = /[\u0009|\u0020-\u007E|\u0080-\u00FF]/ // eslint-disable-line
const HTTP_QUOTED_STRING_TOKENS = /[\u0009\u0020-\u007E\u0080-\u00FF]/ // eslint-disable-line

// https://fetch.spec.whatwg.org/#data-url-processor
/** @param {URL} dataURL */
Expand Down
2 changes: 1 addition & 1 deletion deps/undici/src/lib/web/fetch/headers.js
Expand Up @@ -12,7 +12,7 @@ const {
} = require('./util')
const { webidl } = require('./webidl')
const assert = require('node:assert')
const util = require('util')
const util = require('node:util')

const kHeadersMap = Symbol('headers map')
const kHeadersSortedMap = Symbol('headers map sorted')
Expand Down
23 changes: 0 additions & 23 deletions deps/undici/src/lib/web/fetch/index.js
Expand Up @@ -2141,29 +2141,6 @@ async function httpNetworkFetch (
codings = contentEncoding.toLowerCase().split(',').map((x) => x.trim())
}
location = headersList.get('location', true)
} else {
const keys = Object.keys(rawHeaders)
for (let i = 0; i < keys.length; ++i) {
// The header names are already in lowercase.
const key = keys[i]
const value = rawHeaders[key]
if (key === 'set-cookie') {
for (let j = 0; j < value.length; ++j) {
headersList.append(key, value[j], true)
}
} else {
headersList.append(key, value, true)
}
}
// For H2, The header names are already in lowercase,
// so we can avoid the `HeadersList#get` call here.
const contentEncoding = rawHeaders['content-encoding']
if (contentEncoding) {
// https://www.rfc-editor.org/rfc/rfc7231#section-3.1.2.1
// "All content-coding values are case-insensitive..."
codings = contentEncoding.toLowerCase().split(',').map((x) => x.trim()).reverse()
}
location = rawHeaders.location
}

this.body = new Readable({ read: resume })
Expand Down
138 changes: 105 additions & 33 deletions deps/undici/src/lib/web/fetch/util.js
Expand Up @@ -11,11 +11,15 @@ const assert = require('node:assert')
const { isUint8Array } = require('node:util/types')
const { webidl } = require('./webidl')

let supportedHashes = []

// https://nodejs.org/api/crypto.html#determining-if-crypto-support-is-unavailable
/** @type {import('crypto')} */
let crypto
try {
crypto = require('node:crypto')
const possibleRelevantHashes = ['sha256', 'sha384', 'sha512']
supportedHashes = crypto.getHashes().filter((hash) => possibleRelevantHashes.includes(hash))
/* c8 ignore next 3 */
} catch {

Expand Down Expand Up @@ -565,66 +569,56 @@ function bytesMatch (bytes, metadataList) {
return true
}

// 3. If parsedMetadata is the empty set, return true.
// 3. If response is not eligible for integrity validation, return false.
// TODO

// 4. If parsedMetadata is the empty set, return true.
if (parsedMetadata.length === 0) {
return true
}

// 4. Let metadata be the result of getting the strongest
// 5. Let metadata be the result of getting the strongest
// metadata from parsedMetadata.
const list = parsedMetadata.sort((c, d) => d.algo.localeCompare(c.algo))
// get the strongest algorithm
const strongest = list[0].algo
// get all entries that use the strongest algorithm; ignore weaker
const metadata = list.filter((item) => item.algo === strongest)
const strongest = getStrongestMetadata(parsedMetadata)
const metadata = filterMetadataListByAlgorithm(parsedMetadata, strongest)

// 5. For each item in metadata:
// 6. For each item in metadata:
for (const item of metadata) {
// 1. Let algorithm be the alg component of item.
const algorithm = item.algo

// 2. Let expectedValue be the val component of item.
let expectedValue = item.hash
const expectedValue = item.hash

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

if (expectedValue.endsWith('==')) {
expectedValue = expectedValue.slice(0, -2)
}

// 3. Let actualValue be the result of applying algorithm to bytes.
let actualValue = crypto.createHash(algorithm).update(bytes).digest('base64')

if (actualValue.endsWith('==')) {
actualValue = actualValue.slice(0, -2)
if (actualValue[actualValue.length - 1] === '=') {
if (actualValue[actualValue.length - 2] === '=') {
actualValue = actualValue.slice(0, -2)
} else {
actualValue = actualValue.slice(0, -1)
}
}

// 4. If actualValue is a case-sensitive match for expectedValue,
// return true.
if (actualValue === expectedValue) {
return true
}

let actualBase64URL = crypto.createHash(algorithm).update(bytes).digest('base64url')

if (actualBase64URL.endsWith('==')) {
actualBase64URL = actualBase64URL.slice(0, -2)
}

if (actualBase64URL === expectedValue) {
if (compareBase64Mixed(actualValue, expectedValue)) {
return true
}
}

// 6. Return false.
// 7. Return false.
return false
}

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

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

const supportedHashes = crypto.getHashes()

// 3. For each token returned by splitting metadata on spaces:
for (const token of metadata.split(' ')) {
// 1. Set empty to false.
Expand All @@ -649,7 +641,11 @@ function parseMetadata (metadata) {
const parsedToken = parseHashWithOptions.exec(token)

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

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

// 5. If algorithm is a hash function recognized by the user
// agent, add the parsed token to result.
if (supportedHashes.includes(algorithm.toLowerCase())) {
if (supportedHashes.includes(algorithm)) {
result.push(parsedToken.groups)
}
}
Expand All @@ -675,6 +671,82 @@ function parseMetadata (metadata) {
return result
}

/**
* @param {{ algo: 'sha256' | 'sha384' | 'sha512' }[]} metadataList
*/
function getStrongestMetadata (metadataList) {
// Let algorithm be the algo component of the first item in metadataList.
// Can be sha256
let algorithm = metadataList[0].algo
// If the algorithm is sha512, then it is the strongest
// and we can return immediately
if (algorithm[3] === '5') {
return algorithm
}

for (let i = 1; i < metadataList.length; ++i) {
const metadata = metadataList[i]
// If the algorithm is sha512, then it is the strongest
// and we can break the loop immediately
if (metadata.algo[3] === '5') {
algorithm = 'sha512'
break
// If the algorithm is sha384, then a potential sha256 or sha384 is ignored
} else if (algorithm[3] === '3') {
continue
// algorithm is sha256, check if algorithm is sha384 and if so, set it as
// the strongest
} else if (metadata.algo[3] === '3') {
algorithm = 'sha384'
}
}
return algorithm
}

function filterMetadataListByAlgorithm (metadataList, algorithm) {
if (metadataList.length === 1) {
return metadataList
}

let pos = 0
for (let i = 0; i < metadataList.length; ++i) {
if (metadataList[i].algo === algorithm) {
metadataList[pos++] = metadataList[i]
}
}

metadataList.length = pos

return metadataList
}

/**
* Compares two base64 strings, allowing for base64url
* in the second string.
*
* @param {string} actualValue always base64
* @param {string} expectedValue base64 or base64url
* @returns {boolean}
*/
function compareBase64Mixed (actualValue, expectedValue) {
if (actualValue.length !== expectedValue.length) {
return false
}
for (let i = 0; i < actualValue.length; ++i) {
if (actualValue[i] !== expectedValue[i]) {
if (
(actualValue[i] === '+' && expectedValue[i] === '-') ||
(actualValue[i] === '/' && expectedValue[i] === '_')
) {
continue
}
return false
}
}

return true
}

// https://w3c.github.io/webappsec-upgrade-insecure-requests/#upgrade-request
function tryUpgradeRequestToAPotentiallyTrustworthyURL (request) {
// TODO
Expand Down

0 comments on commit 3dfc10c

Please sign in to comment.