AikidoSec-safe-chain/packages/safe-chain/src/registryProxy/interceptors/pip/parsePipPackageUrl.js
2026-04-01 20:08:56 -07:00

162 lines
4.8 KiB
JavaScript

/**
* Parses a PyPI metadata URL and returns the package name and API type.
*
* @example
* parsePipMetadataUrl("https://pypi.org/simple/requests/")
* // => { packageName: "requests", type: "simple" }
*
* parsePipMetadataUrl("https://pypi.org/pypi/requests/json")
* // => { packageName: "requests", type: "json" }
*
* parsePipMetadataUrl("https://pypi.org/pypi/requests/2.28.1/json")
* // => { packageName: "requests", type: "json" }
*
* parsePipMetadataUrl("https://files.pythonhosted.org/packages/requests-2.28.1.tar.gz")
* // => { packageName: undefined, type: undefined }
*
* @param {string} url
* @returns {{ packageName: string | undefined, type: "simple" | "json" | undefined }}
*/
export function parsePipMetadataUrl(url) {
if (typeof url !== "string") {
return { packageName: undefined, type: undefined };
}
let urlObj;
try {
urlObj = new URL(url);
} catch {
return { packageName: undefined, type: undefined };
}
const pathSegments = urlObj.pathname.split("/").filter(Boolean);
if (pathSegments[0] === "simple" && pathSegments[1]) {
return {
packageName: decodeURIComponent(pathSegments[1]),
type: "simple",
};
}
if (
pathSegments[0] === "pypi" &&
pathSegments[pathSegments.length - 1] === "json" &&
pathSegments[1]
) {
return {
packageName: decodeURIComponent(pathSegments[1]),
type: "json",
};
}
return { packageName: undefined, type: undefined };
}
/**
* @param {string} url
* @returns {boolean}
*/
export function isPipPackageInfoUrl(url) {
return !!parsePipMetadataUrl(url).packageName;
}
/**
* Parse Python package artifact URLs from PyPI-style registries.
* Examples:
* - Wheel: https://files.pythonhosted.org/packages/.../requests-2.28.1-py3-none-any.whl
* - Wheel metadata: https://files.pythonhosted.org/packages/.../requests-2.28.1-py3-none-any.whl.metadata
* - Sdist: https://files.pythonhosted.org/packages/.../requests-2.28.1.tar.gz
*
* @param {string} url
* @param {string} registry
* @returns {{packageName: string | undefined, version: string | undefined}}
*/
export function parsePipPackageFromUrl(url, registry) {
if (!registry || typeof url !== "string") {
return { packageName: undefined, version: undefined };
}
let urlObj;
try {
urlObj = new URL(url);
} catch {
return { packageName: undefined, version: undefined };
}
const lastSegment = urlObj.pathname.split("/").filter(Boolean).pop();
if (!lastSegment) {
return { packageName: undefined, version: undefined };
}
const filename = decodeURIComponent(lastSegment);
const wheelExtRe = /\.whl(?:\.metadata)?$/;
if (wheelExtRe.test(filename)) {
return parseWheelFilename(filename, wheelExtRe);
}
const sdistExtWithMetadataRe = /\.(tar\.gz|zip|tar\.bz2|tar\.xz)(\.metadata)?$/i;
if (!sdistExtWithMetadataRe.test(filename)) {
return { packageName: undefined, version: undefined };
}
return parseSdistFilename(filename, sdistExtWithMetadataRe);
}
/**
* Parse wheel filenames and Poetry preflight metadata.
* Examples:
* - foo_bar-2.0.0-py3-none-any.whl
* - foo_bar-2.0.0-py3-none-any.whl.metadata
*
* @param {string} filename
* @param {RegExp} wheelExtRe
* @returns {{packageName: string | undefined, version: string | undefined}}
*/
function parseWheelFilename(filename, wheelExtRe) {
const base = filename.replace(wheelExtRe, "");
const firstDash = base.indexOf("-");
if (firstDash <= 0) {
return { packageName: undefined, version: undefined };
}
const packageName = base.slice(0, firstDash);
const rest = base.slice(firstDash + 1);
const secondDash = rest.indexOf("-");
const version = secondDash >= 0 ? rest.slice(0, secondDash) : rest;
// "latest" is a resolver-style token, not an actual published artifact version.
if (version === "latest" || !packageName || !version) {
return { packageName: undefined, version: undefined };
}
return { packageName, version };
}
/**
* Parse source distribution filenames, with optional metadata suffix.
* Examples:
* - requests-2.28.1.tar.gz
* - requests-2.28.1.zip
* - requests-2.28.1.tar.gz.metadata
*
* @param {string} filename
* @param {RegExp} sdistExtWithMetadataRe
* @returns {{packageName: string | undefined, version: string | undefined}}
*/
function parseSdistFilename(filename, sdistExtWithMetadataRe) {
const base = filename.replace(sdistExtWithMetadataRe, "");
const lastDash = base.lastIndexOf("-");
if (lastDash <= 0 || lastDash >= base.length - 1) {
return { packageName: undefined, version: undefined };
}
const packageName = base.slice(0, lastDash);
const version = base.slice(lastDash + 1);
// "latest" is a resolver-style token, not an actual published artifact version.
if (version === "latest" || !packageName || !version) {
return { packageName: undefined, version: undefined };
}
return { packageName, version };
}