undici
Advanced tools
Comparing version 5.28.3 to 5.28.4
@@ -12,2 +12,3 @@ 'use strict' | ||
const { stringify } = require('querystring') | ||
const { headerNameLowerCasedRecord } = require('./constants') | ||
@@ -222,2 +223,11 @@ const [nodeMajor, nodeMinor] = process.versions.node.split('.').map(v => Number(v)) | ||
/** | ||
* Retrieves a header name and returns its lowercase value. | ||
* @param {string | Buffer} value Header name | ||
* @returns {string} | ||
*/ | ||
function headerNameToString (value) { | ||
return headerNameLowerCasedRecord[value] || value.toLowerCase() | ||
} | ||
function parseHeaders (headers, obj = {}) { | ||
@@ -494,2 +504,3 @@ // For H2 support | ||
isDestroyed, | ||
headerNameToString, | ||
parseRawHeaders, | ||
@@ -496,0 +507,0 @@ parseHeaders, |
@@ -10,2 +10,4 @@ 'use strict' | ||
let supportedHashes = [] | ||
// https://nodejs.org/api/crypto.html#determining-if-crypto-support-is-unavailable | ||
@@ -17,4 +19,6 @@ /** @type {import('crypto')|undefined} */ | ||
crypto = require('crypto') | ||
const possibleRelevantHashes = ['sha256', 'sha384', 'sha512'] | ||
supportedHashes = crypto.getHashes().filter((hash) => possibleRelevantHashes.includes(hash)) | ||
/* c8 ignore next 3 */ | ||
} catch { | ||
} | ||
@@ -547,3 +551,6 @@ | ||
// 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) { | ||
@@ -553,11 +560,8 @@ 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) { | ||
@@ -568,3 +572,3 @@ // 1. Let algorithm be the alg component of item. | ||
// 2. Let expectedValue be the val component of item. | ||
let expectedValue = item.hash | ||
const expectedValue = item.hash | ||
@@ -574,11 +578,11 @@ // See https://github.com/web-platform-tests/wpt/commit/e4c5cc7a5e48093220528dfdd1c4012dc3837a0e | ||
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) | ||
} | ||
} | ||
@@ -588,18 +592,8 @@ | ||
// return true. | ||
if (actualValue === expectedValue) { | ||
if (compareBase64Mixed(actualValue, expectedValue)) { | ||
return true | ||
} | ||
let actualBase64URL = crypto.createHash(algorithm).update(bytes).digest('base64url') | ||
if (actualBase64URL.endsWith('==')) { | ||
actualBase64URL = actualBase64URL.slice(0, -2) | ||
} | ||
if (actualBase64URL === expectedValue) { | ||
return true | ||
} | ||
} | ||
// 6. Return false. | ||
// 7. Return false. | ||
return false | ||
@@ -611,3 +605,3 @@ } | ||
// https://www.rfc-editor.org/rfc/rfc5234#appendix-B.1 | ||
const parseHashWithOptions = /((?<algo>sha256|sha384|sha512)-(?<hash>[A-z0-9+/]{1}.*={0,2}))( +[\x21-\x7e]?)?/i | ||
const parseHashWithOptions = /(?<algo>sha256|sha384|sha512)-((?<hash>[A-Za-z0-9+/]+|[A-Za-z0-9_-]+)={0,2}(?:\s|$)( +[!-~]*)?)?/i | ||
@@ -626,4 +620,2 @@ /** | ||
const supportedHashes = crypto.getHashes() | ||
// 3. For each token returned by splitting metadata on spaces: | ||
@@ -638,3 +630,7 @@ for (const token of metadata.split(' ')) { | ||
// 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 | ||
@@ -648,7 +644,7 @@ // gives a warning that an invalid integrity was given. The | ||
// 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) | ||
@@ -666,2 +662,78 @@ } | ||
/** | ||
* @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 | ||
@@ -1082,3 +1154,4 @@ function tryUpgradeRequestToAPotentiallyTrustworthyURL (request) { | ||
readAllBytes, | ||
normalizeMethodRecord | ||
normalizeMethodRecord, | ||
parseMetadata | ||
} |
@@ -187,8 +187,13 @@ 'use strict' | ||
function shouldRemoveHeader (header, removeContent, unknownOrigin) { | ||
return ( | ||
(header.length === 4 && header.toString().toLowerCase() === 'host') || | ||
(removeContent && header.toString().toLowerCase().indexOf('content-') === 0) || | ||
(unknownOrigin && header.length === 13 && header.toString().toLowerCase() === 'authorization') || | ||
(unknownOrigin && header.length === 6 && header.toString().toLowerCase() === 'cookie') | ||
) | ||
if (header.length === 4) { | ||
return util.headerNameToString(header) === 'host' | ||
} | ||
if (removeContent && util.headerNameToString(header).startsWith('content-')) { | ||
return true | ||
} | ||
if (unknownOrigin && (header.length === 13 || header.length === 6 || header.length === 19)) { | ||
const name = util.headerNameToString(header) | ||
return name === 'authorization' || name === 'cookie' || name === 'proxy-authorization' | ||
} | ||
return false | ||
} | ||
@@ -195,0 +200,0 @@ |
{ | ||
"name": "undici", | ||
"version": "5.28.3", | ||
"version": "5.28.4", | ||
"description": "An HTTP/1.1 client, written from scratch for Node.js", | ||
@@ -113,3 +113,3 @@ "homepage": "https://undici.nodejs.org", | ||
"form-data": "^4.0.0", | ||
"formdata-node": "^6.0.3", | ||
"formdata-node": "^4.3.1", | ||
"https-pem": "^3.0.0", | ||
@@ -116,0 +116,0 @@ "husky": "^8.0.1", |
1171978
155
20693
8