mirror of
https://github.com/AikidoSec/safe-chain.git
synced 2026-05-26 12:10:49 +00:00
More cleanup
This commit is contained in:
parent
e29c11546c
commit
1a811edc95
5 changed files with 201 additions and 78 deletions
|
|
@ -18,6 +18,8 @@ export function getHeaderValueAsString(headers, headerName) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove headers that become stale when the response body is modified.
|
* Remove headers that become stale when the response body is modified.
|
||||||
|
* Mutates the provided headers object in place.
|
||||||
|
*
|
||||||
* @param {NodeJS.Dict<string | string[]> | undefined} headers
|
* @param {NodeJS.Dict<string | string[]> | undefined} headers
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -3,15 +3,8 @@ import { clearCachingHeaders } from "../../http-utils.js";
|
||||||
import { normalizePipPackageName } from "../../../scanning/packageNameVariants.js";
|
import { normalizePipPackageName } from "../../../scanning/packageNameVariants.js";
|
||||||
import { parsePipPackageFromUrl } from "./parsePipPackageUrl.js";
|
import { parsePipPackageFromUrl } from "./parsePipPackageUrl.js";
|
||||||
export { parsePipMetadataUrl, isPipPackageInfoUrl } from "./parsePipPackageUrl.js";
|
export { parsePipMetadataUrl, isPipPackageInfoUrl } from "./parsePipPackageUrl.js";
|
||||||
import {
|
import { getPipMetadataContentType, logSuppressedVersion } from "./pipMetadataResponseUtils.js";
|
||||||
calculateLatestVersion,
|
import { modifyPipJsonResponse } from "./modifyPipJsonResponse.js";
|
||||||
getAvailableVersionsFromJson,
|
|
||||||
getPackageVersionFromMetadataFile,
|
|
||||||
} from "./pipMetadataVersionUtils.js";
|
|
||||||
import {
|
|
||||||
getPipMetadataContentType,
|
|
||||||
logSuppressedVersion,
|
|
||||||
} from "./pipMetadataResponseUtils.js";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Buffer} body
|
* @param {Buffer} body
|
||||||
|
|
@ -134,63 +127,12 @@ function modifyJsonResponse(
|
||||||
packageName
|
packageName
|
||||||
) {
|
) {
|
||||||
const json = JSON.parse(body.toString("utf8"));
|
const json = JSON.parse(body.toString("utf8"));
|
||||||
let modified = false;
|
const modified = modifyPipJsonResponse(
|
||||||
|
json,
|
||||||
if (Array.isArray(json.files)) {
|
metadataUrl,
|
||||||
const filteredFiles = json.files.filter((/** @type {any} */ file) => {
|
isNewlyReleasedPackage,
|
||||||
const version = getPackageVersionFromMetadataFile(file, metadataUrl);
|
packageName
|
||||||
|
);
|
||||||
if (version && isNewlyReleasedPackage(packageName, version)) {
|
|
||||||
modified = true;
|
|
||||||
logSuppressedVersion(packageName, version);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
json.files = filteredFiles;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (json.releases && typeof json.releases === "object") {
|
|
||||||
for (const [version, files] of Object.entries(json.releases)) {
|
|
||||||
if (
|
|
||||||
Array.isArray(/** @type {unknown[]} */ (files)) &&
|
|
||||||
isNewlyReleasedPackage(packageName, version)
|
|
||||||
) {
|
|
||||||
delete json.releases[version];
|
|
||||||
modified = true;
|
|
||||||
logSuppressedVersion(packageName, version);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Array.isArray(json.urls)) {
|
|
||||||
json.urls = json.urls.filter((/** @type {any} */ file) => {
|
|
||||||
const version = getPackageVersionFromMetadataFile(file, metadataUrl);
|
|
||||||
|
|
||||||
if (version && isNewlyReleasedPackage(packageName, version)) {
|
|
||||||
modified = true;
|
|
||||||
logSuppressedVersion(packageName, version);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (json.info && typeof json.info === "object") {
|
|
||||||
const candidateVersions = getAvailableVersionsFromJson(json, metadataUrl);
|
|
||||||
const replacementVersion = calculateLatestVersion(candidateVersions);
|
|
||||||
|
|
||||||
if (
|
|
||||||
typeof json.info.version === "string" &&
|
|
||||||
replacementVersion &&
|
|
||||||
json.info.version !== replacementVersion
|
|
||||||
) {
|
|
||||||
json.info.version = replacementVersion;
|
|
||||||
modified = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!modified) return body;
|
if (!modified) return body;
|
||||||
const modifiedBuffer = Buffer.from(JSON.stringify(json));
|
const modifiedBuffer = Buffer.from(JSON.stringify(json));
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,168 @@
|
||||||
|
import {
|
||||||
|
calculateLatestVersion,
|
||||||
|
getAvailableVersionsFromJson,
|
||||||
|
getPackageVersionFromMetadataFile,
|
||||||
|
} from "./pipMetadataVersionUtils.js";
|
||||||
|
import { logSuppressedVersion } from "./pipMetadataResponseUtils.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {any} json
|
||||||
|
* @param {string} metadataUrl
|
||||||
|
* @param {(packageName: string | undefined, version: string | undefined) => boolean} isNewlyReleasedPackage
|
||||||
|
* @param {string} packageName
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
export function modifyPipJsonResponse(
|
||||||
|
json,
|
||||||
|
metadataUrl,
|
||||||
|
isNewlyReleasedPackage,
|
||||||
|
packageName
|
||||||
|
) {
|
||||||
|
const filesModified = filterJsonMetadataFiles(
|
||||||
|
json,
|
||||||
|
metadataUrl,
|
||||||
|
isNewlyReleasedPackage,
|
||||||
|
packageName
|
||||||
|
);
|
||||||
|
const releasesModified = removeJsonMetadataReleases(
|
||||||
|
json,
|
||||||
|
isNewlyReleasedPackage,
|
||||||
|
packageName
|
||||||
|
);
|
||||||
|
const urlsModified = filterJsonMetadataUrls(
|
||||||
|
json,
|
||||||
|
metadataUrl,
|
||||||
|
isNewlyReleasedPackage,
|
||||||
|
packageName
|
||||||
|
);
|
||||||
|
const versionModified = updateJsonInfoVersion(json, metadataUrl);
|
||||||
|
|
||||||
|
return filesModified || releasesModified || urlsModified || versionModified;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {any} json
|
||||||
|
* @param {string} metadataUrl
|
||||||
|
* @param {(packageName: string | undefined, version: string | undefined) => boolean} isNewlyReleasedPackage
|
||||||
|
* @param {string} packageName
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
function filterJsonMetadataFiles(
|
||||||
|
json,
|
||||||
|
metadataUrl,
|
||||||
|
isNewlyReleasedPackage,
|
||||||
|
packageName
|
||||||
|
) {
|
||||||
|
if (!Array.isArray(json.files)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let modified = false;
|
||||||
|
json.files = json.files.filter((/** @type {any} */ file) => {
|
||||||
|
const version = getPackageVersionFromMetadataFile(file, metadataUrl);
|
||||||
|
|
||||||
|
if (version && isNewlyReleasedPackage(packageName, version)) {
|
||||||
|
modified = true;
|
||||||
|
logSuppressedVersion(packageName, version);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
return modified;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {any} json
|
||||||
|
* @param {(packageName: string | undefined, version: string | undefined) => boolean} isNewlyReleasedPackage
|
||||||
|
* @param {string} packageName
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
function removeJsonMetadataReleases(json, isNewlyReleasedPackage, packageName) {
|
||||||
|
if (!json.releases || typeof json.releases !== "object") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let modified = false;
|
||||||
|
|
||||||
|
for (const [version, files] of Object.entries(json.releases)) {
|
||||||
|
if (
|
||||||
|
Array.isArray(/** @type {unknown[]} */ (files)) &&
|
||||||
|
isNewlyReleasedPackage(packageName, version)
|
||||||
|
) {
|
||||||
|
delete json.releases[version];
|
||||||
|
modified = true;
|
||||||
|
logSuppressedVersion(packageName, version);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return modified;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {any} json
|
||||||
|
* @param {string} metadataUrl
|
||||||
|
* @param {(packageName: string | undefined, version: string | undefined) => boolean} isNewlyReleasedPackage
|
||||||
|
* @param {string} packageName
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
function filterJsonMetadataUrls(
|
||||||
|
json,
|
||||||
|
metadataUrl,
|
||||||
|
isNewlyReleasedPackage,
|
||||||
|
packageName
|
||||||
|
) {
|
||||||
|
if (!Array.isArray(json.urls)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let modified = false;
|
||||||
|
json.urls = json.urls.filter((/** @type {any} */ file) => {
|
||||||
|
const version = getPackageVersionFromMetadataFile(file, metadataUrl);
|
||||||
|
|
||||||
|
if (version && isNewlyReleasedPackage(packageName, version)) {
|
||||||
|
modified = true;
|
||||||
|
logSuppressedVersion(packageName, version);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
return modified;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {any} json
|
||||||
|
* @param {string} metadataUrl
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
function updateJsonInfoVersion(json, metadataUrl) {
|
||||||
|
if (!json.info || typeof json.info !== "object") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const replacementVersion = computeReplacementVersion(json, metadataUrl);
|
||||||
|
|
||||||
|
if (
|
||||||
|
typeof json.info.version !== "string" ||
|
||||||
|
!replacementVersion ||
|
||||||
|
json.info.version === replacementVersion
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
json.info.version = replacementVersion;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {any} json
|
||||||
|
* @param {string} metadataUrl
|
||||||
|
* @returns {string | undefined}
|
||||||
|
*/
|
||||||
|
function computeReplacementVersion(json, metadataUrl) {
|
||||||
|
const candidateVersions = getAvailableVersionsFromJson(json, metadataUrl);
|
||||||
|
return calculateLatestVersion(candidateVersions);
|
||||||
|
}
|
||||||
|
|
@ -3,6 +3,10 @@ const state = {
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Tracks whether any rewritten metadata response suppressed versions during the
|
||||||
|
* current process lifetime. This is intentional shared state used only for the
|
||||||
|
* end-of-run summary message exposed through the proxy API.
|
||||||
|
*
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
export function recordSuppressedVersion() {
|
export function recordSuppressedVersion() {
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import https from "https";
|
||||||
import { generateCertForHost } from "./certUtils.js";
|
import { generateCertForHost } from "./certUtils.js";
|
||||||
import { HttpsProxyAgent } from "https-proxy-agent";
|
import { HttpsProxyAgent } from "https-proxy-agent";
|
||||||
import { ui } from "../environment/userInteraction.js";
|
import { ui } from "../environment/userInteraction.js";
|
||||||
import { gunzipSync, gzipSync } from "zlib";
|
import { gunzipSync } from "zlib";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {import("./interceptors/interceptorBuilder.js").Interceptor} Interceptor
|
* @typedef {import("./interceptors/interceptorBuilder.js").Interceptor} Interceptor
|
||||||
|
|
@ -107,6 +107,23 @@ function getRequestPathAndQuery(url) {
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {NodeJS.Dict<string | string[]>} headers
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
function normalizeRewrittenResponseHeaders(headers) {
|
||||||
|
for (const headerName of Object.keys(headers)) {
|
||||||
|
const lowerHeaderName = headerName.toLowerCase();
|
||||||
|
if (
|
||||||
|
lowerHeaderName === "content-length" ||
|
||||||
|
lowerHeaderName === "transfer-encoding" ||
|
||||||
|
lowerHeaderName === "content-encoding"
|
||||||
|
) {
|
||||||
|
delete headers[headerName];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {import("http").IncomingMessage} req
|
* @param {import("http").IncomingMessage} req
|
||||||
* @param {string} hostname
|
* @param {string} hostname
|
||||||
|
|
@ -218,17 +235,7 @@ function createProxyRequest(hostname, port, req, res, requestHandler) {
|
||||||
// For rewritten responses, send the final body uncompressed.
|
// For rewritten responses, send the final body uncompressed.
|
||||||
// This avoids mismatches between upstream compression metadata and the
|
// This avoids mismatches between upstream compression metadata and the
|
||||||
// rewritten payload on the wire.
|
// rewritten payload on the wire.
|
||||||
for (const headerName of Object.keys(headers)) {
|
normalizeRewrittenResponseHeaders(headers);
|
||||||
const lowerHeaderName = headerName.toLowerCase();
|
|
||||||
if (
|
|
||||||
lowerHeaderName === "content-length" ||
|
|
||||||
lowerHeaderName === "transfer-encoding" ||
|
|
||||||
lowerHeaderName === "content-encoding"
|
|
||||||
) {
|
|
||||||
delete headers[headerName];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
headers["content-length"] = String(buffer.byteLength);
|
headers["content-length"] = String(buffer.byteLength);
|
||||||
res.writeHead(statusCode, headers);
|
res.writeHead(statusCode, headers);
|
||||||
res.end(buffer);
|
res.end(buffer);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue