From 0aabba668e94a34e3c37dbe7ebc6272b93d5755b Mon Sep 17 00:00:00 2001 From: Reinier Criel Date: Thu, 2 Apr 2026 08:56:20 -0700 Subject: [PATCH] Adapt per review --- .../src/registryProxy/http-utils.js | 55 +++++++++++++++++-- .../pip/pipMetadataVersionUtils.js | 32 ++++++----- .../src/registryProxy/mitmRequestHandler.js | 29 ++-------- 3 files changed, 75 insertions(+), 41 deletions(-) diff --git a/packages/safe-chain/src/registryProxy/http-utils.js b/packages/safe-chain/src/registryProxy/http-utils.js index 967aec8..8e2f8e2 100644 --- a/packages/safe-chain/src/registryProxy/http-utils.js +++ b/packages/safe-chain/src/registryProxy/http-utils.js @@ -16,9 +16,42 @@ export function getHeaderValueAsString(headers, headerName) { return header; } +/** + * Returns a copy of headers without the provided header names, matched + * either exactly or case-insensitively. + * + * @param {NodeJS.Dict | undefined} headers + * @param {string[]} headerNames + * @param {{ caseInsensitive?: boolean }} [options] + * @returns {NodeJS.Dict | undefined} + */ +export function omitHeaders(headers, headerNames, options = {}) { + if (!headers) { + return headers; + } + + const omittedHeaderNames = new Set( + options.caseInsensitive + ? headerNames.map((name) => name.toLowerCase()) + : headerNames + ); + /** @type {NodeJS.Dict} */ + const filteredHeaders = {}; + + for (const [headerName, value] of Object.entries(headers)) { + const comparableHeaderName = options.caseInsensitive + ? headerName.toLowerCase() + : headerName; + if (!omittedHeaderNames.has(comparableHeaderName)) { + filteredHeaders[headerName] = value; + } + } + + return filteredHeaders; +} + /** * Remove headers that become stale when the response body is modified. - * Mutates the provided headers object in place. * * @param {NodeJS.Dict | undefined} headers * @returns {void} @@ -28,8 +61,20 @@ export function clearCachingHeaders(headers) { return; } - delete headers["etag"]; - delete headers["last-modified"]; - delete headers["cache-control"]; - delete headers["content-length"]; + const filteredHeaders = omitHeaders(headers, [ + "etag", + "last-modified", + "cache-control", + "content-length", + ]); + + if (!filteredHeaders) { + return; + } + + for (const key of Object.keys(headers)) { + delete headers[key]; + } + + Object.assign(headers, filteredHeaders); } diff --git a/packages/safe-chain/src/registryProxy/interceptors/pip/pipMetadataVersionUtils.js b/packages/safe-chain/src/registryProxy/interceptors/pip/pipMetadataVersionUtils.js index 938b149..4ccb953 100644 --- a/packages/safe-chain/src/registryProxy/interceptors/pip/pipMetadataVersionUtils.js +++ b/packages/safe-chain/src/registryProxy/interceptors/pip/pipMetadataVersionUtils.js @@ -37,21 +37,27 @@ export function getAvailableVersionsFromJson(json, metadataUrl) { return Object.keys(json.releases); } - if (Array.isArray(json.files)) { - return [ - ...new Set( - json.files - .map((/** @type {any} */ file) => - getPackageVersionFromMetadataFile(file, metadataUrl) - ) - .filter((/** @type {string | undefined} */ version) => - typeof version === "string" - ) - ), - ]; + if (!Array.isArray(json.files)) { + return []; } - return []; + return [ + ...new Set( + json.files + .map((/** @type {any} */ file) => + getPackageVersionFromMetadataFile(file, metadataUrl) + ) + .filter(isDefinedString) + ), + ]; +} + +/** + * @param {string | undefined} value + * @returns {value is string} + */ +function isDefinedString(value) { + return typeof value === "string"; } /** diff --git a/packages/safe-chain/src/registryProxy/mitmRequestHandler.js b/packages/safe-chain/src/registryProxy/mitmRequestHandler.js index b2d82e9..4c4e9ec 100644 --- a/packages/safe-chain/src/registryProxy/mitmRequestHandler.js +++ b/packages/safe-chain/src/registryProxy/mitmRequestHandler.js @@ -3,6 +3,7 @@ import { generateCertForHost } from "./certUtils.js"; import { HttpsProxyAgent } from "https-proxy-agent"; import { ui } from "../environment/userInteraction.js"; import { gunzipSync } from "zlib"; +import { omitHeaders } from "./http-utils.js"; /** * @typedef {import("./interceptors/interceptorBuilder.js").Interceptor} Interceptor @@ -107,28 +108,6 @@ function getRequestPathAndQuery(url) { return url; } -/** - * @param {NodeJS.Dict} headers - * @returns {NodeJS.Dict} - */ -function normalizeRewrittenResponseHeaders(headers) { - /** @type {NodeJS.Dict} */ - const normalizedHeaders = { ...headers }; - - for (const headerName of Object.keys(headers)) { - const lowerHeaderName = headerName.toLowerCase(); - if ( - lowerHeaderName === "content-length" || - lowerHeaderName === "transfer-encoding" || - lowerHeaderName === "content-encoding" - ) { - delete normalizedHeaders[headerName]; - } - } - - return normalizedHeaders; -} - /** * @param {import("http").IncomingMessage} req * @param {string} hostname @@ -240,7 +219,11 @@ function createProxyRequest(hostname, port, req, res, requestHandler) { // For rewritten responses, send the final body uncompressed. // This avoids mismatches between upstream compression metadata and the // rewritten payload on the wire. - const rewrittenHeaders = normalizeRewrittenResponseHeaders(headers); + const rewrittenHeaders = omitHeaders( + headers, + ["content-length", "transfer-encoding", "content-encoding"], + { caseInsensitive: true } + ) || {}; rewrittenHeaders["content-length"] = String(buffer.byteLength); res.writeHead(statusCode, rewrittenHeaders); res.end(buffer);