@cdxgen/cdxgen 0.0.1 → 13.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +921 -87
- package/bin/audit.js +239 -0
- package/bin/cdxgen.js +2335 -0
- package/bin/convert.js +220 -0
- package/bin/evinse.js +353 -0
- package/bin/hbom.js +504 -0
- package/bin/repl.js +2224 -0
- package/bin/sign.js +195 -0
- package/bin/tracebom.js +303 -0
- package/bin/validate.js +268 -0
- package/bin/verify.js +189 -0
- package/data/README.md +307 -0
- package/data/bom-1.4.schema.json +1605 -0
- package/data/bom-1.5.schema.json +3660 -0
- package/data/bom-1.6.schema.json +5511 -0
- package/data/bom-1.7.schema.json +6700 -0
- package/data/cbomosdb-queries.json +68 -0
- package/data/component-tags.json +339 -0
- package/data/container-knowledge-index.json +125 -0
- package/data/cosdb-queries.json +17 -0
- package/data/crypto-oid.json +1998 -0
- package/data/cryptography-defs.json +3793 -0
- package/data/cryptography-defs.schema.json +576 -0
- package/data/cyclonedx-2.0-bundled.schema.json +7182 -0
- package/data/frameworks-list.json +219 -0
- package/data/glibc-stdlib.json +117 -0
- package/data/gtfobins-index.json +6296 -0
- package/data/helpers/init.gradle +71 -0
- package/data/jsf-0.82.schema.json +211 -0
- package/data/known-licenses.json +82 -0
- package/data/lic-mapping.json +338 -0
- package/data/license-aliases.json +6624 -0
- package/data/license-db.json +39059 -0
- package/data/license-deprecations.json +302 -0
- package/data/lolbas-index.json +150 -0
- package/data/predictive-audit-allowlist.json +11 -0
- package/data/pypi-pkg-aliases.json +1199 -0
- package/data/python-stdlib.json +308 -0
- package/data/queries-darwin.json +193 -0
- package/data/queries-win.json +257 -0
- package/data/queries.json +268 -0
- package/data/ruby-known-modules.json +281 -0
- package/data/rules/ai-agent-governance.yaml +323 -0
- package/data/rules/ai-bom.yaml +268 -0
- package/data/rules/ai-oversight.yaml +186 -0
- package/data/rules/ai-provenance.yaml +92 -0
- package/data/rules/asar-archives.yaml +150 -0
- package/data/rules/cbom-security.yaml +165 -0
- package/data/rules/chrome-extensions.yaml +248 -0
- package/data/rules/ci-permissions.yaml +802 -0
- package/data/rules/container-risk.yaml +353 -0
- package/data/rules/dependency-sources.yaml +236 -0
- package/data/rules/golem-go.yaml +229 -0
- package/data/rules/hbom-compliance.yaml +325 -0
- package/data/rules/hbom-performance.yaml +307 -0
- package/data/rules/hbom-security.yaml +248 -0
- package/data/rules/host-topology.yaml +165 -0
- package/data/rules/mcp-servers.yaml +388 -0
- package/data/rules/obom-runtime.yaml +1776 -0
- package/data/rules/package-integrity.yaml +364 -0
- package/data/rules/rootfs-hardening.yaml +179 -0
- package/data/rules/vscode-extensions.yaml +237 -0
- package/data/spdx-export.schema.json +6794 -0
- package/data/spdx-license-list.json +7449 -0
- package/data/spdx-licenses.json +759 -0
- package/data/spdx.schema.json +819 -0
- package/data/templates/README.md +3 -0
- package/data/templates/asvs-4.0.3.cdx.json +2943 -0
- package/data/templates/asvs-5.0.cdx.json +3046 -0
- package/data/templates/bsimm-v13.cdx.json +1008 -0
- package/data/templates/masvs-2.0.0.cdx.json +225 -0
- package/data/templates/nist-ssdf-1.1.cdx.json +828 -0
- package/data/templates/pcissc-secure-slc-1.1.cdx.json +1027 -0
- package/data/templates/scvs-1.0.0.cdx.json +813 -0
- package/data/templates/ssaf-DRAFT-2023-11.cdx.json +127 -0
- package/data/vendor-alias.json +41967 -0
- package/data/wrapdb-releases.json +4227 -0
- package/index.cjs +15 -0
- package/lib/audit/index.js +2800 -0
- package/lib/audit/progress.js +114 -0
- package/lib/audit/reporters.js +937 -0
- package/lib/audit/scoring.js +560 -0
- package/lib/audit/targets.js +798 -0
- package/lib/cli/bomAssembly.js +1882 -0
- package/lib/cli/cliOptions.js +543 -0
- package/lib/cli/index.js +2759 -0
- package/lib/cli/jsBom.js +2087 -0
- package/lib/cli/jvmBom.js +1858 -0
- package/lib/cli/managedBom.js +2083 -0
- package/lib/cli/nativeBom.js +2125 -0
- package/lib/core/activity.js +1087 -0
- package/lib/core/env.js +719 -0
- package/lib/core/fs.js +1058 -0
- package/lib/core/httpClient.js +730 -0
- package/lib/core/logger.js +280 -0
- package/lib/core/parallel.js +323 -0
- package/lib/core/paths.js +70 -0
- package/lib/core/projectConfig.js +122 -0
- package/lib/core/propertySanitizer.js +174 -0
- package/lib/core/spdx.js +5 -0
- package/lib/core/state.js +59 -0
- package/lib/core/text.js +207 -0
- package/lib/core/ui.js +920 -0
- package/lib/core/urls.js +84 -0
- package/lib/ecosystems/asarutils.js +1636 -0
- package/lib/ecosystems/bunutils.js +402 -0
- package/lib/ecosystems/caxa.js +77 -0
- package/lib/ecosystems/chromextutils.js +1174 -0
- package/lib/ecosystems/cmakeResolver.js +587 -0
- package/lib/ecosystems/cppEvidence.js +305 -0
- package/lib/ecosystems/denoutils.js +1094 -0
- package/lib/ecosystems/dotnetutils.js +132 -0
- package/lib/ecosystems/ecosystems.js +2772 -0
- package/lib/ecosystems/golem.js +1807 -0
- package/lib/ecosystems/gradleutils.js +1048 -0
- package/lib/ecosystems/jsEvidence.js +317 -0
- package/lib/ecosystems/npmutils.js +586 -0
- package/lib/ecosystems/parsers-bazel.js +875 -0
- package/lib/ecosystems/parsers-dotnet.js +1430 -0
- package/lib/ecosystems/parsers-gleam.js +302 -0
- package/lib/ecosystems/parsers-go.js +818 -0
- package/lib/ecosystems/parsers-js.js +4222 -0
- package/lib/ecosystems/parsers-jvm.js +948 -0
- package/lib/ecosystems/parsers-misc.js +3191 -0
- package/lib/ecosystems/parsers-python.js +2070 -0
- package/lib/ecosystems/parsers-rust.js +1294 -0
- package/lib/ecosystems/parsers-zig.js +861 -0
- package/lib/ecosystems/pep770.js +341 -0
- package/lib/ecosystems/pylockutils.js +427 -0
- package/lib/ecosystems/pythonutils.js +1281 -0
- package/lib/ecosystems/registryProvenance.js +1003 -0
- package/lib/ecosystems/remote/huggingface.js +1343 -0
- package/lib/ecosystems/rubyutils.js +1629 -0
- package/lib/ecosystems/rusi.js +820 -0
- package/lib/ecosystems/sbtutils.js +972 -0
- package/lib/ecosystems/tea.js +607 -0
- package/lib/ecosystems/utils.js +583 -0
- package/lib/ecosystems/zigResolver.js +410 -0
- package/lib/evinser/db.js +145 -0
- package/lib/evinser/evinser.js +2362 -0
- package/lib/evinser/scalasem.js +65 -0
- package/lib/evinser/swiftsem.js +767 -0
- package/lib/helpers/annotationFormatter.js +84 -0
- package/lib/helpers/bomSigner.js +312 -0
- package/lib/helpers/exportUtils.js +160 -0
- package/lib/helpers/remote/dependency-track.js +117 -0
- package/lib/helpers/versutils.js +202 -0
- package/lib/helpers/vsixutils.js +1069 -0
- package/lib/inventory/agentFormulationParser.js +322 -0
- package/lib/inventory/aiCollector.js +2140 -0
- package/lib/inventory/aiInventory.js +414 -0
- package/lib/inventory/aiModelVariants.js +121 -0
- package/lib/inventory/aiOversightCollector.js +661 -0
- package/lib/inventory/aiProvenanceCollector.js +822 -0
- package/lib/inventory/analyzer.js +6089 -0
- package/lib/inventory/analyzerScope.js +807 -0
- package/lib/inventory/astWorker.js +20 -0
- package/lib/inventory/atomUtils.js +1092 -0
- package/lib/inventory/auditCategories.js +192 -0
- package/lib/inventory/bomUtils.js +369 -0
- package/lib/inventory/cacheCommands.js +182 -0
- package/lib/inventory/cacheDir.js +104 -0
- package/lib/inventory/cbomutils.js +590 -0
- package/lib/inventory/cdxrs.js +434 -0
- package/lib/inventory/ciParsers/azurePipelines.js +295 -0
- package/lib/inventory/ciParsers/circleCi.js +286 -0
- package/lib/inventory/ciParsers/common.js +24 -0
- package/lib/inventory/ciParsers/githubActions.js +2434 -0
- package/lib/inventory/ciParsers/gitlabCi.js +213 -0
- package/lib/inventory/ciParsers/jenkins.js +180 -0
- package/lib/inventory/citations.js +234 -0
- package/lib/inventory/communityAiConfigParser.js +756 -0
- package/lib/inventory/containerRisk.js +204 -0
- package/lib/inventory/cryptoAlgorithmFamily.js +258 -0
- package/lib/inventory/deps.js +807 -0
- package/lib/inventory/depsUtils.js +588 -0
- package/lib/inventory/display.js +2128 -0
- package/lib/inventory/dosai.js +601 -0
- package/lib/inventory/dosaiParsers.js +154 -0
- package/lib/inventory/dynamic.js +145 -0
- package/lib/inventory/envcontext.js +1793 -0
- package/lib/inventory/evidenceUtils.js +774 -0
- package/lib/inventory/fetchBatch.js +779 -0
- package/lib/inventory/fetchRate.js +255 -0
- package/lib/inventory/forgeEnricher.js +300 -0
- package/lib/inventory/formulationParsers.js +422 -0
- package/lib/inventory/gtfobins.js +339 -0
- package/lib/inventory/hbom.js +719 -0
- package/lib/inventory/hbomAnalysis.js +310 -0
- package/lib/inventory/hbomLoader.js +35 -0
- package/lib/inventory/hostTopology.js +835 -0
- package/lib/inventory/inventoryStats.js +100 -0
- package/lib/inventory/jsonLike.js +121 -0
- package/lib/inventory/licenseEnhancer.js +918 -0
- package/lib/inventory/lolbas.js +285 -0
- package/lib/inventory/mcp.js +253 -0
- package/lib/inventory/mcpConfigParser.js +683 -0
- package/lib/inventory/mcpDiscovery.js +112 -0
- package/lib/inventory/mcpPinning.js +209 -0
- package/lib/inventory/osPackageResolver.js +493 -0
- package/lib/inventory/osinfo.js +383 -0
- package/lib/inventory/osqueryTransform.js +274 -0
- package/lib/inventory/plugins.js +466 -0
- package/lib/inventory/protobom.js +231 -0
- package/lib/inventory/protobomLoader.js +43 -0
- package/lib/inventory/provenanceUtils.js +224 -0
- package/lib/inventory/purl.js +749 -0
- package/lib/inventory/rustFormulationParser.js +339 -0
- package/lib/inventory/sbomDocument.js +340 -0
- package/lib/inventory/source.js +1488 -0
- package/lib/inventory/spdx.js +361 -0
- package/lib/inventory/spdxExpression.js +512 -0
- package/lib/inventory/spdxUtils.js +107 -0
- package/lib/inventory/table.js +267 -0
- package/lib/inventory/traceRunner.js +439 -0
- package/lib/inventory/unicodeScan.js +147 -0
- package/lib/managers/binary.js +3459 -0
- package/lib/managers/containerutils.js +125 -0
- package/lib/managers/docker.js +2145 -0
- package/lib/managers/dockerConnection.js +162 -0
- package/lib/managers/oci.js +704 -0
- package/lib/managers/piptree.js +267 -0
- package/lib/parsers/cmakeCache.js +199 -0
- package/lib/parsers/gguf.js +492 -0
- package/lib/parsers/gitAiNotes.js +233 -0
- package/lib/parsers/gitmodules.js +239 -0
- package/lib/parsers/htmlExtract.js +610 -0
- package/lib/parsers/huggingfaceManifest.js +551 -0
- package/lib/parsers/huggingfaceUtils.js +506 -0
- package/lib/parsers/iri.js +618 -0
- package/lib/parsers/npmrc.js +99 -0
- package/lib/parsers/ollama.js +101 -0
- package/lib/parsers/xml.js +841 -0
- package/lib/server/openapi.yaml +433 -0
- package/lib/server/server.js +616 -0
- package/lib/stages/postgen/annotator.js +977 -0
- package/lib/stages/postgen/auditBom.js +468 -0
- package/lib/stages/postgen/postgen.js +1239 -0
- package/lib/stages/postgen/ruleEngine.js +731 -0
- package/lib/stages/postgen/sortBom.js +195 -0
- package/lib/stages/postgen/spdxConverter.js +796 -0
- package/lib/stages/postgen/specVersionCompat.js +703 -0
- package/lib/stages/pregen/envAudit.js +388 -0
- package/lib/stages/pregen/pregen.js +637 -0
- package/lib/third-party/README.md +9 -0
- package/lib/third-party/arborist/CHANGELOG.md +1109 -0
- package/lib/third-party/arborist/LICENSE.md +20 -0
- package/lib/third-party/arborist/README.md +315 -0
- package/lib/third-party/arborist/lib/arborist/index.js +258 -0
- package/lib/third-party/arborist/lib/arborist/load-actual.js +605 -0
- package/lib/third-party/arborist/lib/arborist/load-virtual.js +300 -0
- package/lib/third-party/arborist/lib/bin-links.js +120 -0
- package/lib/third-party/arborist/lib/calc-dep-flags.js +130 -0
- package/lib/third-party/arborist/lib/case-insensitive-map.js +51 -0
- package/lib/third-party/arborist/lib/consistent-resolve.js +42 -0
- package/lib/third-party/arborist/lib/debug.js +50 -0
- package/lib/third-party/arborist/lib/dep-valid.js +190 -0
- package/lib/third-party/arborist/lib/edge.js +417 -0
- package/lib/third-party/arborist/lib/from-path.js +36 -0
- package/lib/third-party/arborist/lib/gather-dep-set.js +43 -0
- package/lib/third-party/arborist/lib/index.js +22 -0
- package/lib/third-party/arborist/lib/inventory.js +138 -0
- package/lib/third-party/arborist/lib/link.js +169 -0
- package/lib/third-party/arborist/lib/node.js +1673 -0
- package/lib/third-party/arborist/lib/npm-extension.js +45 -0
- package/lib/third-party/arborist/lib/override-resolves.js +11 -0
- package/lib/third-party/arborist/lib/override-set.js +319 -0
- package/lib/third-party/arborist/lib/package-extensions.js +289 -0
- package/lib/third-party/arborist/lib/printable.js +210 -0
- package/lib/third-party/arborist/lib/proc-log.js +30 -0
- package/lib/third-party/arborist/lib/realpath.js +100 -0
- package/lib/third-party/arborist/lib/relpath.js +4 -0
- package/lib/third-party/arborist/lib/shrinkwrap.js +1291 -0
- package/lib/third-party/arborist/lib/spec-from-lock.js +34 -0
- package/lib/third-party/arborist/lib/tree-check.js +165 -0
- package/lib/third-party/arborist/lib/validate-npm-package-name.js +107 -0
- package/lib/third-party/arborist/lib/version-from-tgz.js +51 -0
- package/lib/third-party/arborist/lib/yarn-lock.js +397 -0
- package/lib/validator/bomValidator.js +985 -0
- package/lib/validator/complianceEngine.js +241 -0
- package/lib/validator/complianceRules.js +1680 -0
- package/lib/validator/generated/runtime.mjs +17 -0
- package/lib/validator/generated/validate-1.6.mjs +21258 -0
- package/lib/validator/generated/validate-1.7.mjs +24803 -0
- package/lib/validator/generated/validate-2.0.mjs +22931 -0
- package/lib/validator/index.js +246 -0
- package/lib/validator/reporters/annotations.js +142 -0
- package/lib/validator/reporters/console.js +160 -0
- package/lib/validator/reporters/index.js +41 -0
- package/lib/validator/reporters/json.js +37 -0
- package/lib/validator/reporters/sarif.js +184 -0
- package/package.json +209 -26
- package/types/bin/audit.d.ts +3 -0
- package/types/bin/audit.d.ts.map +1 -0
- package/types/bin/cdxgen.d.ts +3 -0
- package/types/bin/cdxgen.d.ts.map +1 -0
- package/types/bin/convert.d.ts +3 -0
- package/types/bin/convert.d.ts.map +1 -0
- package/types/bin/evinse.d.ts +3 -0
- package/types/bin/evinse.d.ts.map +1 -0
- package/types/bin/hbom.d.ts +3 -0
- package/types/bin/hbom.d.ts.map +1 -0
- package/types/bin/repl.d.ts +3 -0
- package/types/bin/repl.d.ts.map +1 -0
- package/types/bin/sign.d.ts +3 -0
- package/types/bin/sign.d.ts.map +1 -0
- package/types/bin/tracebom.d.ts +3 -0
- package/types/bin/tracebom.d.ts.map +1 -0
- package/types/bin/validate.d.ts +14 -0
- package/types/bin/validate.d.ts.map +1 -0
- package/types/bin/verify.d.ts +3 -0
- package/types/bin/verify.d.ts.map +1 -0
- package/types/lib/audit/index.d.ts +153 -0
- package/types/lib/audit/index.d.ts.map +1 -0
- package/types/lib/audit/progress.d.ts +31 -0
- package/types/lib/audit/progress.d.ts.map +1 -0
- package/types/lib/audit/reporters.d.ts +62 -0
- package/types/lib/audit/reporters.d.ts.map +1 -0
- package/types/lib/audit/scoring.d.ts +36 -0
- package/types/lib/audit/scoring.d.ts.map +1 -0
- package/types/lib/audit/targets.d.ts +77 -0
- package/types/lib/audit/targets.d.ts.map +1 -0
- package/types/lib/cli/bomAssembly.d.ts +329 -0
- package/types/lib/cli/bomAssembly.d.ts.map +1 -0
- package/types/lib/cli/cliOptions.d.ts +156 -0
- package/types/lib/cli/cliOptions.d.ts.map +1 -0
- package/types/lib/cli/index.d.ts +107 -0
- package/types/lib/cli/index.d.ts.map +1 -0
- package/types/lib/cli/jsBom.d.ts +94 -0
- package/types/lib/cli/jsBom.d.ts.map +1 -0
- package/types/lib/cli/jvmBom.d.ts +54 -0
- package/types/lib/cli/jvmBom.d.ts.map +1 -0
- package/types/lib/cli/managedBom.d.ts +105 -0
- package/types/lib/cli/managedBom.d.ts.map +1 -0
- package/types/lib/cli/nativeBom.d.ts +143 -0
- package/types/lib/cli/nativeBom.d.ts.map +1 -0
- package/types/lib/core/activity.d.ts +178 -0
- package/types/lib/core/activity.d.ts.map +1 -0
- package/types/lib/core/env.d.ts +192 -0
- package/types/lib/core/env.d.ts.map +1 -0
- package/types/lib/core/fs.d.ts +158 -0
- package/types/lib/core/fs.d.ts.map +1 -0
- package/types/lib/core/httpClient.d.ts +149 -0
- package/types/lib/core/httpClient.d.ts.map +1 -0
- package/types/lib/core/logger.d.ts +50 -0
- package/types/lib/core/logger.d.ts.map +1 -0
- package/types/lib/core/parallel.d.ts +95 -0
- package/types/lib/core/parallel.d.ts.map +1 -0
- package/types/lib/core/paths.d.ts +36 -0
- package/types/lib/core/paths.d.ts.map +1 -0
- package/types/lib/core/projectConfig.d.ts +23 -0
- package/types/lib/core/projectConfig.d.ts.map +1 -0
- package/types/lib/core/propertySanitizer.d.ts +23 -0
- package/types/lib/core/propertySanitizer.d.ts.map +1 -0
- package/types/lib/core/spdx.d.ts +5 -0
- package/types/lib/core/spdx.d.ts.map +1 -0
- package/types/lib/core/state.d.ts +20 -0
- package/types/lib/core/state.d.ts.map +1 -0
- package/types/lib/core/text.d.ts +78 -0
- package/types/lib/core/text.d.ts.map +1 -0
- package/types/lib/core/ui.d.ts +87 -0
- package/types/lib/core/ui.d.ts.map +1 -0
- package/types/lib/core/urls.d.ts +43 -0
- package/types/lib/core/urls.d.ts.map +1 -0
- package/types/lib/ecosystems/asarutils.d.ts +71 -0
- package/types/lib/ecosystems/asarutils.d.ts.map +1 -0
- package/types/lib/ecosystems/bunutils.d.ts +38 -0
- package/types/lib/ecosystems/bunutils.d.ts.map +1 -0
- package/types/lib/ecosystems/caxa.d.ts +8 -0
- package/types/lib/ecosystems/caxa.d.ts.map +1 -0
- package/types/lib/ecosystems/chromextutils.d.ts +97 -0
- package/types/lib/ecosystems/chromextutils.d.ts.map +1 -0
- package/types/lib/ecosystems/cmakeResolver.d.ts +176 -0
- package/types/lib/ecosystems/cmakeResolver.d.ts.map +1 -0
- package/types/lib/ecosystems/cppEvidence.d.ts +35 -0
- package/types/lib/ecosystems/cppEvidence.d.ts.map +1 -0
- package/types/lib/ecosystems/denoutils.d.ts +76 -0
- package/types/lib/ecosystems/denoutils.d.ts.map +1 -0
- package/types/lib/ecosystems/dotnetutils.d.ts +45 -0
- package/types/lib/ecosystems/dotnetutils.d.ts.map +1 -0
- package/types/lib/ecosystems/ecosystems.d.ts +279 -0
- package/types/lib/ecosystems/ecosystems.d.ts.map +1 -0
- package/types/lib/ecosystems/golem.d.ts +60 -0
- package/types/lib/ecosystems/golem.d.ts.map +1 -0
- package/types/lib/ecosystems/gradleutils.d.ts +139 -0
- package/types/lib/ecosystems/gradleutils.d.ts.map +1 -0
- package/types/lib/ecosystems/jsEvidence.d.ts +17 -0
- package/types/lib/ecosystems/jsEvidence.d.ts.map +1 -0
- package/types/lib/ecosystems/npmutils.d.ts +183 -0
- package/types/lib/ecosystems/npmutils.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-bazel.d.ts +36 -0
- package/types/lib/ecosystems/parsers-bazel.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-dotnet.d.ts +116 -0
- package/types/lib/ecosystems/parsers-dotnet.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-gleam.d.ts +33 -0
- package/types/lib/ecosystems/parsers-gleam.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-go.d.ts +92 -0
- package/types/lib/ecosystems/parsers-go.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-js.d.ts +242 -0
- package/types/lib/ecosystems/parsers-js.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-jvm.d.ts +100 -0
- package/types/lib/ecosystems/parsers-jvm.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-misc.d.ts +450 -0
- package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-python.d.ts +145 -0
- package/types/lib/ecosystems/parsers-python.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-rust.d.ts +56 -0
- package/types/lib/ecosystems/parsers-rust.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-zig.d.ts +83 -0
- package/types/lib/ecosystems/parsers-zig.d.ts.map +1 -0
- package/types/lib/ecosystems/pep770.d.ts +38 -0
- package/types/lib/ecosystems/pep770.d.ts.map +1 -0
- package/types/lib/ecosystems/pylockutils.d.ts +68 -0
- package/types/lib/ecosystems/pylockutils.d.ts.map +1 -0
- package/types/lib/ecosystems/pythonutils.d.ts +70 -0
- package/types/lib/ecosystems/pythonutils.d.ts.map +1 -0
- package/types/lib/ecosystems/registryProvenance.d.ts +26 -0
- package/types/lib/ecosystems/registryProvenance.d.ts.map +1 -0
- package/types/lib/ecosystems/remote/huggingface.d.ts +31 -0
- package/types/lib/ecosystems/remote/huggingface.d.ts.map +1 -0
- package/types/lib/ecosystems/rubyutils.d.ts +182 -0
- package/types/lib/ecosystems/rubyutils.d.ts.map +1 -0
- package/types/lib/ecosystems/rusi.d.ts +40 -0
- package/types/lib/ecosystems/rusi.d.ts.map +1 -0
- package/types/lib/ecosystems/sbtutils.d.ts +179 -0
- package/types/lib/ecosystems/sbtutils.d.ts.map +1 -0
- package/types/lib/ecosystems/tea.d.ts +180 -0
- package/types/lib/ecosystems/tea.d.ts.map +1 -0
- package/types/lib/ecosystems/utils.d.ts +41 -0
- package/types/lib/ecosystems/utils.d.ts.map +1 -0
- package/types/lib/ecosystems/zigResolver.d.ts +70 -0
- package/types/lib/ecosystems/zigResolver.d.ts.map +1 -0
- package/types/lib/evinser/db.d.ts +17 -0
- package/types/lib/evinser/db.d.ts.map +1 -0
- package/types/lib/evinser/evinser.d.ts +354 -0
- package/types/lib/evinser/evinser.d.ts.map +1 -0
- package/types/lib/evinser/scalasem.d.ts +15 -0
- package/types/lib/evinser/scalasem.d.ts.map +1 -0
- package/types/lib/evinser/swiftsem.d.ts +103 -0
- package/types/lib/evinser/swiftsem.d.ts.map +1 -0
- package/types/lib/helpers/annotationFormatter.d.ts +23 -0
- package/types/lib/helpers/annotationFormatter.d.ts.map +1 -0
- package/types/lib/helpers/bomSigner.d.ts +28 -0
- package/types/lib/helpers/bomSigner.d.ts.map +1 -0
- package/types/lib/helpers/exportUtils.d.ts +52 -0
- package/types/lib/helpers/exportUtils.d.ts.map +1 -0
- package/types/lib/helpers/remote/dependency-track.d.ts +23 -0
- package/types/lib/helpers/remote/dependency-track.d.ts.map +1 -0
- package/types/lib/helpers/versutils.d.ts +8 -0
- package/types/lib/helpers/versutils.d.ts.map +1 -0
- package/types/lib/helpers/vsixutils.d.ts +130 -0
- package/types/lib/helpers/vsixutils.d.ts.map +1 -0
- package/types/lib/inventory/agentFormulationParser.d.ts +21 -0
- package/types/lib/inventory/agentFormulationParser.d.ts.map +1 -0
- package/types/lib/inventory/aiCollector.d.ts +61 -0
- package/types/lib/inventory/aiCollector.d.ts.map +1 -0
- package/types/lib/inventory/aiInventory.d.ts +103 -0
- package/types/lib/inventory/aiInventory.d.ts.map +1 -0
- package/types/lib/inventory/aiModelVariants.d.ts +31 -0
- package/types/lib/inventory/aiModelVariants.d.ts.map +1 -0
- package/types/lib/inventory/aiOversightCollector.d.ts +28 -0
- package/types/lib/inventory/aiOversightCollector.d.ts.map +1 -0
- package/types/lib/inventory/aiProvenanceCollector.d.ts +13 -0
- package/types/lib/inventory/aiProvenanceCollector.d.ts.map +1 -0
- package/types/lib/inventory/analyzer.d.ts +157 -0
- package/types/lib/inventory/analyzer.d.ts.map +1 -0
- package/types/lib/inventory/analyzerScope.d.ts +106 -0
- package/types/lib/inventory/analyzerScope.d.ts.map +1 -0
- package/types/lib/inventory/astWorker.d.ts +9 -0
- package/types/lib/inventory/astWorker.d.ts.map +1 -0
- package/types/lib/inventory/atomUtils.d.ts +159 -0
- package/types/lib/inventory/atomUtils.d.ts.map +1 -0
- package/types/lib/inventory/auditCategories.d.ts +80 -0
- package/types/lib/inventory/auditCategories.d.ts.map +1 -0
- package/types/lib/inventory/bomUtils.d.ts +145 -0
- package/types/lib/inventory/bomUtils.d.ts.map +1 -0
- package/types/lib/inventory/cacheCommands.d.ts +28 -0
- package/types/lib/inventory/cacheCommands.d.ts.map +1 -0
- package/types/lib/inventory/cacheDir.d.ts +55 -0
- package/types/lib/inventory/cacheDir.d.ts.map +1 -0
- package/types/lib/inventory/cbomutils.d.ts +37 -0
- package/types/lib/inventory/cbomutils.d.ts.map +1 -0
- package/types/lib/inventory/cdxrs.d.ts +90 -0
- package/types/lib/inventory/cdxrs.d.ts.map +1 -0
- package/types/lib/inventory/ciParsers/azurePipelines.d.ts +27 -0
- package/types/lib/inventory/ciParsers/azurePipelines.d.ts.map +1 -0
- package/types/lib/inventory/ciParsers/circleCi.d.ts +27 -0
- package/types/lib/inventory/ciParsers/circleCi.d.ts.map +1 -0
- package/types/lib/inventory/ciParsers/common.d.ts +11 -0
- package/types/lib/inventory/ciParsers/common.d.ts.map +1 -0
- package/types/lib/inventory/ciParsers/githubActions.d.ts +38 -0
- package/types/lib/inventory/ciParsers/githubActions.d.ts.map +1 -0
- package/types/lib/inventory/ciParsers/gitlabCi.d.ts +26 -0
- package/types/lib/inventory/ciParsers/gitlabCi.d.ts.map +1 -0
- package/types/lib/inventory/ciParsers/jenkins.d.ts +26 -0
- package/types/lib/inventory/ciParsers/jenkins.d.ts.map +1 -0
- package/types/lib/inventory/citations.d.ts +90 -0
- package/types/lib/inventory/citations.d.ts.map +1 -0
- package/types/lib/inventory/communityAiConfigParser.d.ts +14 -0
- package/types/lib/inventory/communityAiConfigParser.d.ts.map +1 -0
- package/types/lib/inventory/containerRisk.d.ts +20 -0
- package/types/lib/inventory/containerRisk.d.ts.map +1 -0
- package/types/lib/inventory/cryptoAlgorithmFamily.d.ts +37 -0
- package/types/lib/inventory/cryptoAlgorithmFamily.d.ts.map +1 -0
- package/types/lib/inventory/deps.d.ts +140 -0
- package/types/lib/inventory/deps.d.ts.map +1 -0
- package/types/lib/inventory/depsUtils.d.ts +74 -0
- package/types/lib/inventory/depsUtils.d.ts.map +1 -0
- package/types/lib/inventory/display.d.ts +227 -0
- package/types/lib/inventory/display.d.ts.map +1 -0
- package/types/lib/inventory/dosai.d.ts +130 -0
- package/types/lib/inventory/dosai.d.ts.map +1 -0
- package/types/lib/inventory/dosaiParsers.d.ts +59 -0
- package/types/lib/inventory/dosaiParsers.d.ts.map +1 -0
- package/types/lib/inventory/dynamic.d.ts +25 -0
- package/types/lib/inventory/dynamic.d.ts.map +1 -0
- package/types/lib/inventory/envcontext.d.ts +377 -0
- package/types/lib/inventory/envcontext.d.ts.map +1 -0
- package/types/lib/inventory/evidenceUtils.d.ts +74 -0
- package/types/lib/inventory/evidenceUtils.d.ts.map +1 -0
- package/types/lib/inventory/fetchBatch.d.ts +127 -0
- package/types/lib/inventory/fetchBatch.d.ts.map +1 -0
- package/types/lib/inventory/fetchRate.d.ts +118 -0
- package/types/lib/inventory/fetchRate.d.ts.map +1 -0
- package/types/lib/inventory/forgeEnricher.d.ts +31 -0
- package/types/lib/inventory/forgeEnricher.d.ts.map +1 -0
- package/types/lib/inventory/formulationParsers.d.ts +50 -0
- package/types/lib/inventory/formulationParsers.d.ts.map +1 -0
- package/types/lib/inventory/gtfobins.d.ts +27 -0
- package/types/lib/inventory/gtfobins.d.ts.map +1 -0
- package/types/lib/inventory/hbom.d.ts +58 -0
- package/types/lib/inventory/hbom.d.ts.map +1 -0
- package/types/lib/inventory/hbomAnalysis.d.ts +56 -0
- package/types/lib/inventory/hbomAnalysis.d.ts.map +1 -0
- package/types/lib/inventory/hbomLoader.d.ts +7 -0
- package/types/lib/inventory/hbomLoader.d.ts.map +1 -0
- package/types/lib/inventory/hostTopology.d.ts +47 -0
- package/types/lib/inventory/hostTopology.d.ts.map +1 -0
- package/types/lib/inventory/inventoryStats.d.ts +42 -0
- package/types/lib/inventory/inventoryStats.d.ts.map +1 -0
- package/types/lib/inventory/jsonLike.d.ts +23 -0
- package/types/lib/inventory/jsonLike.d.ts.map +1 -0
- package/types/lib/inventory/licenseEnhancer.d.ts +70 -0
- package/types/lib/inventory/licenseEnhancer.d.ts.map +1 -0
- package/types/lib/inventory/lolbas.d.ts +16 -0
- package/types/lib/inventory/lolbas.d.ts.map +1 -0
- package/types/lib/inventory/mcp.d.ts +29 -0
- package/types/lib/inventory/mcp.d.ts.map +1 -0
- package/types/lib/inventory/mcpConfigParser.d.ts +20 -0
- package/types/lib/inventory/mcpConfigParser.d.ts.map +1 -0
- package/types/lib/inventory/mcpDiscovery.d.ts +33 -0
- package/types/lib/inventory/mcpDiscovery.d.ts.map +1 -0
- package/types/lib/inventory/mcpPinning.d.ts +11 -0
- package/types/lib/inventory/mcpPinning.d.ts.map +1 -0
- package/types/lib/inventory/osPackageResolver.d.ts +49 -0
- package/types/lib/inventory/osPackageResolver.d.ts.map +1 -0
- package/types/lib/inventory/osinfo.d.ts +201 -0
- package/types/lib/inventory/osinfo.d.ts.map +1 -0
- package/types/lib/inventory/osqueryTransform.d.ts +84 -0
- package/types/lib/inventory/osqueryTransform.d.ts.map +1 -0
- package/types/lib/inventory/plugins.d.ts +58 -0
- package/types/lib/inventory/plugins.d.ts.map +1 -0
- package/types/lib/inventory/protobom.d.ts +71 -0
- package/types/lib/inventory/protobom.d.ts.map +1 -0
- package/types/lib/inventory/protobomLoader.d.ts +17 -0
- package/types/lib/inventory/protobomLoader.d.ts.map +1 -0
- package/types/lib/inventory/provenanceUtils.d.ts +98 -0
- package/types/lib/inventory/provenanceUtils.d.ts.map +1 -0
- package/types/lib/inventory/purl.d.ts +313 -0
- package/types/lib/inventory/purl.d.ts.map +1 -0
- package/types/lib/inventory/rustFormulationParser.d.ts +17 -0
- package/types/lib/inventory/rustFormulationParser.d.ts.map +1 -0
- package/types/lib/inventory/sbomDocument.d.ts +59 -0
- package/types/lib/inventory/sbomDocument.d.ts.map +1 -0
- package/types/lib/inventory/source.d.ts +163 -0
- package/types/lib/inventory/source.d.ts.map +1 -0
- package/types/lib/inventory/spdx.d.ts +66 -0
- package/types/lib/inventory/spdx.d.ts.map +1 -0
- package/types/lib/inventory/spdxExpression.d.ts +58 -0
- package/types/lib/inventory/spdxExpression.d.ts.map +1 -0
- package/types/lib/inventory/spdxUtils.d.ts +12 -0
- package/types/lib/inventory/spdxUtils.d.ts.map +1 -0
- package/types/lib/inventory/table.d.ts +22 -0
- package/types/lib/inventory/table.d.ts.map +1 -0
- package/types/lib/inventory/traceRunner.d.ts +80 -0
- package/types/lib/inventory/traceRunner.d.ts.map +1 -0
- package/types/lib/inventory/unicodeScan.d.ts +46 -0
- package/types/lib/inventory/unicodeScan.d.ts.map +1 -0
- package/types/lib/managers/binary.d.ts +147 -0
- package/types/lib/managers/binary.d.ts.map +1 -0
- package/types/lib/managers/containerutils.d.ts +30 -0
- package/types/lib/managers/containerutils.d.ts.map +1 -0
- package/types/lib/managers/docker.d.ts +157 -0
- package/types/lib/managers/docker.d.ts.map +1 -0
- package/types/lib/managers/dockerConnection.d.ts +42 -0
- package/types/lib/managers/dockerConnection.d.ts.map +1 -0
- package/types/lib/managers/oci.d.ts +21 -0
- package/types/lib/managers/oci.d.ts.map +1 -0
- package/types/lib/managers/piptree.d.ts +13 -0
- package/types/lib/managers/piptree.d.ts.map +1 -0
- package/types/lib/parsers/cmakeCache.d.ts +70 -0
- package/types/lib/parsers/cmakeCache.d.ts.map +1 -0
- package/types/lib/parsers/gguf.d.ts +39 -0
- package/types/lib/parsers/gguf.d.ts.map +1 -0
- package/types/lib/parsers/gitAiNotes.d.ts +15 -0
- package/types/lib/parsers/gitAiNotes.d.ts.map +1 -0
- package/types/lib/parsers/gitmodules.d.ts +88 -0
- package/types/lib/parsers/gitmodules.d.ts.map +1 -0
- package/types/lib/parsers/htmlExtract.d.ts +34 -0
- package/types/lib/parsers/htmlExtract.d.ts.map +1 -0
- package/types/lib/parsers/huggingfaceManifest.d.ts +153 -0
- package/types/lib/parsers/huggingfaceManifest.d.ts.map +1 -0
- package/types/lib/parsers/huggingfaceUtils.d.ts +155 -0
- package/types/lib/parsers/huggingfaceUtils.d.ts.map +1 -0
- package/types/lib/parsers/iri.d.ts +68 -0
- package/types/lib/parsers/iri.d.ts.map +1 -0
- package/types/lib/parsers/npmrc.d.ts +33 -0
- package/types/lib/parsers/npmrc.d.ts.map +1 -0
- package/types/lib/parsers/ollama.d.ts +22 -0
- package/types/lib/parsers/ollama.d.ts.map +1 -0
- package/types/lib/parsers/xml.d.ts +47 -0
- package/types/lib/parsers/xml.d.ts.map +1 -0
- package/types/lib/server/server.d.ts +51 -0
- package/types/lib/server/server.d.ts.map +1 -0
- package/types/lib/stages/postgen/annotator.d.ts +36 -0
- package/types/lib/stages/postgen/annotator.d.ts.map +1 -0
- package/types/lib/stages/postgen/auditBom.d.ts +92 -0
- package/types/lib/stages/postgen/auditBom.d.ts.map +1 -0
- package/types/lib/stages/postgen/postgen.d.ts +59 -0
- package/types/lib/stages/postgen/postgen.d.ts.map +1 -0
- package/types/lib/stages/postgen/ruleEngine.d.ts +18 -0
- package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -0
- package/types/lib/stages/postgen/sortBom.d.ts +34 -0
- package/types/lib/stages/postgen/sortBom.d.ts.map +1 -0
- package/types/lib/stages/postgen/spdxConverter.d.ts +11 -0
- package/types/lib/stages/postgen/spdxConverter.d.ts.map +1 -0
- package/types/lib/stages/postgen/specVersionCompat.d.ts +71 -0
- package/types/lib/stages/postgen/specVersionCompat.d.ts.map +1 -0
- package/types/lib/stages/pregen/envAudit.d.ts +14 -0
- package/types/lib/stages/pregen/envAudit.d.ts.map +1 -0
- package/types/lib/stages/pregen/pregen.d.ts +59 -0
- package/types/lib/stages/pregen/pregen.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/arborist/index.d.ts +47 -0
- package/types/lib/third-party/arborist/lib/arborist/index.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/arborist/load-actual.d.ts +4 -0
- package/types/lib/third-party/arborist/lib/arborist/load-actual.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/arborist/load-virtual.d.ts +4 -0
- package/types/lib/third-party/arborist/lib/arborist/load-virtual.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/bin-links.d.ts +18 -0
- package/types/lib/third-party/arborist/lib/bin-links.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/calc-dep-flags.d.ts +3 -0
- package/types/lib/third-party/arborist/lib/calc-dep-flags.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/case-insensitive-map.d.ts +9 -0
- package/types/lib/third-party/arborist/lib/case-insensitive-map.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/consistent-resolve.d.ts +3 -0
- package/types/lib/third-party/arborist/lib/consistent-resolve.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/debug.d.ts +3 -0
- package/types/lib/third-party/arborist/lib/debug.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/dep-valid.d.ts +3 -0
- package/types/lib/third-party/arborist/lib/dep-valid.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/edge.d.ts +46 -0
- package/types/lib/third-party/arborist/lib/edge.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/from-path.d.ts +3 -0
- package/types/lib/third-party/arborist/lib/from-path.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/gather-dep-set.d.ts +3 -0
- package/types/lib/third-party/arborist/lib/gather-dep-set.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/index.d.ts +3 -0
- package/types/lib/third-party/arborist/lib/index.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/inventory.d.ts +14 -0
- package/types/lib/third-party/arborist/lib/inventory.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/link.d.ts +18 -0
- package/types/lib/third-party/arborist/lib/link.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/node.d.ts +112 -0
- package/types/lib/third-party/arborist/lib/node.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/npm-extension.d.ts +18 -0
- package/types/lib/third-party/arborist/lib/npm-extension.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/override-resolves.d.ts +3 -0
- package/types/lib/third-party/arborist/lib/override-resolves.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/override-set.d.ts +27 -0
- package/types/lib/third-party/arborist/lib/override-set.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/package-extensions.d.ts +36 -0
- package/types/lib/third-party/arborist/lib/package-extensions.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/printable.d.ts +3 -0
- package/types/lib/third-party/arborist/lib/printable.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/proc-log.d.ts +20 -0
- package/types/lib/third-party/arborist/lib/proc-log.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/realpath.d.ts +3 -0
- package/types/lib/third-party/arborist/lib/realpath.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/relpath.d.ts +3 -0
- package/types/lib/third-party/arborist/lib/relpath.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/shrinkwrap.d.ts +54 -0
- package/types/lib/third-party/arborist/lib/shrinkwrap.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/spec-from-lock.d.ts +3 -0
- package/types/lib/third-party/arborist/lib/spec-from-lock.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/tree-check.d.ts +3 -0
- package/types/lib/third-party/arborist/lib/tree-check.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/validate-npm-package-name.d.ts +13 -0
- package/types/lib/third-party/arborist/lib/validate-npm-package-name.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/version-from-tgz.d.ts +6 -0
- package/types/lib/third-party/arborist/lib/version-from-tgz.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/yarn-lock.d.ts +35 -0
- package/types/lib/third-party/arborist/lib/yarn-lock.d.ts.map +1 -0
- package/types/lib/validator/bomValidator.d.ts +40 -0
- package/types/lib/validator/bomValidator.d.ts.map +1 -0
- package/types/lib/validator/complianceEngine.d.ts +72 -0
- package/types/lib/validator/complianceEngine.d.ts.map +1 -0
- package/types/lib/validator/complianceRules.d.ts +61 -0
- package/types/lib/validator/complianceRules.d.ts.map +1 -0
- package/types/lib/validator/index.d.ts +101 -0
- package/types/lib/validator/index.d.ts.map +1 -0
- package/types/lib/validator/reporters/annotations.d.ts +37 -0
- package/types/lib/validator/reporters/annotations.d.ts.map +1 -0
- package/types/lib/validator/reporters/console.d.ts +34 -0
- package/types/lib/validator/reporters/console.d.ts.map +1 -0
- package/types/lib/validator/reporters/index.d.ts +30 -0
- package/types/lib/validator/reporters/index.d.ts.map +1 -0
- package/types/lib/validator/reporters/json.d.ts +11 -0
- package/types/lib/validator/reporters/json.d.ts.map +1 -0
- package/types/lib/validator/reporters/sarif.d.ts +22 -0
- package/types/lib/validator/reporters/sarif.d.ts.map +1 -0
- package/mod.d.ts +0 -7
- package/mod.js +0 -22
|
@@ -0,0 +1,2772 @@
|
|
|
1
|
+
import { Buffer } from "node:buffer";
|
|
2
|
+
import {
|
|
3
|
+
constants,
|
|
4
|
+
existsSync,
|
|
5
|
+
lstatSync,
|
|
6
|
+
readdirSync,
|
|
7
|
+
readFileSync,
|
|
8
|
+
statSync,
|
|
9
|
+
} from "node:fs";
|
|
10
|
+
import { homedir } from "node:os";
|
|
11
|
+
import {
|
|
12
|
+
delimiter as _delimiter,
|
|
13
|
+
sep as _sep,
|
|
14
|
+
basename,
|
|
15
|
+
dirname,
|
|
16
|
+
join,
|
|
17
|
+
relative,
|
|
18
|
+
} from "node:path";
|
|
19
|
+
|
|
20
|
+
import { build } from "@cdxgen/cdx-purl";
|
|
21
|
+
import { globSync } from "glob";
|
|
22
|
+
import StreamZip from "node-stream-zip";
|
|
23
|
+
import {
|
|
24
|
+
clean,
|
|
25
|
+
coerce,
|
|
26
|
+
compare,
|
|
27
|
+
maxSatisfying,
|
|
28
|
+
parse,
|
|
29
|
+
satisfies,
|
|
30
|
+
} from "semver";
|
|
31
|
+
|
|
32
|
+
import {
|
|
33
|
+
cdxgenAgent,
|
|
34
|
+
DEBUG_MODE,
|
|
35
|
+
readEnvironmentVariable,
|
|
36
|
+
} from "../core/activity.js";
|
|
37
|
+
import {
|
|
38
|
+
SEARCH_MAVEN_ORG,
|
|
39
|
+
shouldFetchLicense,
|
|
40
|
+
shouldFetchPackageMetadata,
|
|
41
|
+
shouldFetchVCS,
|
|
42
|
+
} from "../core/env.js";
|
|
43
|
+
import {
|
|
44
|
+
checksumFile,
|
|
45
|
+
getAllFiles,
|
|
46
|
+
multiChecksumFile,
|
|
47
|
+
safeCopyFileSync,
|
|
48
|
+
safeExistsSync,
|
|
49
|
+
safeExtractArchive,
|
|
50
|
+
safeRmSync,
|
|
51
|
+
} from "../core/fs.js";
|
|
52
|
+
import { thoughtLog } from "../core/logger.js";
|
|
53
|
+
import { mapWithConcurrency } from "../core/parallel.js";
|
|
54
|
+
import { isWin } from "../core/paths.js";
|
|
55
|
+
import { vendorAliases } from "../core/state.js";
|
|
56
|
+
import { isGitHubUrl, modulePathHost } from "../core/urls.js";
|
|
57
|
+
import {
|
|
58
|
+
collectJarNS,
|
|
59
|
+
getPomPropertiesFromMavenDir,
|
|
60
|
+
inferJarGroupFromManifest,
|
|
61
|
+
parseJarManifest,
|
|
62
|
+
parsePomXml,
|
|
63
|
+
trimJarGroupSuffix,
|
|
64
|
+
} from "../inventory/deps.js";
|
|
65
|
+
import {
|
|
66
|
+
prefetchEnabled,
|
|
67
|
+
prefetchedResponse,
|
|
68
|
+
prefetchJson,
|
|
69
|
+
} from "../inventory/fetchBatch.js";
|
|
70
|
+
import {
|
|
71
|
+
applyPurl,
|
|
72
|
+
encodeForPurl,
|
|
73
|
+
genericPurl,
|
|
74
|
+
pypiPurl,
|
|
75
|
+
} from "../inventory/purl.js";
|
|
76
|
+
import {
|
|
77
|
+
findLicenseId,
|
|
78
|
+
guessLicenseId,
|
|
79
|
+
spdxLicenses,
|
|
80
|
+
} from "../inventory/spdx.js";
|
|
81
|
+
import { extractLicenseText, extractRepoUrl } from "../parsers/htmlExtract.js";
|
|
82
|
+
import { xml2js } from "../parsers/xml.js";
|
|
83
|
+
import {
|
|
84
|
+
collectCargoRegistryProvenanceProperties,
|
|
85
|
+
collectNpmRegistryProvenanceProperties,
|
|
86
|
+
collectPypiRegistryProvenanceProperties,
|
|
87
|
+
} from "./registryProvenance.js";
|
|
88
|
+
|
|
89
|
+
// Metadata cache
|
|
90
|
+
export let metadata_cache = {};
|
|
91
|
+
|
|
92
|
+
// Speed up lookup namespaces for a given jar
|
|
93
|
+
|
|
94
|
+
// circuit breaker for search maven.org
|
|
95
|
+
let search_maven_org_errors = 0;
|
|
96
|
+
const MAX_SEARCH_MAVEN_ORG_ERRORS = 1;
|
|
97
|
+
|
|
98
|
+
// circuit breaker for get repo license
|
|
99
|
+
let get_repo_license_errors = 0;
|
|
100
|
+
const MAX_GET_REPO_LICENSE_ERRORS = 5;
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Internal helper to reset metadata_cache. Used by parseGoModData (still in
|
|
104
|
+
* utils.js until batch 6) because ESM forbids reassigning an imported binding.
|
|
105
|
+
* NOT re-exported through the utils.js barrel.
|
|
106
|
+
*/
|
|
107
|
+
export function _clearMetadataCache() {
|
|
108
|
+
metadata_cache = {};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Fetches license information for a list of Swift packages by querying the
|
|
113
|
+
* GitHub repository license API for packages hosted on github.com.
|
|
114
|
+
*
|
|
115
|
+
* @param {Object[]} pkgList List of Swift package objects with optional repository.url fields
|
|
116
|
+
* @returns {Promise<Object[]>} Resolved list of package objects, each augmented with a license field where available
|
|
117
|
+
*/
|
|
118
|
+
export async function getSwiftPackageMetadata(pkgList) {
|
|
119
|
+
const cdepList = [];
|
|
120
|
+
// Swift resolves licences purely through repository lookups, so its whole
|
|
121
|
+
// network cost is one batched round.
|
|
122
|
+
await prefetchRepoLicenses(
|
|
123
|
+
pkgList
|
|
124
|
+
.filter((p) => isGitHubUrl(p.repository?.url))
|
|
125
|
+
.map((p) => p.repository.url),
|
|
126
|
+
);
|
|
127
|
+
for (const p of pkgList) {
|
|
128
|
+
if (p.repository?.url) {
|
|
129
|
+
if (isGitHubUrl(p.repository.url)) {
|
|
130
|
+
try {
|
|
131
|
+
p.license = await getRepoLicense(p.repository.url, undefined);
|
|
132
|
+
} catch (_e) {
|
|
133
|
+
console.error("error fetching repo license from", p.repository.url);
|
|
134
|
+
}
|
|
135
|
+
} else {
|
|
136
|
+
if (DEBUG_MODE) {
|
|
137
|
+
console.log(
|
|
138
|
+
p.repository.url,
|
|
139
|
+
"is currently not supported to fetch for licenses",
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
} else {
|
|
144
|
+
if (DEBUG_MODE) {
|
|
145
|
+
console.warn("no repository url found for", p.name);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
cdepList.push(p);
|
|
149
|
+
}
|
|
150
|
+
return cdepList;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** Accept header pub.dev requires for its v2 API. */
|
|
154
|
+
const PUB_ACCEPT = "application/vnd.pub.v2+json";
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* The registry path segment for a package: the name, scoped by its group when
|
|
158
|
+
* it has one.
|
|
159
|
+
*
|
|
160
|
+
* Extracted so the batch prefetch and the request inside the loop cannot drift
|
|
161
|
+
* apart — a prefetch that computed the URL even slightly differently would
|
|
162
|
+
* silently fetch documents nobody reads and leave the loop making its own
|
|
163
|
+
* serial requests.
|
|
164
|
+
*
|
|
165
|
+
* @param {Object} p Package object with `name` and optional `group`.
|
|
166
|
+
* @returns {string} Registry key, e.g. `@babel/core` or `left-pad`.
|
|
167
|
+
*/
|
|
168
|
+
function npmRegistryKey(p) {
|
|
169
|
+
if (p.group && p.group !== "") {
|
|
170
|
+
const group = p.group.startsWith("@") ? p.group : `@${p.group}`;
|
|
171
|
+
return `${group}/${p.name}`;
|
|
172
|
+
}
|
|
173
|
+
return p.name;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Method to retrieve metadata for npm packages by querying npmjs
|
|
178
|
+
*
|
|
179
|
+
* @param {Array} pkgList Package list
|
|
180
|
+
*/
|
|
181
|
+
export async function getNpmMetadata(pkgList, registryUrl) {
|
|
182
|
+
const NPM_URL =
|
|
183
|
+
registryUrl ||
|
|
184
|
+
readEnvironmentVariable("NPM_URL") ||
|
|
185
|
+
"https://registry.npmjs.org/";
|
|
186
|
+
const cdepList = [];
|
|
187
|
+
// Every URL this loop needs is known up front, so they are fetched
|
|
188
|
+
// concurrently before the loop rather than one at a time inside it. The
|
|
189
|
+
// derivation below is unchanged and still reads `body`; only where `body`
|
|
190
|
+
// comes from differs.
|
|
191
|
+
const prefetched = await prefetchJson(
|
|
192
|
+
prefetchEnabled()
|
|
193
|
+
? pkgList.map((p) => ({ url: NPM_URL + npmRegistryKey(p) }))
|
|
194
|
+
: [],
|
|
195
|
+
);
|
|
196
|
+
// The licence fallback calls getRepoLicense with a URL that only exists once
|
|
197
|
+
// the registry document has arrived, so it needs a second batched round. The
|
|
198
|
+
// documents are already in hand here, which is why this can be done without a
|
|
199
|
+
// second pass over the network for the registry itself.
|
|
200
|
+
if (prefetched.size) {
|
|
201
|
+
const repoUrls = [];
|
|
202
|
+
for (const p of pkgList) {
|
|
203
|
+
const entry = prefetched.get(NPM_URL + npmRegistryKey(p));
|
|
204
|
+
if (!entry?.ok) {
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
const body = entry.body;
|
|
208
|
+
const versionLicense = body?.versions?.[p.version]?.license;
|
|
209
|
+
if (!versionLicense && !body?.license && body?.repository?.url) {
|
|
210
|
+
repoUrls.push(body.repository.url);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
await prefetchRepoLicenses(repoUrls);
|
|
214
|
+
}
|
|
215
|
+
for (const p of pkgList) {
|
|
216
|
+
try {
|
|
217
|
+
const key = npmRegistryKey(p);
|
|
218
|
+
// Namespace the cache by registry so packages resolved from a
|
|
219
|
+
// non-default registry (e.g. jsr's npm mirror at npm.jsr.io) do not
|
|
220
|
+
// collide with identically-named packages on the default registry.
|
|
221
|
+
const cacheKey = `${NPM_URL}${key}`;
|
|
222
|
+
let body = {};
|
|
223
|
+
if (metadata_cache[cacheKey]) {
|
|
224
|
+
body = metadata_cache[cacheKey];
|
|
225
|
+
} else {
|
|
226
|
+
const url = NPM_URL + key;
|
|
227
|
+
const res =
|
|
228
|
+
prefetchedResponse(prefetched, url) ||
|
|
229
|
+
(await cdxgenAgent.get(url, {
|
|
230
|
+
responseType: "json",
|
|
231
|
+
}));
|
|
232
|
+
body = res.body;
|
|
233
|
+
metadata_cache[cacheKey] = body;
|
|
234
|
+
}
|
|
235
|
+
p.description =
|
|
236
|
+
body.versions?.[p.version]?.description || body.description;
|
|
237
|
+
p.license =
|
|
238
|
+
body.versions?.[p.version]?.license ||
|
|
239
|
+
body.license ||
|
|
240
|
+
(await getRepoLicense(body.repository?.url, undefined));
|
|
241
|
+
if (body.repository?.url) {
|
|
242
|
+
p.repository = { url: body.repository.url };
|
|
243
|
+
}
|
|
244
|
+
if (body.homepage) {
|
|
245
|
+
p.homepage = { url: body.homepage };
|
|
246
|
+
}
|
|
247
|
+
// Capture the resolved tarball as a distribution external reference when
|
|
248
|
+
// the component does not already carry one. This is the reliable source
|
|
249
|
+
// of a download URL for registries (such as jsr's npm mirror) whose
|
|
250
|
+
// tarball path is not deterministically constructible.
|
|
251
|
+
const distTarball = body.versions?.[p.version]?.dist?.tarball;
|
|
252
|
+
if (distTarball) {
|
|
253
|
+
p.externalReferences = p.externalReferences || [];
|
|
254
|
+
if (
|
|
255
|
+
!p.externalReferences.some(
|
|
256
|
+
(ref) => ref.type === "distribution" && ref.url === distTarball,
|
|
257
|
+
)
|
|
258
|
+
) {
|
|
259
|
+
p.externalReferences.push({
|
|
260
|
+
type: "distribution",
|
|
261
|
+
url: distTarball,
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
p.properties = p.properties || [];
|
|
266
|
+
p.properties.push(
|
|
267
|
+
...collectNpmRegistryProvenanceProperties(body, p.version),
|
|
268
|
+
);
|
|
269
|
+
cdepList.push(p);
|
|
270
|
+
} catch (_err) {
|
|
271
|
+
cdepList.push(p);
|
|
272
|
+
if (DEBUG_MODE) {
|
|
273
|
+
console.error(p, "was not found on npm");
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return cdepList;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Method to locate local Gradle, Maven, or Coursier cache files for a given maven coordinate.
|
|
282
|
+
*
|
|
283
|
+
* @param {string} group Maven groupId
|
|
284
|
+
* @param {string} name Maven artifactId
|
|
285
|
+
* @param {string} version Package version
|
|
286
|
+
* @returns {Object|null} Object containing jarPath, sha1, and pomPath, or null
|
|
287
|
+
*/
|
|
288
|
+
export function findLocalMvnArtifact(group, name, version) {
|
|
289
|
+
if (!group || !name || !version) {
|
|
290
|
+
return null;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// 1. Gradle cache
|
|
294
|
+
const gradleUserHome = readEnvironmentVariable("GRADLE_USER_HOME");
|
|
295
|
+
const gradleCacheDir =
|
|
296
|
+
readEnvironmentVariable("GRADLE_CACHE_DIR") ||
|
|
297
|
+
(gradleUserHome
|
|
298
|
+
? join(gradleUserHome, "caches", "modules-2", "files-2.1")
|
|
299
|
+
: join(homedir(), ".gradle", "caches", "modules-2", "files-2.1"));
|
|
300
|
+
|
|
301
|
+
if (existsSync(gradleCacheDir)) {
|
|
302
|
+
const gradlePath = join(gradleCacheDir, group, name, version);
|
|
303
|
+
if (existsSync(gradlePath)) {
|
|
304
|
+
try {
|
|
305
|
+
const subdirs = readdirSync(gradlePath);
|
|
306
|
+
let jarPath = null;
|
|
307
|
+
let sha1 = null;
|
|
308
|
+
let pomPath = null;
|
|
309
|
+
for (const subdir of subdirs) {
|
|
310
|
+
const subdirPath = join(gradlePath, subdir);
|
|
311
|
+
if (statSync(subdirPath).isDirectory()) {
|
|
312
|
+
const files = readdirSync(subdirPath);
|
|
313
|
+
for (const file of files) {
|
|
314
|
+
if (
|
|
315
|
+
file.endsWith(".jar") &&
|
|
316
|
+
!file.endsWith("-sources.jar") &&
|
|
317
|
+
!file.endsWith("-javadoc.jar")
|
|
318
|
+
) {
|
|
319
|
+
jarPath = join(subdirPath, file);
|
|
320
|
+
sha1 = subdir;
|
|
321
|
+
} else if (file.endsWith(".pom")) {
|
|
322
|
+
pomPath = join(subdirPath, file);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
if (jarPath || pomPath) {
|
|
328
|
+
return { jarPath, sha1, pomPath };
|
|
329
|
+
}
|
|
330
|
+
} catch (_err) {
|
|
331
|
+
// ignore
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// 2. Maven local repo
|
|
337
|
+
const mavenLocal = join(homedir(), ".m2", "repository");
|
|
338
|
+
const groupPath = group.replace(/\./g, "/");
|
|
339
|
+
const mavenPath = join(mavenLocal, groupPath, name, version);
|
|
340
|
+
if (existsSync(mavenPath)) {
|
|
341
|
+
const jarPath = join(mavenPath, `${name}-${version}.jar`);
|
|
342
|
+
const pomPath = join(mavenPath, `${name}-${version}.pom`);
|
|
343
|
+
let hasFiles = false;
|
|
344
|
+
const result = {};
|
|
345
|
+
if (existsSync(jarPath)) {
|
|
346
|
+
result.jarPath = jarPath;
|
|
347
|
+
hasFiles = true;
|
|
348
|
+
}
|
|
349
|
+
if (existsSync(pomPath)) {
|
|
350
|
+
result.pomPath = pomPath;
|
|
351
|
+
hasFiles = true;
|
|
352
|
+
}
|
|
353
|
+
if (hasFiles) {
|
|
354
|
+
return result;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// 3. Coursier cache (SBT)
|
|
359
|
+
let cacheRoot = readEnvironmentVariable("COURSIER_CACHE");
|
|
360
|
+
if (!cacheRoot) {
|
|
361
|
+
if (process.platform === "darwin") {
|
|
362
|
+
cacheRoot = join(homedir(), "Library", "Caches", "Coursier", "v1");
|
|
363
|
+
} else if (process.platform === "win32") {
|
|
364
|
+
const localAppData = readEnvironmentVariable("LOCALAPPDATA");
|
|
365
|
+
if (localAppData) {
|
|
366
|
+
cacheRoot = join(localAppData, "Coursier", "Cache", "v1");
|
|
367
|
+
} else {
|
|
368
|
+
cacheRoot = join(
|
|
369
|
+
homedir(),
|
|
370
|
+
"AppData",
|
|
371
|
+
"Local",
|
|
372
|
+
"Coursier",
|
|
373
|
+
"Cache",
|
|
374
|
+
"v1",
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
} else {
|
|
378
|
+
cacheRoot = join(homedir(), ".cache", "coursier", "v1");
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
if (existsSync(cacheRoot)) {
|
|
383
|
+
const pattern = `**/${groupPath}/${name}/${version}/*`;
|
|
384
|
+
try {
|
|
385
|
+
const matches = globSync(pattern, { cwd: cacheRoot });
|
|
386
|
+
if (matches && matches.length > 0) {
|
|
387
|
+
let jarPath = null;
|
|
388
|
+
let pomPath = null;
|
|
389
|
+
for (const m of matches) {
|
|
390
|
+
const match = m.replace(/\\/g, "/");
|
|
391
|
+
if (
|
|
392
|
+
match.endsWith(".jar") &&
|
|
393
|
+
!match.endsWith("-sources.jar") &&
|
|
394
|
+
!match.endsWith("-javadoc.jar")
|
|
395
|
+
) {
|
|
396
|
+
const localPath = join(cacheRoot, match);
|
|
397
|
+
if (existsSync(localPath)) {
|
|
398
|
+
jarPath = localPath;
|
|
399
|
+
}
|
|
400
|
+
} else if (match.endsWith(".pom")) {
|
|
401
|
+
const localPath = join(cacheRoot, match);
|
|
402
|
+
if (existsSync(localPath)) {
|
|
403
|
+
pomPath = localPath;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
if (jarPath || pomPath) {
|
|
408
|
+
return { jarPath, pomPath };
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
} catch (_err) {
|
|
412
|
+
// ignore
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
return null;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* Method to retrieve metadata for maven packages by querying maven central
|
|
421
|
+
*
|
|
422
|
+
* @param {Array} pkgList Package list
|
|
423
|
+
* @param {Object} jarNSMapping Jar Namespace mapping object
|
|
424
|
+
* @param {Boolean} force Force fetching of license
|
|
425
|
+
*
|
|
426
|
+
* @returns {Array} Updated package list
|
|
427
|
+
*/
|
|
428
|
+
export async function getMvnMetadata(
|
|
429
|
+
pkgList,
|
|
430
|
+
jarNSMapping = {},
|
|
431
|
+
force = false,
|
|
432
|
+
) {
|
|
433
|
+
const MAVEN_CENTRAL_URL =
|
|
434
|
+
readEnvironmentVariable("MAVEN_CENTRAL_URL") ||
|
|
435
|
+
"https://repo1.maven.org/maven2/";
|
|
436
|
+
const ANDROID_MAVEN_URL =
|
|
437
|
+
readEnvironmentVariable("ANDROID_MAVEN_URL") || "https://maven.google.com/";
|
|
438
|
+
const cdepList = [];
|
|
439
|
+
if (!pkgList?.length) {
|
|
440
|
+
return pkgList;
|
|
441
|
+
}
|
|
442
|
+
if (DEBUG_MODE && shouldFetchLicense()) {
|
|
443
|
+
console.log(`About to query maven for ${pkgList.length} packages`);
|
|
444
|
+
}
|
|
445
|
+
// Collect the direct POM URLs the loop is going to need and fetch them in
|
|
446
|
+
// one batched round. Only the direct POMs are prefetched: parent POMs (walked
|
|
447
|
+
// inside fetchPomXmlAsJson) depend on the direct POM's content and stay
|
|
448
|
+
// serial. The conditions below mirror the loop's skip checks so the batch
|
|
449
|
+
// does not request anything the loop would skip.
|
|
450
|
+
const batchUrls = [];
|
|
451
|
+
const seenPomUrls = new Set();
|
|
452
|
+
for (const p of pkgList) {
|
|
453
|
+
const group = p.group || "";
|
|
454
|
+
if (
|
|
455
|
+
!p.version ||
|
|
456
|
+
(group && p.name && p.version && !shouldFetchLicense() && !force) ||
|
|
457
|
+
(group && p.name && p.version && p.license)
|
|
458
|
+
) {
|
|
459
|
+
continue;
|
|
460
|
+
}
|
|
461
|
+
if (!group || group === "") {
|
|
462
|
+
continue;
|
|
463
|
+
}
|
|
464
|
+
const urlPrefix =
|
|
465
|
+
group.indexOf("android") !== -1 ? ANDROID_MAVEN_URL : MAVEN_CENTRAL_URL;
|
|
466
|
+
const pomUrl = composePomXmlUrl({
|
|
467
|
+
urlPrefix,
|
|
468
|
+
group,
|
|
469
|
+
name: p.name,
|
|
470
|
+
version: p.version,
|
|
471
|
+
});
|
|
472
|
+
if (!seenPomUrls.has(pomUrl)) {
|
|
473
|
+
seenPomUrls.add(pomUrl);
|
|
474
|
+
batchUrls.push({ url: pomUrl, responseType: "text" });
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
const ownPomUrls = [];
|
|
478
|
+
if (batchUrls.length) {
|
|
479
|
+
const fetched = await prefetchJson(batchUrls);
|
|
480
|
+
const repoUrls = [];
|
|
481
|
+
for (const [key, value] of fetched) {
|
|
482
|
+
prefetchedPoms.set(key, value);
|
|
483
|
+
ownPomUrls.push(key);
|
|
484
|
+
const scmUrl = pomScmUrlNeedingLicenseLookup(value);
|
|
485
|
+
if (scmUrl) {
|
|
486
|
+
repoUrls.push(scmUrl);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
// The POMs are in hand, so the licence fallback's repository URLs are known
|
|
490
|
+
// without a second network round for the POMs themselves — the same second
|
|
491
|
+
// round getNpmMetadata does. Only a POM that declares no licence and no
|
|
492
|
+
// licence comment reaches getRepoLicense, and only its own scm element is
|
|
493
|
+
// read here, so this never fetches a licence the serial path would not.
|
|
494
|
+
await prefetchRepoLicenses(repoUrls);
|
|
495
|
+
}
|
|
496
|
+
try {
|
|
497
|
+
return await enrichMvnPackages(pkgList, cdepList, jarNSMapping, force, {
|
|
498
|
+
MAVEN_CENTRAL_URL,
|
|
499
|
+
ANDROID_MAVEN_URL,
|
|
500
|
+
});
|
|
501
|
+
} finally {
|
|
502
|
+
// Each body is read once, by the loop below. Holding them past that only
|
|
503
|
+
// grows the process, which matters in server mode where one process serves
|
|
504
|
+
// many scans. Only the documents this call fetched are dropped, so a scan
|
|
505
|
+
// running alongside this one keeps its own.
|
|
506
|
+
for (const pomUrl of ownPomUrls) {
|
|
507
|
+
prefetchedPoms.delete(pomUrl);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* Enrich Maven packages from their POMs, reading any document already fetched
|
|
514
|
+
* by {@link getMvnMetadata}'s batch round.
|
|
515
|
+
*
|
|
516
|
+
* @param {Array} pkgList Packages to enrich.
|
|
517
|
+
* @param {Array} cdepList Accumulator for the enriched packages.
|
|
518
|
+
* @param {Object} jarNSMapping Namespace and hash data from jar analysis.
|
|
519
|
+
* @param {Boolean} force Whether to query even when metadata is present.
|
|
520
|
+
* @param {{MAVEN_CENTRAL_URL: string, ANDROID_MAVEN_URL: string}} repos Repository
|
|
521
|
+
* base URLs resolved by the caller from the environment.
|
|
522
|
+
* @returns {Promise<Array>} The enriched package list.
|
|
523
|
+
*/
|
|
524
|
+
async function enrichMvnPackages(
|
|
525
|
+
pkgList,
|
|
526
|
+
cdepList,
|
|
527
|
+
jarNSMapping,
|
|
528
|
+
force,
|
|
529
|
+
{ MAVEN_CENTRAL_URL, ANDROID_MAVEN_URL },
|
|
530
|
+
) {
|
|
531
|
+
for (const p of pkgList) {
|
|
532
|
+
// Reuse any namespace and hashes data from jarNSMapping
|
|
533
|
+
if (jarNSMapping && p.purl && jarNSMapping[p.purl]) {
|
|
534
|
+
if (jarNSMapping[p.purl].jarFile) {
|
|
535
|
+
p.evidence = {
|
|
536
|
+
identity: {
|
|
537
|
+
field: "purl",
|
|
538
|
+
confidence: 0.8,
|
|
539
|
+
methods: [
|
|
540
|
+
{
|
|
541
|
+
technique: "binary-analysis",
|
|
542
|
+
confidence: 0.8,
|
|
543
|
+
value: jarNSMapping[p.purl].jarFile,
|
|
544
|
+
},
|
|
545
|
+
],
|
|
546
|
+
},
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
if (jarNSMapping[p.purl].hashes && !p?.hashes?.length) {
|
|
550
|
+
p.hashes = jarNSMapping[p.purl].hashes;
|
|
551
|
+
}
|
|
552
|
+
if (
|
|
553
|
+
jarNSMapping[p.purl].namespaces &&
|
|
554
|
+
jarNSMapping[p.purl].namespaces.length
|
|
555
|
+
) {
|
|
556
|
+
if (!p.properties) {
|
|
557
|
+
p.properties = [];
|
|
558
|
+
}
|
|
559
|
+
p.properties.push({
|
|
560
|
+
name: "internal:Namespaces",
|
|
561
|
+
value: jarNSMapping[p.purl].namespaces.join("\n"),
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
const localArtifact = findLocalMvnArtifact(p.group, p.name, p.version);
|
|
566
|
+
if (localArtifact) {
|
|
567
|
+
if (localArtifact.jarPath && (!p.hashes || p.hashes.length === 0)) {
|
|
568
|
+
try {
|
|
569
|
+
const hashValues = await multiChecksumFile(
|
|
570
|
+
["md5", "sha1", "sha256", "sha512"],
|
|
571
|
+
localArtifact.jarPath,
|
|
572
|
+
);
|
|
573
|
+
p.hashes = [
|
|
574
|
+
{ alg: "MD5", content: hashValues["md5"] },
|
|
575
|
+
{ alg: "SHA-1", content: hashValues["sha1"] },
|
|
576
|
+
{ alg: "SHA-256", content: hashValues["sha256"] },
|
|
577
|
+
{ alg: "SHA-512", content: hashValues["sha512"] },
|
|
578
|
+
];
|
|
579
|
+
} catch (_err) {
|
|
580
|
+
if (localArtifact.sha1) {
|
|
581
|
+
p.hashes = [{ alg: "SHA-1", content: localArtifact.sha1 }];
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
if (localArtifact.pomPath) {
|
|
586
|
+
try {
|
|
587
|
+
const xmlData = readFileSync(localArtifact.pomPath, "utf-8");
|
|
588
|
+
const pomData = parsePomXml(xmlData);
|
|
589
|
+
if (pomData) {
|
|
590
|
+
if (!p.publisher && pomData.organization?.name?._) {
|
|
591
|
+
p.publisher = pomData.organization.name._;
|
|
592
|
+
}
|
|
593
|
+
if (!p.description && pomData.description) {
|
|
594
|
+
p.description = pomData.description
|
|
595
|
+
.replace(/[ \t]+/g, " ")
|
|
596
|
+
.replace(/^[ \t]+|[ \t]+$/gm, "")
|
|
597
|
+
.replace(/\n\s*\n/g, "\n")
|
|
598
|
+
.trim();
|
|
599
|
+
}
|
|
600
|
+
if (!p.repository && pomData.scm) {
|
|
601
|
+
p.repository = { url: pomData.scm };
|
|
602
|
+
}
|
|
603
|
+
if (!p.license) {
|
|
604
|
+
const parsedLicense = parseLicenseEntryOrArrayFromPomXml(
|
|
605
|
+
pomData.licenses,
|
|
606
|
+
);
|
|
607
|
+
if (parsedLicense) {
|
|
608
|
+
p.license = parsedLicense;
|
|
609
|
+
} else {
|
|
610
|
+
const licenseRegex = /<!--([\s\S]*?)-->[\s\n]*<project/m;
|
|
611
|
+
const match = licenseRegex.exec(xmlData);
|
|
612
|
+
if (match?.[1]) {
|
|
613
|
+
const commentLicense = findLicenseId(match[1].trim());
|
|
614
|
+
if (commentLicense) {
|
|
615
|
+
p.license = commentLicense;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
} catch (_err) {
|
|
622
|
+
// ignore
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
const group = p.group || "";
|
|
627
|
+
// If the package already has key metadata skip querying maven
|
|
628
|
+
if (
|
|
629
|
+
!p.version ||
|
|
630
|
+
(group && p.name && p.version && !shouldFetchLicense() && !force) ||
|
|
631
|
+
(group && p.name && p.version && p.license)
|
|
632
|
+
) {
|
|
633
|
+
cdepList.push(p);
|
|
634
|
+
continue;
|
|
635
|
+
}
|
|
636
|
+
let urlPrefix = MAVEN_CENTRAL_URL;
|
|
637
|
+
// Ideally we should try one resolver after the other. But it increases the time taken
|
|
638
|
+
if (group.indexOf("android") !== -1) {
|
|
639
|
+
urlPrefix = ANDROID_MAVEN_URL;
|
|
640
|
+
}
|
|
641
|
+
// Querying maven requires a valid group name
|
|
642
|
+
if (!group || group === "") {
|
|
643
|
+
cdepList.push(p);
|
|
644
|
+
continue;
|
|
645
|
+
}
|
|
646
|
+
const pomMetadata = {
|
|
647
|
+
urlPrefix: urlPrefix,
|
|
648
|
+
group: group,
|
|
649
|
+
name: p.name,
|
|
650
|
+
version: p.version,
|
|
651
|
+
};
|
|
652
|
+
try {
|
|
653
|
+
if (DEBUG_MODE) {
|
|
654
|
+
console.log(
|
|
655
|
+
`Querying ${pomMetadata.urlPrefix} for '${group}/${p.name}@${p.version}' ${composePomXmlUrl(
|
|
656
|
+
pomMetadata,
|
|
657
|
+
)}`,
|
|
658
|
+
);
|
|
659
|
+
}
|
|
660
|
+
const bodyJson = await fetchPomXmlAsJson(pomMetadata);
|
|
661
|
+
if (bodyJson) {
|
|
662
|
+
p.publisher = bodyJson?.organization?.name
|
|
663
|
+
? bodyJson?.organization.name._
|
|
664
|
+
: "";
|
|
665
|
+
p.description = bodyJson?.description
|
|
666
|
+
? bodyJson.description._.replace(/[ \t]+/g, " ")
|
|
667
|
+
.replace(/^[ \t]+|[ \t]+$/gm, "")
|
|
668
|
+
.replace(/\n\s*\n/g, "\n")
|
|
669
|
+
.trim()
|
|
670
|
+
: "";
|
|
671
|
+
if (bodyJson?.scm?.url) {
|
|
672
|
+
p.repository = { url: bodyJson.scm.url._ };
|
|
673
|
+
}
|
|
674
|
+
p.license =
|
|
675
|
+
parseLicenseEntryOrArrayFromPomXml(bodyJson?.licenses?.license) ||
|
|
676
|
+
(await extractLicenseCommentFromPomXml(pomMetadata)) ||
|
|
677
|
+
(await getRepoLicense(p.repository?.url, undefined));
|
|
678
|
+
}
|
|
679
|
+
} catch (err) {
|
|
680
|
+
if (DEBUG_MODE) {
|
|
681
|
+
console.log(
|
|
682
|
+
`An error occurred when trying to fetch metadata ${pomMetadata}`,
|
|
683
|
+
err,
|
|
684
|
+
);
|
|
685
|
+
}
|
|
686
|
+
} finally {
|
|
687
|
+
cdepList.push(p);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
return cdepList;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
/**
|
|
694
|
+
* The repository URL a prefetched POM will need a GitHub licence lookup for.
|
|
695
|
+
*
|
|
696
|
+
* Mirrors the order `enrichMvnPackages` resolves a licence in: a `<licenses>`
|
|
697
|
+
* element wins, then a licence comment, and only a POM offering neither falls
|
|
698
|
+
* through to `getRepoLicense`. A POM whose `scm` element is inherited from a
|
|
699
|
+
* parent is not covered, since the parent document is not prefetched.
|
|
700
|
+
*
|
|
701
|
+
* @param {{ok?: boolean, body?: String}} entry A prefetched POM batch entry.
|
|
702
|
+
* @returns {String|undefined} The scm URL to prefetch a licence for, if any.
|
|
703
|
+
*/
|
|
704
|
+
function pomScmUrlNeedingLicenseLookup(entry) {
|
|
705
|
+
if (!entry?.ok || !entry.body) {
|
|
706
|
+
return undefined;
|
|
707
|
+
}
|
|
708
|
+
let pomJson;
|
|
709
|
+
try {
|
|
710
|
+
pomJson = xml2js(entry.body, {
|
|
711
|
+
compact: true,
|
|
712
|
+
spaces: 4,
|
|
713
|
+
textKey: "_",
|
|
714
|
+
attributesKey: "$",
|
|
715
|
+
commentKey: "value",
|
|
716
|
+
}).project;
|
|
717
|
+
} catch (_err) {
|
|
718
|
+
return undefined;
|
|
719
|
+
}
|
|
720
|
+
if (parseLicenseEntryOrArrayFromPomXml(pomJson?.licenses?.license)) {
|
|
721
|
+
return undefined;
|
|
722
|
+
}
|
|
723
|
+
const commentMatch = /<!--([\s\S]*?)-->[\s\n]*<project/m.exec(entry.body);
|
|
724
|
+
if (commentMatch?.[1] && findLicenseId(commentMatch[1].trim())) {
|
|
725
|
+
return undefined;
|
|
726
|
+
}
|
|
727
|
+
return pomJson?.scm?.url?._;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
/**
|
|
731
|
+
* Method to compose URL of pom.xml
|
|
732
|
+
*
|
|
733
|
+
* @param {String} urlPrefix
|
|
734
|
+
* @param {String} group
|
|
735
|
+
* @param {String} name
|
|
736
|
+
* @param {String} version
|
|
737
|
+
*
|
|
738
|
+
* @return {String} fullUrl
|
|
739
|
+
*/
|
|
740
|
+
export function composePomXmlUrl({ urlPrefix, group, name, version }) {
|
|
741
|
+
const groupPart = group.replace(/\./g, "/");
|
|
742
|
+
return `${urlPrefix + groupPart}/${name}/${version}/${name}-${version}.pom`;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
/**
|
|
746
|
+
* Method to fetch pom.xml data and parse it to JSON
|
|
747
|
+
*
|
|
748
|
+
* @param {String} urlPrefix
|
|
749
|
+
* @param {String} group
|
|
750
|
+
* @param {String} name
|
|
751
|
+
* @param {String} version
|
|
752
|
+
*
|
|
753
|
+
* @return {Object|undefined}
|
|
754
|
+
*/
|
|
755
|
+
export async function fetchPomXmlAsJson({ urlPrefix, group, name, version }) {
|
|
756
|
+
const pomXml = await fetchPomXml({ urlPrefix, group, name, version });
|
|
757
|
+
if (!pomXml) {
|
|
758
|
+
return undefined;
|
|
759
|
+
}
|
|
760
|
+
const options = {
|
|
761
|
+
compact: true,
|
|
762
|
+
spaces: 4,
|
|
763
|
+
textKey: "_",
|
|
764
|
+
attributesKey: "$",
|
|
765
|
+
commentKey: "value",
|
|
766
|
+
};
|
|
767
|
+
const pomJson = xml2js(pomXml, options).project;
|
|
768
|
+
if (pomJson?.parent) {
|
|
769
|
+
const parentXml = await fetchPomXml({
|
|
770
|
+
urlPrefix,
|
|
771
|
+
group: pomJson.parent.groupId?._,
|
|
772
|
+
name: pomJson.parent.artifactId?._,
|
|
773
|
+
version: pomJson.parent.version?._,
|
|
774
|
+
});
|
|
775
|
+
if (!parentXml) {
|
|
776
|
+
return undefined;
|
|
777
|
+
}
|
|
778
|
+
const parentJson = xml2js(parentXml, options).project;
|
|
779
|
+
return { ...parentJson, ...pomJson };
|
|
780
|
+
}
|
|
781
|
+
return pomJson;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
/**
|
|
785
|
+
* Prefetched POM documents, populated by `getMvnMetadata` before its loop and
|
|
786
|
+
* read by `fetchPomXml`. Module state rather than a parameter because
|
|
787
|
+
* `fetchPomXml` is called from `fetchPomXmlAsJson` (direct + parent) and
|
|
788
|
+
* `extractLicenseCommentFromPomXml`, and threading a map through all of them
|
|
789
|
+
* would be a wide change for no gain.
|
|
790
|
+
*
|
|
791
|
+
* Only the *direct* POMs — the ones whose URLs are known before the loop — are
|
|
792
|
+
* prefetched. The parent POM walk inside `fetchPomXmlAsJson` depends on
|
|
793
|
+
* parsing the direct POM's content, so those URLs are not known up front and
|
|
794
|
+
* remain serial.
|
|
795
|
+
*/
|
|
796
|
+
const prefetchedPoms = new Map();
|
|
797
|
+
|
|
798
|
+
/**
|
|
799
|
+
* Method to fetch pom.xml data
|
|
800
|
+
*
|
|
801
|
+
* @param {String} urlPrefix
|
|
802
|
+
* @param {String} group
|
|
803
|
+
* @param {String} name
|
|
804
|
+
* @param {String} version
|
|
805
|
+
*
|
|
806
|
+
* @return {Promise<String>}
|
|
807
|
+
*/
|
|
808
|
+
export async function fetchPomXml({ urlPrefix, group, name, version }) {
|
|
809
|
+
const fullUrl = composePomXmlUrl({ urlPrefix, group, name, version });
|
|
810
|
+
// A direct POM may have been prefetched by getMvnMetadata's batch round.
|
|
811
|
+
// Parent POMs (fetched from inside fetchPomXmlAsJson) are not prefetched
|
|
812
|
+
// because their URL depends on the direct POM's content, so they fall
|
|
813
|
+
// through to the serial request unchanged.
|
|
814
|
+
try {
|
|
815
|
+
const res =
|
|
816
|
+
prefetchedResponse(prefetchedPoms, fullUrl) ||
|
|
817
|
+
(await cdxgenAgent.get(fullUrl));
|
|
818
|
+
return res.body;
|
|
819
|
+
} catch (_err) {
|
|
820
|
+
return undefined;
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
/**
|
|
825
|
+
* Method extract single or multiple license entries that might appear in pom.xml
|
|
826
|
+
*
|
|
827
|
+
* @param {Object|Array} license
|
|
828
|
+
*/
|
|
829
|
+
export function parseLicenseEntryOrArrayFromPomXml(license) {
|
|
830
|
+
if (!license) return;
|
|
831
|
+
if (Array.isArray(license)) {
|
|
832
|
+
return license.map((l) => {
|
|
833
|
+
return findLicenseId(l.name?._);
|
|
834
|
+
});
|
|
835
|
+
}
|
|
836
|
+
if (Object.keys(license).length) {
|
|
837
|
+
return [findLicenseId(license.name?._)];
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
/**
|
|
842
|
+
* Method to parse pom.xml in search of a comment containing license text
|
|
843
|
+
*
|
|
844
|
+
* @param {String} urlPrefix
|
|
845
|
+
* @param {String} group
|
|
846
|
+
* @param {String} name
|
|
847
|
+
* @param {String} version
|
|
848
|
+
*
|
|
849
|
+
* @return {Promise<String>} License ID
|
|
850
|
+
*/
|
|
851
|
+
export async function extractLicenseCommentFromPomXml({
|
|
852
|
+
urlPrefix,
|
|
853
|
+
group,
|
|
854
|
+
name,
|
|
855
|
+
version,
|
|
856
|
+
}) {
|
|
857
|
+
const pom_xml = await fetchPomXml({ urlPrefix, group, name, version });
|
|
858
|
+
const licenseRegex = /<!--([\s\S]*?)-->[\s\n]*<project/m;
|
|
859
|
+
const match = licenseRegex.exec(pom_xml);
|
|
860
|
+
if (match?.[1]) {
|
|
861
|
+
return findLicenseId(match[1].trim());
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
/**
|
|
866
|
+
* Method to mimic pip version solver using node-semver
|
|
867
|
+
*
|
|
868
|
+
* @param {Array} versionsList List of version numbers available
|
|
869
|
+
* @param {*} versionSpecifiers pip version specifier
|
|
870
|
+
*/
|
|
871
|
+
export function guessPypiMatchingVersion(versionsList, versionSpecifiers) {
|
|
872
|
+
versionSpecifiers = versionSpecifiers.replace(/,/g, " ").split(";")[0];
|
|
873
|
+
const comparator = (a, b) => {
|
|
874
|
+
if (!a && !b) {
|
|
875
|
+
return 0;
|
|
876
|
+
}
|
|
877
|
+
if (!a || !coerce(a, { loose: true })) {
|
|
878
|
+
return -1;
|
|
879
|
+
}
|
|
880
|
+
let c = coerce(a, { loose: true }).compare(coerce(b, { loose: true }));
|
|
881
|
+
// if coerced versions are "equal", compare them as strings
|
|
882
|
+
if (c === 0) {
|
|
883
|
+
c = a < b ? -1 : 1;
|
|
884
|
+
}
|
|
885
|
+
return -c;
|
|
886
|
+
};
|
|
887
|
+
// Iterate in the "reverse" order
|
|
888
|
+
for (const rv of versionsList.sort(comparator)) {
|
|
889
|
+
if (satisfies(coerce(rv, { loose: true }), versionSpecifiers, true)) {
|
|
890
|
+
return rv;
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
// Let's try to clean and have another go
|
|
894
|
+
return maxSatisfying(versionsList, clean(versionSpecifiers, { loose: true }));
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
/**
|
|
898
|
+
* The PyPI path for a package, or `undefined` when the package will not be
|
|
899
|
+
* queried at all.
|
|
900
|
+
*
|
|
901
|
+
* Shared by the batch prefetch and the request inside the loop so the two
|
|
902
|
+
* cannot construct different URLs. It deliberately reproduces the loop's skip
|
|
903
|
+
* conditions — a URL built for a package the loop skips would be a request the
|
|
904
|
+
* JS path never makes, which is both wasted work and a divergence.
|
|
905
|
+
*
|
|
906
|
+
* @param {Object} p Package object.
|
|
907
|
+
* @returns {string|undefined} Path to append to PYPI_URL.
|
|
908
|
+
*/
|
|
909
|
+
function pyUrlAddition(p) {
|
|
910
|
+
if (!p?.name) {
|
|
911
|
+
return undefined;
|
|
912
|
+
}
|
|
913
|
+
// A URL as a name, or a package that already has both fields, is not queried.
|
|
914
|
+
if (p.name.includes("https") || (p.license && p.version)) {
|
|
915
|
+
return undefined;
|
|
916
|
+
}
|
|
917
|
+
// Extras (`requests[security]`) are not part of the PyPI path.
|
|
918
|
+
const name = p.name.includes("[") ? p.name.split("[")[0] : p.name;
|
|
919
|
+
return p.version?.trim().length
|
|
920
|
+
? `${name}/${p.version.trim()}/json`
|
|
921
|
+
: `${name}/json`;
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
/**
|
|
925
|
+
* Method to retrieve metadata for python packages by querying pypi
|
|
926
|
+
*
|
|
927
|
+
* @param {Array} pkgList Package list
|
|
928
|
+
* @param {Boolean} fetchDepsInfo Fetch dependencies info from pypi
|
|
929
|
+
*/
|
|
930
|
+
export async function getPyMetadata(pkgList, fetchDepsInfo) {
|
|
931
|
+
if (!shouldFetchPackageMetadata() && !fetchDepsInfo) {
|
|
932
|
+
return pkgList;
|
|
933
|
+
}
|
|
934
|
+
const PYPI_URL =
|
|
935
|
+
readEnvironmentVariable("PYPI_URL") || "https://pypi.org/pypi/";
|
|
936
|
+
const cdepList = [];
|
|
937
|
+
// One batched round for the primary lookups. The `django-` retry below is not
|
|
938
|
+
// prefetched: it only fires when the primary 404s, so batching it would issue
|
|
939
|
+
// a request the serial path never makes for every package that resolves.
|
|
940
|
+
const prefetched = await prefetchJson(
|
|
941
|
+
prefetchEnabled()
|
|
942
|
+
? pkgList
|
|
943
|
+
.map((p) => pyUrlAddition(p))
|
|
944
|
+
.filter(Boolean)
|
|
945
|
+
.map((addition) => ({ url: PYPI_URL + addition }))
|
|
946
|
+
: [],
|
|
947
|
+
);
|
|
948
|
+
for (const p of pkgList) {
|
|
949
|
+
if (!p?.name) {
|
|
950
|
+
continue;
|
|
951
|
+
}
|
|
952
|
+
try {
|
|
953
|
+
// If the package name has a url or already includes license and version skip it
|
|
954
|
+
if (p.name.includes("https") || (p.license && p.version)) {
|
|
955
|
+
cdepList.push(p);
|
|
956
|
+
continue;
|
|
957
|
+
}
|
|
958
|
+
const origName = p.name;
|
|
959
|
+
// Some packages support extra modules
|
|
960
|
+
if (p.name.includes("[")) {
|
|
961
|
+
p.name = p.name.split("[")[0];
|
|
962
|
+
}
|
|
963
|
+
let res;
|
|
964
|
+
let url_addition;
|
|
965
|
+
if (p.version?.trim().length) {
|
|
966
|
+
url_addition = `${p.name}/${p.version.trim()}/json`;
|
|
967
|
+
} else {
|
|
968
|
+
url_addition = `${p.name}/json`;
|
|
969
|
+
}
|
|
970
|
+
try {
|
|
971
|
+
res =
|
|
972
|
+
prefetchedResponse(prefetched, PYPI_URL + url_addition) ||
|
|
973
|
+
(await cdxgenAgent.get(`${PYPI_URL + url_addition}`, {
|
|
974
|
+
responseType: "json",
|
|
975
|
+
}));
|
|
976
|
+
} catch (_err) {
|
|
977
|
+
// retry by prefixing django- to the package name
|
|
978
|
+
res = await cdxgenAgent.get(`${PYPI_URL}django-${url_addition}`, {
|
|
979
|
+
responseType: "json",
|
|
980
|
+
});
|
|
981
|
+
p.name = `django-${p.name}`;
|
|
982
|
+
}
|
|
983
|
+
const body = res.body;
|
|
984
|
+
if (body.info.author && body.info.author.trim() !== "") {
|
|
985
|
+
if (body.info.author_email && body.info.author_email.trim() !== "") {
|
|
986
|
+
p.author = `${body.info.author.trim()} <${body.info.author_email.trim()}>`;
|
|
987
|
+
} else {
|
|
988
|
+
p.author = body.info.author.trim();
|
|
989
|
+
}
|
|
990
|
+
} else if (
|
|
991
|
+
body.info.author_email &&
|
|
992
|
+
body.info.author_email.trim() !== ""
|
|
993
|
+
) {
|
|
994
|
+
p.author = body.info.author_email.trim();
|
|
995
|
+
}
|
|
996
|
+
if (
|
|
997
|
+
p.name !== body.info?.name &&
|
|
998
|
+
p.name.toLowerCase() === body.info?.name.toLowerCase()
|
|
999
|
+
) {
|
|
1000
|
+
p.name = body.info.name;
|
|
1001
|
+
}
|
|
1002
|
+
p.description = body.info.summary;
|
|
1003
|
+
p.license = [];
|
|
1004
|
+
if (body.info.classifiers) {
|
|
1005
|
+
for (const c of body.info.classifiers) {
|
|
1006
|
+
if (c.startsWith("License :: ")) {
|
|
1007
|
+
const licenseName = c.split("::").slice(-1)[0].trim();
|
|
1008
|
+
const licenseId = findLicenseId(licenseName);
|
|
1009
|
+
if (licenseId && !p.license.includes(licenseId)) {
|
|
1010
|
+
p.license.push(licenseId);
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
if (body.info.license) {
|
|
1016
|
+
const licenseId = findLicenseId(body.info.license);
|
|
1017
|
+
if (licenseId && !p.license.includes(licenseId)) {
|
|
1018
|
+
p.license.push(licenseId);
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
if (body.info.license_expression) {
|
|
1022
|
+
const licenseId = findLicenseId(body.info.license_expression);
|
|
1023
|
+
if (licenseId && !p.license.includes(licenseId)) {
|
|
1024
|
+
p.license.push(licenseId);
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
if (body.info.home_page) {
|
|
1028
|
+
if (body.info.home_page.includes("git")) {
|
|
1029
|
+
p.repository = { url: body.info.home_page };
|
|
1030
|
+
} else {
|
|
1031
|
+
p.homepage = { url: body.info.home_page };
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
// Use the latest version if none specified
|
|
1035
|
+
if (!p.version?.trim().length) {
|
|
1036
|
+
let versionSpecifiers;
|
|
1037
|
+
if (p.properties?.length) {
|
|
1038
|
+
for (const pprop of p.properties) {
|
|
1039
|
+
if (pprop.name === "cdx:pypi:versionSpecifiers") {
|
|
1040
|
+
versionSpecifiers = pprop.value;
|
|
1041
|
+
break;
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
} else if (
|
|
1045
|
+
p.version &&
|
|
1046
|
+
(p.version.includes("*") ||
|
|
1047
|
+
p.version.includes("<") ||
|
|
1048
|
+
p.version.includes(">") ||
|
|
1049
|
+
p.version.includes("!"))
|
|
1050
|
+
) {
|
|
1051
|
+
versionSpecifiers = p.version;
|
|
1052
|
+
}
|
|
1053
|
+
if (versionSpecifiers) {
|
|
1054
|
+
p.version = guessPypiMatchingVersion(
|
|
1055
|
+
Object.keys(body.releases || {}),
|
|
1056
|
+
versionSpecifiers,
|
|
1057
|
+
);
|
|
1058
|
+
// Indicate the confidence with our guess
|
|
1059
|
+
p.evidence = {
|
|
1060
|
+
identity: {
|
|
1061
|
+
field: "version",
|
|
1062
|
+
confidence: 0.6,
|
|
1063
|
+
methods: [
|
|
1064
|
+
{
|
|
1065
|
+
technique: "manifest-analysis",
|
|
1066
|
+
confidence: 0.6,
|
|
1067
|
+
value: `Version specifiers: ${versionSpecifiers}`,
|
|
1068
|
+
},
|
|
1069
|
+
],
|
|
1070
|
+
},
|
|
1071
|
+
};
|
|
1072
|
+
}
|
|
1073
|
+
// If we have reached here, it means we have not solved the version
|
|
1074
|
+
// So assume latest
|
|
1075
|
+
if (!p.version) {
|
|
1076
|
+
p.version = body.info.version;
|
|
1077
|
+
// Indicate the low confidence
|
|
1078
|
+
p.evidence = {
|
|
1079
|
+
identity: {
|
|
1080
|
+
field: "version",
|
|
1081
|
+
confidence: 0.5,
|
|
1082
|
+
methods: [
|
|
1083
|
+
{
|
|
1084
|
+
technique: "source-code-analysis",
|
|
1085
|
+
confidence: 0.5,
|
|
1086
|
+
value: `PyPI package: ${p.name}`,
|
|
1087
|
+
},
|
|
1088
|
+
],
|
|
1089
|
+
},
|
|
1090
|
+
};
|
|
1091
|
+
}
|
|
1092
|
+
} else if (p.version !== body.info.version) {
|
|
1093
|
+
if (!p.properties) {
|
|
1094
|
+
p.properties = [];
|
|
1095
|
+
}
|
|
1096
|
+
p.properties.push({
|
|
1097
|
+
name: "cdx:pypi:latest_version",
|
|
1098
|
+
value: body.info.version,
|
|
1099
|
+
});
|
|
1100
|
+
p.properties.push({
|
|
1101
|
+
name: "cdx:pypi:resolved_from",
|
|
1102
|
+
value: origName,
|
|
1103
|
+
});
|
|
1104
|
+
}
|
|
1105
|
+
const releaseEntries = body.releases?.[p.version]?.length
|
|
1106
|
+
? body.releases[p.version]
|
|
1107
|
+
: Array.isArray(body.urls)
|
|
1108
|
+
? body.urls
|
|
1109
|
+
: [];
|
|
1110
|
+
mergeExternalReferences(
|
|
1111
|
+
p,
|
|
1112
|
+
collectPypiReleaseExternalReferences(releaseEntries),
|
|
1113
|
+
);
|
|
1114
|
+
if (releaseEntries.length) {
|
|
1115
|
+
const digest = releaseEntries[0].digests;
|
|
1116
|
+
if (digest["sha256"]) {
|
|
1117
|
+
p._integrity = `sha256-${digest["sha256"]}`;
|
|
1118
|
+
} else if (digest["md5"]) {
|
|
1119
|
+
p._integrity = `md5-${digest["md5"]}`;
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
const purlString = build({
|
|
1123
|
+
type: "pypi",
|
|
1124
|
+
namespace: "" || null,
|
|
1125
|
+
name: p.name.toLowerCase(),
|
|
1126
|
+
version: p.version || null,
|
|
1127
|
+
});
|
|
1128
|
+
p.properties = p.properties || [];
|
|
1129
|
+
p.properties.push(
|
|
1130
|
+
...collectPypiRegistryProvenanceProperties(body, p.version),
|
|
1131
|
+
);
|
|
1132
|
+
p.purl = purlString;
|
|
1133
|
+
p["bom-ref"] = decodeURIComponent(purlString);
|
|
1134
|
+
cdepList.push(p);
|
|
1135
|
+
} catch (_err) {
|
|
1136
|
+
if (DEBUG_MODE) {
|
|
1137
|
+
console.error(p.name, "is not found on PyPI.");
|
|
1138
|
+
console.log(
|
|
1139
|
+
"If this package is available from PyPI or a registry, its name might be different from the module name. Raise a ticket at https://github.com/cdxgen/cdxgen/issues so that this can be added to the mapping file pypi-pkg-aliases.json",
|
|
1140
|
+
);
|
|
1141
|
+
console.log(
|
|
1142
|
+
"Alternatively, if this is a package that gets installed directly in your environment and offers a python binding, then track such packages manually.",
|
|
1143
|
+
);
|
|
1144
|
+
}
|
|
1145
|
+
if (!p.version) {
|
|
1146
|
+
if (DEBUG_MODE) {
|
|
1147
|
+
console.log(
|
|
1148
|
+
`Assuming the version as latest for the package ${p.name}`,
|
|
1149
|
+
);
|
|
1150
|
+
}
|
|
1151
|
+
p.version = "latest";
|
|
1152
|
+
// Indicate the low confidence
|
|
1153
|
+
p.evidence = {
|
|
1154
|
+
identity: {
|
|
1155
|
+
field: "version",
|
|
1156
|
+
confidence: 0,
|
|
1157
|
+
methods: [
|
|
1158
|
+
{
|
|
1159
|
+
technique: "source-code-analysis",
|
|
1160
|
+
confidence: 0,
|
|
1161
|
+
value: `Module ${p.name}`,
|
|
1162
|
+
},
|
|
1163
|
+
],
|
|
1164
|
+
},
|
|
1165
|
+
};
|
|
1166
|
+
}
|
|
1167
|
+
const purlString = build({
|
|
1168
|
+
type: "pypi",
|
|
1169
|
+
namespace: "" || null,
|
|
1170
|
+
name: p.name.toLowerCase(),
|
|
1171
|
+
version: p.version || null,
|
|
1172
|
+
});
|
|
1173
|
+
p.purl = purlString;
|
|
1174
|
+
p["bom-ref"] = decodeURIComponent(purlString);
|
|
1175
|
+
cdepList.push(p);
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
return cdepList;
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
/**
|
|
1182
|
+
* Method to parse bdist_wheel metadata (dist-info/METADATA)
|
|
1183
|
+
*
|
|
1184
|
+
* @param {string} mDataFile bdist_wheel metadata file
|
|
1185
|
+
* @param {string} rawMetadata Raw metadata
|
|
1186
|
+
*
|
|
1187
|
+
*/
|
|
1188
|
+
export function parseBdistMetadata(mDataFile, rawMetadata = undefined) {
|
|
1189
|
+
const mData = rawMetadata || readFileSync(mDataFile, { encoding: "utf-8" });
|
|
1190
|
+
const pkg = {
|
|
1191
|
+
name: "",
|
|
1192
|
+
version: "",
|
|
1193
|
+
description: "",
|
|
1194
|
+
author: "",
|
|
1195
|
+
licenses: [],
|
|
1196
|
+
externalReferences: [],
|
|
1197
|
+
properties: [],
|
|
1198
|
+
};
|
|
1199
|
+
if (mDataFile) {
|
|
1200
|
+
pkg.properties.push({ name: "internal:SrcFile", value: mDataFile });
|
|
1201
|
+
}
|
|
1202
|
+
const lines = mData.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n");
|
|
1203
|
+
let isBody = false;
|
|
1204
|
+
for (const line of lines) {
|
|
1205
|
+
if (line.trim() === "") {
|
|
1206
|
+
isBody = true;
|
|
1207
|
+
continue;
|
|
1208
|
+
}
|
|
1209
|
+
if (isBody) break;
|
|
1210
|
+
const firstColon = line.indexOf(":");
|
|
1211
|
+
if (firstColon === -1) continue;
|
|
1212
|
+
const key = line.substring(0, firstColon).trim().toLowerCase();
|
|
1213
|
+
const value = line.substring(firstColon + 1).trim();
|
|
1214
|
+
switch (key) {
|
|
1215
|
+
case "name":
|
|
1216
|
+
pkg.name = value;
|
|
1217
|
+
break;
|
|
1218
|
+
case "version":
|
|
1219
|
+
pkg.version = value;
|
|
1220
|
+
break;
|
|
1221
|
+
case "summary":
|
|
1222
|
+
pkg.description = value;
|
|
1223
|
+
break;
|
|
1224
|
+
case "author":
|
|
1225
|
+
case "maintainer":
|
|
1226
|
+
pkg.publisher = value;
|
|
1227
|
+
pkg.author = value;
|
|
1228
|
+
break;
|
|
1229
|
+
case "license-expression":
|
|
1230
|
+
pkg.licenses.push({
|
|
1231
|
+
expression: value,
|
|
1232
|
+
});
|
|
1233
|
+
break;
|
|
1234
|
+
case "license":
|
|
1235
|
+
if (value !== "UNKNOWN" && pkg.licenses.length === 0) {
|
|
1236
|
+
pkg.licenses.push({
|
|
1237
|
+
license: {
|
|
1238
|
+
name: value,
|
|
1239
|
+
},
|
|
1240
|
+
});
|
|
1241
|
+
}
|
|
1242
|
+
break;
|
|
1243
|
+
case "home-page":
|
|
1244
|
+
pkg.homepage = {
|
|
1245
|
+
url: value,
|
|
1246
|
+
};
|
|
1247
|
+
pkg.externalReferences.push({
|
|
1248
|
+
type: "website",
|
|
1249
|
+
url: value,
|
|
1250
|
+
});
|
|
1251
|
+
break;
|
|
1252
|
+
case "project-url": {
|
|
1253
|
+
const commaIndex = value.indexOf(",");
|
|
1254
|
+
if (commaIndex > -1) {
|
|
1255
|
+
const label = value.substring(0, commaIndex).trim();
|
|
1256
|
+
const url = value.substring(commaIndex + 1).trim();
|
|
1257
|
+
const lowerLabel = label.toLowerCase();
|
|
1258
|
+
let type = "website";
|
|
1259
|
+
if (
|
|
1260
|
+
["source", "source code", "repository", "git"].includes(lowerLabel)
|
|
1261
|
+
) {
|
|
1262
|
+
type = "vcs";
|
|
1263
|
+
pkg.repository = {
|
|
1264
|
+
url: url,
|
|
1265
|
+
};
|
|
1266
|
+
} else if (
|
|
1267
|
+
["tracker", "bug tracker", "issue tracker", "issues"].includes(
|
|
1268
|
+
lowerLabel,
|
|
1269
|
+
)
|
|
1270
|
+
) {
|
|
1271
|
+
type = "issue-tracker";
|
|
1272
|
+
} else if (["changelog", "changes", "history"].includes(lowerLabel)) {
|
|
1273
|
+
type = "release-notes";
|
|
1274
|
+
} else if (["documentation", "docs"].includes(lowerLabel)) {
|
|
1275
|
+
type = "documentation";
|
|
1276
|
+
} else if (["funding", "sponsor", "donation"].includes(lowerLabel)) {
|
|
1277
|
+
type = "other";
|
|
1278
|
+
}
|
|
1279
|
+
pkg.externalReferences.push({
|
|
1280
|
+
type: type,
|
|
1281
|
+
url: url,
|
|
1282
|
+
comment: label,
|
|
1283
|
+
});
|
|
1284
|
+
}
|
|
1285
|
+
break;
|
|
1286
|
+
}
|
|
1287
|
+
case "keywords":
|
|
1288
|
+
if (value) {
|
|
1289
|
+
pkg.keywords = value.split(",").map((k) => k.trim());
|
|
1290
|
+
}
|
|
1291
|
+
break;
|
|
1292
|
+
case "requires-python":
|
|
1293
|
+
pkg.properties.push({
|
|
1294
|
+
name: "cdx:python:requires_python",
|
|
1295
|
+
value: value,
|
|
1296
|
+
});
|
|
1297
|
+
break;
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
if (mDataFile) {
|
|
1301
|
+
pkg.evidence = {
|
|
1302
|
+
identity: {
|
|
1303
|
+
field: "purl",
|
|
1304
|
+
confidence: 0.5,
|
|
1305
|
+
methods: [
|
|
1306
|
+
{
|
|
1307
|
+
technique: "manifest-analysis",
|
|
1308
|
+
confidence: 0.5,
|
|
1309
|
+
value: mDataFile,
|
|
1310
|
+
},
|
|
1311
|
+
],
|
|
1312
|
+
},
|
|
1313
|
+
};
|
|
1314
|
+
}
|
|
1315
|
+
applyPurl(pkg, pypiPurl(pkg.name, pkg.version));
|
|
1316
|
+
return [pkg];
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
/**
|
|
1320
|
+
* Build a stable dedupe key for an external reference from its type/url/comment.
|
|
1321
|
+
*
|
|
1322
|
+
* @param {{type: string, url: string, comment?: string}} reference External reference.
|
|
1323
|
+
* @returns {string} JSON-stringified dedupe key.
|
|
1324
|
+
*/
|
|
1325
|
+
export function createExternalReferenceKey(reference) {
|
|
1326
|
+
return JSON.stringify([
|
|
1327
|
+
reference.type,
|
|
1328
|
+
reference.url,
|
|
1329
|
+
reference.comment || "",
|
|
1330
|
+
]);
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
/**
|
|
1334
|
+
* Merge external references onto a component, skipping duplicates.
|
|
1335
|
+
*
|
|
1336
|
+
* @param {object} component Component to enrich with external references.
|
|
1337
|
+
* @param {Array<{type: string, url: string, comment?: string}>} references References to merge.
|
|
1338
|
+
* @returns {void}
|
|
1339
|
+
*/
|
|
1340
|
+
export function mergeExternalReferences(component, references) {
|
|
1341
|
+
if (!references?.length) {
|
|
1342
|
+
return;
|
|
1343
|
+
}
|
|
1344
|
+
const existingReferences = component.externalReferences || [];
|
|
1345
|
+
const seen = new Set(
|
|
1346
|
+
existingReferences.map((reference) =>
|
|
1347
|
+
createExternalReferenceKey(reference),
|
|
1348
|
+
),
|
|
1349
|
+
);
|
|
1350
|
+
for (const reference of references) {
|
|
1351
|
+
const dedupeKey = createExternalReferenceKey(reference);
|
|
1352
|
+
if (seen.has(dedupeKey)) {
|
|
1353
|
+
continue;
|
|
1354
|
+
}
|
|
1355
|
+
seen.add(dedupeKey);
|
|
1356
|
+
existingReferences.push(reference);
|
|
1357
|
+
}
|
|
1358
|
+
if (existingReferences.length) {
|
|
1359
|
+
component.externalReferences = existingReferences;
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
function collectPypiReleaseExternalReferences(releaseEntries) {
|
|
1364
|
+
const externalReferences = [];
|
|
1365
|
+
for (const releaseEntry of releaseEntries || []) {
|
|
1366
|
+
if (typeof releaseEntry?.url !== "string" || !releaseEntry.url.trim()) {
|
|
1367
|
+
continue;
|
|
1368
|
+
}
|
|
1369
|
+
externalReferences.push({
|
|
1370
|
+
type: "distribution",
|
|
1371
|
+
url: releaseEntry.url.trim(),
|
|
1372
|
+
comment: releaseEntry.filename || releaseEntry.packagetype,
|
|
1373
|
+
});
|
|
1374
|
+
}
|
|
1375
|
+
return externalReferences;
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
/**
|
|
1379
|
+
* Method to construct a GitHub API url for the given repo metadata
|
|
1380
|
+
* @param {Object} repoMetadata Repo metadata with group and name
|
|
1381
|
+
* @return {String|undefined} github api url (or undefined - if not enough data)
|
|
1382
|
+
*/
|
|
1383
|
+
export function repoMetadataToGitHubApiUrl(repoMetadata) {
|
|
1384
|
+
if (repoMetadata) {
|
|
1385
|
+
const group = repoMetadata.group;
|
|
1386
|
+
const name = repoMetadata.name;
|
|
1387
|
+
// GITHUB_API_URL is what GitHub Actions itself sets, and it is what a
|
|
1388
|
+
// GitHub Enterprise user needs. It also makes this path testable without
|
|
1389
|
+
// reaching the real api.github.com, which is why the licence lookups had no
|
|
1390
|
+
// offline coverage before.
|
|
1391
|
+
const apiBase = (
|
|
1392
|
+
readEnvironmentVariable("GITHUB_API_URL") || "https://api.github.com"
|
|
1393
|
+
).replace(/\/$/, "");
|
|
1394
|
+
let ghUrl = `${apiBase}/repos`;
|
|
1395
|
+
if (group && group !== "." && group !== "") {
|
|
1396
|
+
ghUrl = `${ghUrl}/${group.replace("github.com/", "")}`;
|
|
1397
|
+
}
|
|
1398
|
+
ghUrl = `${ghUrl}/${name}`;
|
|
1399
|
+
return ghUrl;
|
|
1400
|
+
}
|
|
1401
|
+
return undefined;
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
/**
|
|
1405
|
+
* Method to split GitHub url into its parts
|
|
1406
|
+
* @param {String} repoUrl Repository url
|
|
1407
|
+
* @return {[String]} parts from url
|
|
1408
|
+
*/
|
|
1409
|
+
export function getGithubUrlParts(repoUrl) {
|
|
1410
|
+
if (repoUrl.toLowerCase().endsWith(".git")) {
|
|
1411
|
+
repoUrl = repoUrl.slice(0, -4);
|
|
1412
|
+
}
|
|
1413
|
+
repoUrl.replace(/\/$/, "");
|
|
1414
|
+
return repoUrl.split("/");
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
/**
|
|
1418
|
+
* Method to construct GitHub api url from repo metadata or one of multiple formats of repo URLs
|
|
1419
|
+
* @param {String} repoUrl Repository url
|
|
1420
|
+
* @param {Object} repoMetadata Object containing group and package name strings
|
|
1421
|
+
* @return {String|undefined} github api url (or undefined - if not a GitHub repo)
|
|
1422
|
+
*/
|
|
1423
|
+
export function toGitHubApiUrl(repoUrl, repoMetadata) {
|
|
1424
|
+
if (repoMetadata) {
|
|
1425
|
+
return repoMetadataToGitHubApiUrl(repoMetadata);
|
|
1426
|
+
}
|
|
1427
|
+
const parts = getGithubUrlParts(repoUrl);
|
|
1428
|
+
if (parts.length < 5 || parts[2] !== "github.com") {
|
|
1429
|
+
return undefined; // Not a valid GitHub repo URL
|
|
1430
|
+
}
|
|
1431
|
+
return repoMetadataToGitHubApiUrl({
|
|
1432
|
+
group: parts[3],
|
|
1433
|
+
name: parts[4],
|
|
1434
|
+
});
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
/**
|
|
1438
|
+
* Method to retrieve repo license by querying github api
|
|
1439
|
+
*
|
|
1440
|
+
* @param {String} repoUrl Repository url
|
|
1441
|
+
* @param {Object} repoMetadata Object containing group and package name strings
|
|
1442
|
+
* @return {Promise<String>} SPDX license id
|
|
1443
|
+
*/
|
|
1444
|
+
/**
|
|
1445
|
+
* URLs for repository licence lookups, prefetched by {@link prefetchRepoLicenses}.
|
|
1446
|
+
*
|
|
1447
|
+
* Module state rather than a parameter because `getRepoLicense` is called from
|
|
1448
|
+
* eight places, several of them deep inside other metadata functions, and
|
|
1449
|
+
* threading a map through all of them would be a wide change for no gain. A
|
|
1450
|
+
* miss simply means the lookup issues its own request.
|
|
1451
|
+
*/
|
|
1452
|
+
let repoLicensePrefetch = new Map();
|
|
1453
|
+
|
|
1454
|
+
/**
|
|
1455
|
+
* Prefetch the GitHub licence endpoint for a list of repository URLs.
|
|
1456
|
+
*
|
|
1457
|
+
* The single biggest remaining serialisation: `getRepoLicense` is called once
|
|
1458
|
+
* per component from npm, Maven, Swift and Go, each call a full round trip to
|
|
1459
|
+
* api.github.com. Batching them also makes the authenticated concurrency
|
|
1460
|
+
* allowance worth having — with `GITHUB_TOKEN` set, cdxrs runs eight of these at
|
|
1461
|
+
* a time instead of one.
|
|
1462
|
+
*
|
|
1463
|
+
* @param {Array<string|undefined>} repoUrls Repository URLs (duplicates and
|
|
1464
|
+
* empties are fine).
|
|
1465
|
+
* @returns {Promise<void>}
|
|
1466
|
+
*/
|
|
1467
|
+
export async function prefetchRepoLicenses(repoUrls) {
|
|
1468
|
+
if (!prefetchEnabled() || !Array.isArray(repoUrls)) {
|
|
1469
|
+
return;
|
|
1470
|
+
}
|
|
1471
|
+
const requests = [];
|
|
1472
|
+
const seen = new Set();
|
|
1473
|
+
for (const repoUrl of repoUrls) {
|
|
1474
|
+
if (!repoUrl) {
|
|
1475
|
+
continue;
|
|
1476
|
+
}
|
|
1477
|
+
const apiUrl = toGitHubApiUrl(repoUrl, undefined);
|
|
1478
|
+
if (!apiUrl) {
|
|
1479
|
+
continue;
|
|
1480
|
+
}
|
|
1481
|
+
const licenseUrl = `${apiUrl}/license`;
|
|
1482
|
+
if (seen.has(licenseUrl)) {
|
|
1483
|
+
continue;
|
|
1484
|
+
}
|
|
1485
|
+
seen.add(licenseUrl);
|
|
1486
|
+
requests.push({
|
|
1487
|
+
url: licenseUrl,
|
|
1488
|
+
// The realm keys the cache per repository, never on the token itself, so
|
|
1489
|
+
// an authenticated response cannot be served to an anonymous lookup.
|
|
1490
|
+
authRealm: readEnvironmentVariable("GITHUB_TOKEN")
|
|
1491
|
+
? `github-auth:${apiUrl}`
|
|
1492
|
+
: undefined,
|
|
1493
|
+
});
|
|
1494
|
+
}
|
|
1495
|
+
if (!requests.length) {
|
|
1496
|
+
return;
|
|
1497
|
+
}
|
|
1498
|
+
const fetched = await prefetchJson(requests);
|
|
1499
|
+
for (const [key, value] of fetched) {
|
|
1500
|
+
repoLicensePrefetch.set(key, value);
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
/**
|
|
1505
|
+
* Discard prefetched repository licences. Tests only.
|
|
1506
|
+
*/
|
|
1507
|
+
export function resetRepoLicensePrefetch() {
|
|
1508
|
+
repoLicensePrefetch = new Map();
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
/**
|
|
1512
|
+
* Fetch the license for a repository, primarily via the GitHub license API.
|
|
1513
|
+
*
|
|
1514
|
+
* Resolves the GitHub API license endpoint for the repository URL, deriving an
|
|
1515
|
+
* SPDX id from the response (or by scanning the license file content when the
|
|
1516
|
+
* API reports `NOASSERTION`). Honours any prefetched response.
|
|
1517
|
+
*
|
|
1518
|
+
* @param {string} repoUrl Repository URL.
|
|
1519
|
+
* @param {object} [repoMetadata] Optional repository metadata.
|
|
1520
|
+
* @returns {Promise<{url: string, id?: string, name?: string}|undefined>}
|
|
1521
|
+
* Resolved license object, or undefined when no license can be determined.
|
|
1522
|
+
*/
|
|
1523
|
+
export async function getRepoLicense(repoUrl, repoMetadata) {
|
|
1524
|
+
if (!repoUrl) {
|
|
1525
|
+
return undefined;
|
|
1526
|
+
}
|
|
1527
|
+
const apiUrl = toGitHubApiUrl(repoUrl, repoMetadata);
|
|
1528
|
+
// Perform github lookups
|
|
1529
|
+
if (apiUrl && get_repo_license_errors < MAX_GET_REPO_LICENSE_ERRORS) {
|
|
1530
|
+
const licenseUrl = `${apiUrl}/license`;
|
|
1531
|
+
const headers = {};
|
|
1532
|
+
if (readEnvironmentVariable("GITHUB_TOKEN")) {
|
|
1533
|
+
headers["Authorization"] =
|
|
1534
|
+
`Bearer ${readEnvironmentVariable("GITHUB_TOKEN")}`;
|
|
1535
|
+
}
|
|
1536
|
+
try {
|
|
1537
|
+
const res =
|
|
1538
|
+
prefetchedResponse(repoLicensePrefetch, licenseUrl) ||
|
|
1539
|
+
(await cdxgenAgent.get(licenseUrl, {
|
|
1540
|
+
responseType: "json",
|
|
1541
|
+
headers: headers,
|
|
1542
|
+
}));
|
|
1543
|
+
if (res?.body) {
|
|
1544
|
+
const license = res.body.license;
|
|
1545
|
+
let licenseId = license.spdx_id;
|
|
1546
|
+
const licObj = {
|
|
1547
|
+
url: res.body.html_url,
|
|
1548
|
+
};
|
|
1549
|
+
if (license.spdx_id === "NOASSERTION") {
|
|
1550
|
+
if (res.body.content) {
|
|
1551
|
+
const content = Buffer.from(res.body.content, "base64").toString(
|
|
1552
|
+
"ascii",
|
|
1553
|
+
);
|
|
1554
|
+
licenseId = guessLicenseId(content);
|
|
1555
|
+
}
|
|
1556
|
+
// If content match fails attempt to find by name
|
|
1557
|
+
if (!licenseId && license.name.toLowerCase() !== "other") {
|
|
1558
|
+
licenseId = findLicenseId(license.name);
|
|
1559
|
+
licObj["name"] = license.name;
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
licObj["id"] = licenseId;
|
|
1563
|
+
if (licObj["id"] || licObj["name"]) {
|
|
1564
|
+
return licObj;
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
} catch (err) {
|
|
1568
|
+
if (err?.message) {
|
|
1569
|
+
if (
|
|
1570
|
+
err.message.includes("rate limit exceeded") &&
|
|
1571
|
+
!readEnvironmentVariable("GITHUB_TOKEN")
|
|
1572
|
+
) {
|
|
1573
|
+
console.log(
|
|
1574
|
+
"Rate limit exceeded for REST API of github.com. " +
|
|
1575
|
+
"Please ensure GITHUB_TOKEN is set as environment variable. " +
|
|
1576
|
+
"See: https://docs.github.com/en/rest/overview/rate-limits-for-the-rest-api",
|
|
1577
|
+
);
|
|
1578
|
+
get_repo_license_errors++;
|
|
1579
|
+
} else if (!err.message.includes("404")) {
|
|
1580
|
+
get_repo_license_errors++;
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
return undefined;
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
/**
|
|
1589
|
+
* Documents from pkg.go.dev, prefetched by {@link prefetchGoPkgMetadata}.
|
|
1590
|
+
*
|
|
1591
|
+
* Module state for the same reason as {@link prefetchRepoLicenses}: the two
|
|
1592
|
+
* readers are called once per module from six Go parsers, and threading a map
|
|
1593
|
+
* through all of them would be a wide change for no gain. A miss means the
|
|
1594
|
+
* lookup issues its own request, exactly as before.
|
|
1595
|
+
*/
|
|
1596
|
+
let goPkgPrefetch = new Map();
|
|
1597
|
+
|
|
1598
|
+
/**
|
|
1599
|
+
* Prefetch the pkg.go.dev pages for a list of Go modules.
|
|
1600
|
+
*
|
|
1601
|
+
* Go was the last ecosystem making one round trip per module with the URL known
|
|
1602
|
+
* up front: `getGoPkgLicense` and `getGoPkgVCSUrl` are both called from inside
|
|
1603
|
+
* the parsers' loops, so a module list of any size was fetched strictly one at a
|
|
1604
|
+
* time. Both pages are HTML, which is why these requests carry
|
|
1605
|
+
* `responseType: "text"` and so run on the JS pool rather than through cdxrs.
|
|
1606
|
+
*
|
|
1607
|
+
* Callers must pass exactly the modules their loop will look up. A superset
|
|
1608
|
+
* issues requests the serial path never made; a subset only loses some of the
|
|
1609
|
+
* batching.
|
|
1610
|
+
*
|
|
1611
|
+
* @param {Array<{group?: string, name?: string}>} modules Modules about to be
|
|
1612
|
+
* resolved. Duplicates and entries without a name are fine.
|
|
1613
|
+
* @returns {Promise<void>}
|
|
1614
|
+
*/
|
|
1615
|
+
export async function prefetchGoPkgMetadata(modules) {
|
|
1616
|
+
if (!prefetchEnabled() || !Array.isArray(modules)) {
|
|
1617
|
+
return;
|
|
1618
|
+
}
|
|
1619
|
+
const requests = [];
|
|
1620
|
+
const seen = new Set();
|
|
1621
|
+
for (const module of modules) {
|
|
1622
|
+
if (!module?.name) {
|
|
1623
|
+
continue;
|
|
1624
|
+
}
|
|
1625
|
+
const pkgUrl = getGoPkgUrl(module);
|
|
1626
|
+
// The licence tab and the module page are distinct documents, read by
|
|
1627
|
+
// getGoPkgLicense and getGoPkgVCSUrl respectively. Both are requested only
|
|
1628
|
+
// when the corresponding flag is on, so the batch mirrors that.
|
|
1629
|
+
const wanted = [];
|
|
1630
|
+
if (shouldFetchLicense()) {
|
|
1631
|
+
wanted.push(`${pkgUrl}?tab=licenses`);
|
|
1632
|
+
}
|
|
1633
|
+
// getGoPkgVCSUrl derives github.com and gitlab.com URLs without asking
|
|
1634
|
+
// pkg.go.dev, so those modules must not be requested here either.
|
|
1635
|
+
if (shouldFetchVCS() && !isDirectlyResolvableGoVCS(module)) {
|
|
1636
|
+
wanted.push(pkgUrl);
|
|
1637
|
+
}
|
|
1638
|
+
for (const url of wanted) {
|
|
1639
|
+
if (seen.has(url) || metadata_cache[url]) {
|
|
1640
|
+
continue;
|
|
1641
|
+
}
|
|
1642
|
+
seen.add(url);
|
|
1643
|
+
requests.push({ url, responseType: "text" });
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
if (!requests.length) {
|
|
1647
|
+
return;
|
|
1648
|
+
}
|
|
1649
|
+
const fetched = await prefetchJson(requests);
|
|
1650
|
+
for (const [key, value] of fetched) {
|
|
1651
|
+
goPkgPrefetch.set(key, value);
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1655
|
+
/**
|
|
1656
|
+
* Discard prefetched pkg.go.dev documents. Tests only.
|
|
1657
|
+
*/
|
|
1658
|
+
export function resetGoPkgPrefetch() {
|
|
1659
|
+
goPkgPrefetch = new Map();
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1662
|
+
/**
|
|
1663
|
+
* Method to get go pkg license from go.dev site.
|
|
1664
|
+
*
|
|
1665
|
+
* @param {Object} repoMetadata Repo metadata
|
|
1666
|
+
*/
|
|
1667
|
+
export async function getGoPkgLicense(repoMetadata) {
|
|
1668
|
+
const group = repoMetadata.group;
|
|
1669
|
+
const pkgUrl = `${getGoPkgUrl(repoMetadata)}?tab=licenses`;
|
|
1670
|
+
// Check the metadata cache first
|
|
1671
|
+
if (metadata_cache[pkgUrl]) {
|
|
1672
|
+
return metadata_cache[pkgUrl];
|
|
1673
|
+
}
|
|
1674
|
+
try {
|
|
1675
|
+
const res =
|
|
1676
|
+
prefetchedResponse(goPkgPrefetch, pkgUrl) ||
|
|
1677
|
+
(await cdxgenAgent.get(pkgUrl));
|
|
1678
|
+
if (res?.body) {
|
|
1679
|
+
const licenses = extractLicenseText(res.body);
|
|
1680
|
+
const licenseIds = licenses.split(", ");
|
|
1681
|
+
const licList = [];
|
|
1682
|
+
for (const id of licenseIds) {
|
|
1683
|
+
if (id.trim().length) {
|
|
1684
|
+
const alicense = {};
|
|
1685
|
+
if (id.includes(" ")) {
|
|
1686
|
+
// go.dev separates a package's licences by newline. The first break
|
|
1687
|
+
// ends the licence name, every later one joins another licence.
|
|
1688
|
+
let breakCount = 0;
|
|
1689
|
+
alicense.name = id
|
|
1690
|
+
.trim()
|
|
1691
|
+
.replaceAll(/ {2}/g, "")
|
|
1692
|
+
.replaceAll("\n", () => (breakCount++ === 0 ? " " : " OR "));
|
|
1693
|
+
} else {
|
|
1694
|
+
alicense.id = id.trim();
|
|
1695
|
+
}
|
|
1696
|
+
alicense["url"] = pkgUrl;
|
|
1697
|
+
licList.push(alicense);
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
metadata_cache[pkgUrl] = licList;
|
|
1701
|
+
return licList;
|
|
1702
|
+
}
|
|
1703
|
+
} catch (_err) {
|
|
1704
|
+
return undefined;
|
|
1705
|
+
}
|
|
1706
|
+
if (modulePathHost(group, "github.com")) {
|
|
1707
|
+
return await getRepoLicense(undefined, repoMetadata);
|
|
1708
|
+
}
|
|
1709
|
+
return undefined;
|
|
1710
|
+
}
|
|
1711
|
+
|
|
1712
|
+
/**
|
|
1713
|
+
* Method to get go pkg vcs url from go.dev site.
|
|
1714
|
+
*
|
|
1715
|
+
* @param {String} group Package group
|
|
1716
|
+
* @param {String} name Package name
|
|
1717
|
+
*/
|
|
1718
|
+
export async function getGoPkgVCSUrl(group, name) {
|
|
1719
|
+
const fullName = getGoPkgFullName(group, name);
|
|
1720
|
+
if (isDirectlyResolvableGoVCS({ group, name })) {
|
|
1721
|
+
return `https://${fullName}`;
|
|
1722
|
+
}
|
|
1723
|
+
const pkgUrl = getGoPkgUrl({ fullName });
|
|
1724
|
+
if (metadata_cache[pkgUrl]) {
|
|
1725
|
+
return metadata_cache[pkgUrl];
|
|
1726
|
+
}
|
|
1727
|
+
try {
|
|
1728
|
+
const res =
|
|
1729
|
+
prefetchedResponse(goPkgPrefetch, pkgUrl) ||
|
|
1730
|
+
(await cdxgenAgent.get(pkgUrl));
|
|
1731
|
+
if (res?.body) {
|
|
1732
|
+
const vcs = extractRepoUrl(res.body);
|
|
1733
|
+
metadata_cache[pkgUrl] = vcs;
|
|
1734
|
+
return vcs;
|
|
1735
|
+
}
|
|
1736
|
+
} catch (_err) {
|
|
1737
|
+
return undefined;
|
|
1738
|
+
}
|
|
1739
|
+
return undefined;
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
/**
|
|
1743
|
+
* Whether a module's repository URL follows from its path alone, needing no
|
|
1744
|
+
* pkg.go.dev lookup.
|
|
1745
|
+
*
|
|
1746
|
+
* @param {Object} pkgMetadata pkg metadata with `group` and/or `name`
|
|
1747
|
+
* @returns {boolean} True for github.com and gitlab.com hosted modules
|
|
1748
|
+
*/
|
|
1749
|
+
function isDirectlyResolvableGoVCS(pkgMetadata) {
|
|
1750
|
+
const fullName =
|
|
1751
|
+
pkgMetadata.fullName ||
|
|
1752
|
+
getGoPkgFullName(pkgMetadata.group, pkgMetadata.name);
|
|
1753
|
+
return (
|
|
1754
|
+
modulePathHost(fullName, "github.com") ||
|
|
1755
|
+
modulePathHost(fullName, "gitlab.com")
|
|
1756
|
+
);
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1759
|
+
/**
|
|
1760
|
+
* Method to get go pkg url (go.dev site).
|
|
1761
|
+
*
|
|
1762
|
+
* @param {Object} pkgMetadata pkg metadata
|
|
1763
|
+
*/
|
|
1764
|
+
function getGoPkgUrl(pkgMetadata) {
|
|
1765
|
+
const pkgUrlPrefix =
|
|
1766
|
+
readEnvironmentVariable("GO_PKG_URL") || "https://pkg.go.dev/";
|
|
1767
|
+
const fullName =
|
|
1768
|
+
pkgMetadata.fullName ||
|
|
1769
|
+
getGoPkgFullName(pkgMetadata.group, pkgMetadata.name);
|
|
1770
|
+
return pkgUrlPrefix + fullName;
|
|
1771
|
+
}
|
|
1772
|
+
|
|
1773
|
+
/**
|
|
1774
|
+
* Method to get go pkg full name.
|
|
1775
|
+
*
|
|
1776
|
+
* @param {String} group Package group
|
|
1777
|
+
* @param {String} name Package name
|
|
1778
|
+
*/
|
|
1779
|
+
function getGoPkgFullName(group, name) {
|
|
1780
|
+
return group && group !== "." && group !== name ? `${group}/${name}` : name;
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1783
|
+
/**
|
|
1784
|
+
* Method to retrieve metadata for rust packages by querying crates
|
|
1785
|
+
*
|
|
1786
|
+
* @param {Array} pkgList Package list
|
|
1787
|
+
*/
|
|
1788
|
+
export async function getCratesMetadata(pkgList) {
|
|
1789
|
+
const CRATES_URL =
|
|
1790
|
+
readEnvironmentVariable("RUST_CRATES_URL") ||
|
|
1791
|
+
"https://crates.io/api/v1/crates/";
|
|
1792
|
+
const cdepList = [];
|
|
1793
|
+
// Two URLs per crate — the crate document and its owners — fetched for the
|
|
1794
|
+
// whole list at once. The `workspace` guard below is mirrored here so the
|
|
1795
|
+
// batch does not request anything the loop would have skipped.
|
|
1796
|
+
const prefetched = await prefetchJson(
|
|
1797
|
+
prefetchEnabled()
|
|
1798
|
+
? pkgList
|
|
1799
|
+
.filter((p) => p?.name && p?.version && p.version !== "workspace")
|
|
1800
|
+
.flatMap((p) => [
|
|
1801
|
+
{ url: CRATES_URL + p.name },
|
|
1802
|
+
{ url: `${CRATES_URL + p.name}/owners` },
|
|
1803
|
+
])
|
|
1804
|
+
: [],
|
|
1805
|
+
);
|
|
1806
|
+
for (const p of pkgList) {
|
|
1807
|
+
try {
|
|
1808
|
+
if (!p?.name || !p?.version || p.version === "workspace") {
|
|
1809
|
+
cdepList.push(p);
|
|
1810
|
+
continue;
|
|
1811
|
+
}
|
|
1812
|
+
if (DEBUG_MODE) {
|
|
1813
|
+
console.log(`Querying crates.io for ${p.name}@${p.version}`);
|
|
1814
|
+
}
|
|
1815
|
+
const crateUrl = CRATES_URL + p.name;
|
|
1816
|
+
const res =
|
|
1817
|
+
prefetchedResponse(prefetched, crateUrl) ||
|
|
1818
|
+
(await cdxgenAgent.get(crateUrl, {
|
|
1819
|
+
responseType: "json",
|
|
1820
|
+
}));
|
|
1821
|
+
let ownersRes;
|
|
1822
|
+
try {
|
|
1823
|
+
const ownersUrl = `${crateUrl}/owners`;
|
|
1824
|
+
ownersRes =
|
|
1825
|
+
prefetchedResponse(prefetched, ownersUrl) ||
|
|
1826
|
+
(await cdxgenAgent.get(ownersUrl, {
|
|
1827
|
+
responseType: "json",
|
|
1828
|
+
}));
|
|
1829
|
+
} catch (_err) {
|
|
1830
|
+
ownersRes = undefined;
|
|
1831
|
+
}
|
|
1832
|
+
let versionToUse = res?.body?.versions[0];
|
|
1833
|
+
if (p.version) {
|
|
1834
|
+
for (const aversion of res.body.versions) {
|
|
1835
|
+
if (aversion.num === p.version) {
|
|
1836
|
+
versionToUse = aversion;
|
|
1837
|
+
break;
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1841
|
+
const body = res.body.crate;
|
|
1842
|
+
p.description = body.description;
|
|
1843
|
+
if (versionToUse?.license) {
|
|
1844
|
+
p.license = versionToUse.license;
|
|
1845
|
+
}
|
|
1846
|
+
if (body.repository) {
|
|
1847
|
+
p.repository = { url: body.repository };
|
|
1848
|
+
}
|
|
1849
|
+
if (body.homepage && body.homepage !== body.repository) {
|
|
1850
|
+
p.homepage = { url: body.homepage };
|
|
1851
|
+
}
|
|
1852
|
+
// Use the latest version if none specified
|
|
1853
|
+
if (!p.version) {
|
|
1854
|
+
p.version = body.newest_version;
|
|
1855
|
+
}
|
|
1856
|
+
if (!p._integrity && versionToUse.checksum) {
|
|
1857
|
+
p._integrity = normalizeCargoIntegrity(versionToUse.checksum);
|
|
1858
|
+
}
|
|
1859
|
+
if (!p.properties) {
|
|
1860
|
+
p.properties = [];
|
|
1861
|
+
}
|
|
1862
|
+
p.properties.push({
|
|
1863
|
+
name: "cdx:cargo:crate_id",
|
|
1864
|
+
value: `${versionToUse.id}`,
|
|
1865
|
+
});
|
|
1866
|
+
if (versionToUse.rust_version) {
|
|
1867
|
+
p.properties.push({
|
|
1868
|
+
name: "cdx:cargo:rust_version",
|
|
1869
|
+
value: `${versionToUse.rust_version}`,
|
|
1870
|
+
});
|
|
1871
|
+
}
|
|
1872
|
+
p.properties.push({
|
|
1873
|
+
name: "cdx:cargo:latest_version",
|
|
1874
|
+
value: body.newest_version,
|
|
1875
|
+
});
|
|
1876
|
+
p.distribution = { url: `https://crates.io${versionToUse.dl_path}` };
|
|
1877
|
+
if (versionToUse.features && Object.keys(versionToUse.features).length) {
|
|
1878
|
+
p.properties.push({
|
|
1879
|
+
name: "cdx:cargo:features",
|
|
1880
|
+
value: JSON.stringify(versionToUse.features),
|
|
1881
|
+
});
|
|
1882
|
+
}
|
|
1883
|
+
p.properties = p.properties.concat(
|
|
1884
|
+
collectCargoRegistryProvenanceProperties(
|
|
1885
|
+
res?.body,
|
|
1886
|
+
versionToUse?.num || p.version,
|
|
1887
|
+
ownersRes?.body,
|
|
1888
|
+
),
|
|
1889
|
+
);
|
|
1890
|
+
cdepList.push(p);
|
|
1891
|
+
} catch (_err) {
|
|
1892
|
+
cdepList.push(p);
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1895
|
+
return cdepList;
|
|
1896
|
+
}
|
|
1897
|
+
|
|
1898
|
+
/**
|
|
1899
|
+
* Method to retrieve metadata for dart packages by querying pub.dev
|
|
1900
|
+
*
|
|
1901
|
+
* @param {Array} pkgList Package list
|
|
1902
|
+
*/
|
|
1903
|
+
export async function getDartMetadata(pkgList) {
|
|
1904
|
+
const PUB_DEV_URL =
|
|
1905
|
+
readEnvironmentVariable("PUB_DEV_URL") || "https://pub.dev";
|
|
1906
|
+
const PUB_LICENSE_REGEX = /^license:/i;
|
|
1907
|
+
const OPTIONS = {
|
|
1908
|
+
responseType: "json",
|
|
1909
|
+
headers: {
|
|
1910
|
+
Accept: PUB_ACCEPT,
|
|
1911
|
+
},
|
|
1912
|
+
};
|
|
1913
|
+
|
|
1914
|
+
const cdepList = [];
|
|
1915
|
+
// pub.dev needs the package document and its score document; both carry the
|
|
1916
|
+
// vendor Accept header, which is part of the cache key on the Rust side.
|
|
1917
|
+
const prefetched = await prefetchJson(
|
|
1918
|
+
prefetchEnabled()
|
|
1919
|
+
? pkgList.flatMap((p) => {
|
|
1920
|
+
const base = `${PUB_DEV_URL}/api/packages/${p.name}/versions/${p.version}`;
|
|
1921
|
+
return [
|
|
1922
|
+
{ url: base, accept: PUB_ACCEPT },
|
|
1923
|
+
{ url: `${base}/score`, accept: PUB_ACCEPT },
|
|
1924
|
+
];
|
|
1925
|
+
})
|
|
1926
|
+
: [],
|
|
1927
|
+
);
|
|
1928
|
+
for (const p of pkgList) {
|
|
1929
|
+
try {
|
|
1930
|
+
if (DEBUG_MODE) {
|
|
1931
|
+
console.log(`Querying ${PUB_DEV_URL} for ${p.name}`);
|
|
1932
|
+
}
|
|
1933
|
+
const PUB_PACKAGE_URL = `${PUB_DEV_URL}/api/packages/${p.name}/versions/${p.version}`;
|
|
1934
|
+
const PUB_PACKAGE_SCORE_URL = `${PUB_PACKAGE_URL}/score`;
|
|
1935
|
+
const res =
|
|
1936
|
+
prefetchedResponse(prefetched, PUB_PACKAGE_URL) ||
|
|
1937
|
+
(await cdxgenAgent.get(PUB_PACKAGE_URL, OPTIONS));
|
|
1938
|
+
if (res?.body) {
|
|
1939
|
+
const pubspec = res.body.pubspec;
|
|
1940
|
+
p.description = pubspec.description;
|
|
1941
|
+
if (pubspec.repository) {
|
|
1942
|
+
p.repository = { url: pubspec.repository };
|
|
1943
|
+
}
|
|
1944
|
+
if (pubspec.homepage) {
|
|
1945
|
+
p.homepage = { url: pubspec.homepage };
|
|
1946
|
+
}
|
|
1947
|
+
const score =
|
|
1948
|
+
prefetchedResponse(prefetched, PUB_PACKAGE_SCORE_URL) ||
|
|
1949
|
+
(await cdxgenAgent.get(PUB_PACKAGE_SCORE_URL, OPTIONS));
|
|
1950
|
+
if (score?.body) {
|
|
1951
|
+
const tags = score.body.tags;
|
|
1952
|
+
const license = tags.find((tag) => PUB_LICENSE_REGEX.test(tag));
|
|
1953
|
+
if (license) {
|
|
1954
|
+
p.license = spdxLicenses.find(
|
|
1955
|
+
(spdxLicense) =>
|
|
1956
|
+
spdxLicense.toLowerCase() ===
|
|
1957
|
+
license.replace(PUB_LICENSE_REGEX, "").toLowerCase(),
|
|
1958
|
+
);
|
|
1959
|
+
}
|
|
1960
|
+
}
|
|
1961
|
+
cdepList.push(p);
|
|
1962
|
+
}
|
|
1963
|
+
} catch (_err) {
|
|
1964
|
+
cdepList.push(p);
|
|
1965
|
+
}
|
|
1966
|
+
}
|
|
1967
|
+
return cdepList;
|
|
1968
|
+
}
|
|
1969
|
+
|
|
1970
|
+
/**
|
|
1971
|
+
* Normalize a Cargo checksum/integrity string into canonical hex-prefixed form.
|
|
1972
|
+
*
|
|
1973
|
+
* Accepts an existing `sha256-`/`sha384-` prefixed digest (validating the hex
|
|
1974
|
+
* length) or a bare hex digest, returning `<algo>-<digest>`. Returns undefined
|
|
1975
|
+
* for non-string or unrecognized inputs.
|
|
1976
|
+
*
|
|
1977
|
+
* @param {string} integrity Raw checksum string from a Cargo.lock or registry.
|
|
1978
|
+
* @returns {string|undefined} Canonical `algo-digest` integrity, or undefined.
|
|
1979
|
+
*/
|
|
1980
|
+
export function normalizeCargoIntegrity(integrity) {
|
|
1981
|
+
if (typeof integrity !== "string") {
|
|
1982
|
+
return undefined;
|
|
1983
|
+
}
|
|
1984
|
+
const normalizedIntegrity = integrity.trim().toLowerCase();
|
|
1985
|
+
const prefixedMatch = /^(sha256|sha384)-(?<digest>[a-f0-9]+)$/i.exec(
|
|
1986
|
+
normalizedIntegrity,
|
|
1987
|
+
);
|
|
1988
|
+
if (prefixedMatch?.groups?.digest) {
|
|
1989
|
+
const algorithm = prefixedMatch[1].toLowerCase();
|
|
1990
|
+
const digest = prefixedMatch.groups.digest;
|
|
1991
|
+
const expectedDigestLength = algorithm === "sha384" ? 96 : 64;
|
|
1992
|
+
if (digest.length === expectedDigestLength) {
|
|
1993
|
+
return `${algorithm}-${digest}`;
|
|
1994
|
+
}
|
|
1995
|
+
return undefined;
|
|
1996
|
+
}
|
|
1997
|
+
if (!/^[a-f0-9]+$/i.test(normalizedIntegrity)) {
|
|
1998
|
+
return undefined;
|
|
1999
|
+
}
|
|
2000
|
+
if (normalizedIntegrity.length === 64) {
|
|
2001
|
+
return `sha256-${normalizedIntegrity}`;
|
|
2002
|
+
}
|
|
2003
|
+
if (normalizedIntegrity.length === 96) {
|
|
2004
|
+
return `sha384-${normalizedIntegrity}`;
|
|
2005
|
+
}
|
|
2006
|
+
return undefined;
|
|
2007
|
+
}
|
|
2008
|
+
|
|
2009
|
+
/**
|
|
2010
|
+
* Method to extract a war or ear file
|
|
2011
|
+
*
|
|
2012
|
+
* @param {string} jarFile Path to jar file
|
|
2013
|
+
* @param {string} tempDir Temporary directory to use for extraction
|
|
2014
|
+
* @param {object} jarNSMapping Jar class names mapping object
|
|
2015
|
+
*
|
|
2016
|
+
* @return pkgList Package list
|
|
2017
|
+
*/
|
|
2018
|
+
export async function extractJarArchive(jarFile, tempDir, jarNSMapping = {}) {
|
|
2019
|
+
const pkgList = [];
|
|
2020
|
+
let jarFiles = [];
|
|
2021
|
+
// Archives whose Maven coordinates could not be determined. They are still
|
|
2022
|
+
// reported, as file components with zero-confidence identity evidence.
|
|
2023
|
+
const unresolvedJars = [];
|
|
2024
|
+
const fname = basename(jarFile);
|
|
2025
|
+
let pomname;
|
|
2026
|
+
// If there is a pom file in the same directory, try to use it
|
|
2027
|
+
const manifestname = join(dirname(jarFile), "META-INF", "MANIFEST.MF");
|
|
2028
|
+
// Issue 439: Current implementation checks for existance of a .pom file, but .pom file is not used.
|
|
2029
|
+
// Instead code expects to find META-INF/MANIFEST.MF in the same folder as a .jar file.
|
|
2030
|
+
// For now check for presence of both .pom and MANIFEST.MF files.
|
|
2031
|
+
if (jarFile.endsWith(".jar")) {
|
|
2032
|
+
pomname = jarFile.replace(".jar", ".pom");
|
|
2033
|
+
}
|
|
2034
|
+
if (
|
|
2035
|
+
pomname &&
|
|
2036
|
+
safeExistsSync(pomname) &&
|
|
2037
|
+
manifestname &&
|
|
2038
|
+
safeExistsSync(manifestname)
|
|
2039
|
+
) {
|
|
2040
|
+
tempDir = dirname(jarFile);
|
|
2041
|
+
} else if (
|
|
2042
|
+
!safeExistsSync(join(tempDir, fname)) &&
|
|
2043
|
+
safeExistsSync(jarFile) &&
|
|
2044
|
+
lstatSync(jarFile).isFile()
|
|
2045
|
+
) {
|
|
2046
|
+
// Only copy if the file doesn't exist
|
|
2047
|
+
safeCopyFileSync(jarFile, join(tempDir, fname), constants.COPYFILE_FICLONE);
|
|
2048
|
+
}
|
|
2049
|
+
const env = {
|
|
2050
|
+
...process.env,
|
|
2051
|
+
};
|
|
2052
|
+
// jar command usually would not be available in the PATH for windows
|
|
2053
|
+
if (isWin && env.JAVA_HOME) {
|
|
2054
|
+
env.PATH = `${env.PATH || env.Path}${_delimiter}${join(
|
|
2055
|
+
env.JAVA_HOME,
|
|
2056
|
+
"bin",
|
|
2057
|
+
)}`;
|
|
2058
|
+
}
|
|
2059
|
+
if (
|
|
2060
|
+
jarFile.endsWith(".war") ||
|
|
2061
|
+
jarFile.endsWith(".hpi") ||
|
|
2062
|
+
jarFile.endsWith(".jar")
|
|
2063
|
+
) {
|
|
2064
|
+
if (safeExistsSync(join(tempDir, fname))) {
|
|
2065
|
+
try {
|
|
2066
|
+
const zip = new StreamZip.async({ file: join(tempDir, fname) });
|
|
2067
|
+
const extracted = await safeExtractArchive(
|
|
2068
|
+
join(tempDir, fname),
|
|
2069
|
+
tempDir,
|
|
2070
|
+
async () => {
|
|
2071
|
+
await zip.extract(null, tempDir);
|
|
2072
|
+
},
|
|
2073
|
+
);
|
|
2074
|
+
await zip.close();
|
|
2075
|
+
if (!extracted) {
|
|
2076
|
+
return pkgList;
|
|
2077
|
+
}
|
|
2078
|
+
} catch (e) {
|
|
2079
|
+
console.log("Unable to extract %s. Skipping.", join(tempDir, fname), e);
|
|
2080
|
+
return pkgList;
|
|
2081
|
+
}
|
|
2082
|
+
}
|
|
2083
|
+
jarFiles = getAllFiles(join(tempDir, "WEB-INF", "lib"), "**/*.jar");
|
|
2084
|
+
if (jarFile.endsWith(".hpi")) {
|
|
2085
|
+
jarFiles.push(jarFile);
|
|
2086
|
+
}
|
|
2087
|
+
// Some jar files could also have more jar files inside BOOT-INF directory
|
|
2088
|
+
const jarFiles2 = getAllFiles(join(tempDir, "BOOT-INF", "lib"), "**/*.jar");
|
|
2089
|
+
if (jarFiles && jarFiles2.length) {
|
|
2090
|
+
jarFiles = jarFiles.concat(jarFiles2);
|
|
2091
|
+
}
|
|
2092
|
+
// Fallback. If our jar file didn't include any jar
|
|
2093
|
+
if (jarFile.endsWith(".jar") && !jarFiles.length) {
|
|
2094
|
+
jarFiles = [join(tempDir, fname)];
|
|
2095
|
+
}
|
|
2096
|
+
} else {
|
|
2097
|
+
jarFiles = [join(tempDir, fname)];
|
|
2098
|
+
}
|
|
2099
|
+
if (jarFiles?.length) {
|
|
2100
|
+
for (const jf of jarFiles) {
|
|
2101
|
+
// If the jar file doesn't exist at the point of use, skip it
|
|
2102
|
+
if (!safeExistsSync(jf)) {
|
|
2103
|
+
if (DEBUG_MODE) {
|
|
2104
|
+
console.log("%s %s is not a readable file.", jf, jarFile);
|
|
2105
|
+
}
|
|
2106
|
+
continue;
|
|
2107
|
+
}
|
|
2108
|
+
pomname = jf.replace(".jar", ".pom");
|
|
2109
|
+
const jarname = basename(jf);
|
|
2110
|
+
// Ignore test jars
|
|
2111
|
+
if (
|
|
2112
|
+
jarname.endsWith("-tests.jar") ||
|
|
2113
|
+
jarname.endsWith("-test-sources.jar")
|
|
2114
|
+
) {
|
|
2115
|
+
if (DEBUG_MODE) {
|
|
2116
|
+
console.log(`Skipping tests jar ${jarname}`);
|
|
2117
|
+
}
|
|
2118
|
+
continue;
|
|
2119
|
+
}
|
|
2120
|
+
const manifestDir = join(tempDir, "META-INF");
|
|
2121
|
+
const manifestFile = join(manifestDir, "MANIFEST.MF");
|
|
2122
|
+
const mavenDir = join(manifestDir, "maven");
|
|
2123
|
+
let jarResult = {
|
|
2124
|
+
status: 1,
|
|
2125
|
+
};
|
|
2126
|
+
if (safeExistsSync(pomname)) {
|
|
2127
|
+
jarResult = { status: 0 };
|
|
2128
|
+
} else {
|
|
2129
|
+
// Unzip natively
|
|
2130
|
+
try {
|
|
2131
|
+
const zip = new StreamZip.async({ file: jf });
|
|
2132
|
+
const extracted = await safeExtractArchive(jf, tempDir, async () => {
|
|
2133
|
+
await zip.extract(null, tempDir);
|
|
2134
|
+
});
|
|
2135
|
+
await zip.close();
|
|
2136
|
+
jarResult = { status: extracted ? 0 : 1 };
|
|
2137
|
+
} catch (_e) {
|
|
2138
|
+
if (DEBUG_MODE) {
|
|
2139
|
+
console.log(`Unable to extract ${jf}. Skipping.`);
|
|
2140
|
+
}
|
|
2141
|
+
jarResult = { status: 1 };
|
|
2142
|
+
}
|
|
2143
|
+
}
|
|
2144
|
+
if (jarResult.status === 0) {
|
|
2145
|
+
// When maven descriptor is available take group, name and version from pom.properties
|
|
2146
|
+
// META-INF/maven/${groupId}/${artifactId}/pom.properties
|
|
2147
|
+
// see https://maven.apache.org/shared/maven-archiver/index.html
|
|
2148
|
+
const pomProperties = getPomPropertiesFromMavenDir(mavenDir);
|
|
2149
|
+
let group = pomProperties["groupId"];
|
|
2150
|
+
let name = pomProperties["artifactId"];
|
|
2151
|
+
let version = pomProperties["version"];
|
|
2152
|
+
let confidence = 0.5;
|
|
2153
|
+
let technique = "manifest-analysis";
|
|
2154
|
+
if (
|
|
2155
|
+
(!group || !name || !version) &&
|
|
2156
|
+
SEARCH_MAVEN_ORG &&
|
|
2157
|
+
search_maven_org_errors < MAX_SEARCH_MAVEN_ORG_ERRORS
|
|
2158
|
+
) {
|
|
2159
|
+
try {
|
|
2160
|
+
const sha = await checksumFile("sha1", jf);
|
|
2161
|
+
const searchurl = `https://central.sonatype.com/solrsearch/select?q=1:%22${sha}%22&rows=20&wt=json`;
|
|
2162
|
+
const res = await cdxgenAgent.get(searchurl, {
|
|
2163
|
+
responseType: "json",
|
|
2164
|
+
timeout: {
|
|
2165
|
+
lookup: 1000,
|
|
2166
|
+
connect: 5000,
|
|
2167
|
+
secureConnect: 5000,
|
|
2168
|
+
socket: 1000,
|
|
2169
|
+
send: 10000,
|
|
2170
|
+
response: 1000,
|
|
2171
|
+
},
|
|
2172
|
+
});
|
|
2173
|
+
const data = res?.body ? res.body["response"] : undefined;
|
|
2174
|
+
if (data && data["numFound"] === 1) {
|
|
2175
|
+
const jarInfo = data["docs"][0];
|
|
2176
|
+
group = jarInfo["g"];
|
|
2177
|
+
name = jarInfo["a"];
|
|
2178
|
+
version = jarInfo["v"];
|
|
2179
|
+
technique = "hash-comparison";
|
|
2180
|
+
}
|
|
2181
|
+
} catch (err) {
|
|
2182
|
+
if (err?.message && !err.message.includes("404")) {
|
|
2183
|
+
if (
|
|
2184
|
+
err.message.includes("Timeout") ||
|
|
2185
|
+
err.message.includes("429")
|
|
2186
|
+
) {
|
|
2187
|
+
console.log(
|
|
2188
|
+
"Maven search appears to be unavailable. Search will be skipped for all remaining packages.",
|
|
2189
|
+
);
|
|
2190
|
+
}
|
|
2191
|
+
search_maven_org_errors++;
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
}
|
|
2195
|
+
let jarMetadata;
|
|
2196
|
+
if ((!group || !name || !version) && safeExistsSync(manifestFile)) {
|
|
2197
|
+
confidence = 0.3;
|
|
2198
|
+
jarMetadata = parseJarManifest(
|
|
2199
|
+
readFileSync(manifestFile, {
|
|
2200
|
+
encoding: "utf-8",
|
|
2201
|
+
}),
|
|
2202
|
+
);
|
|
2203
|
+
if (jarMetadata["Bundle-SymbolicName"]) {
|
|
2204
|
+
jarMetadata["Bundle-SymbolicName"] = jarMetadata[
|
|
2205
|
+
"Bundle-SymbolicName"
|
|
2206
|
+
]
|
|
2207
|
+
.split(";")[0]
|
|
2208
|
+
.trim();
|
|
2209
|
+
}
|
|
2210
|
+
group = group || inferJarGroupFromManifest(jarMetadata);
|
|
2211
|
+
version =
|
|
2212
|
+
version ||
|
|
2213
|
+
jarMetadata["Bundle-Version"] ||
|
|
2214
|
+
jarMetadata["Implementation-Version"] ||
|
|
2215
|
+
jarMetadata["Specification-Version"];
|
|
2216
|
+
if (version?.includes(" ")) {
|
|
2217
|
+
version = version.split(" ")[0];
|
|
2218
|
+
}
|
|
2219
|
+
// Prefer jar filename to construct name and version
|
|
2220
|
+
const tmpA = jarname.split("-");
|
|
2221
|
+
let fileVersionCandidate;
|
|
2222
|
+
let nameCandidate;
|
|
2223
|
+
if (tmpA && tmpA.length > 1) {
|
|
2224
|
+
const lastPart = tmpA[tmpA.length - 1];
|
|
2225
|
+
// Bug #768. Check if we have any number before simplifying the name.
|
|
2226
|
+
if (/\d/.test(lastPart)) {
|
|
2227
|
+
fileVersionCandidate = lastPart.replace(".jar", "");
|
|
2228
|
+
nameCandidate = jarname.replace(`-${lastPart}`, "") || "";
|
|
2229
|
+
if (nameCandidate.includes(".")) {
|
|
2230
|
+
const gnArr = nameCandidate.split(".");
|
|
2231
|
+
if (gnArr?.length === 2 && gnArr[0] === gnArr[1]) {
|
|
2232
|
+
nameCandidate = gnArr[1];
|
|
2233
|
+
}
|
|
2234
|
+
}
|
|
2235
|
+
}
|
|
2236
|
+
}
|
|
2237
|
+
if (
|
|
2238
|
+
fileVersionCandidate &&
|
|
2239
|
+
(!version ||
|
|
2240
|
+
version === "" ||
|
|
2241
|
+
(version.includes(fileVersionCandidate) &&
|
|
2242
|
+
version.length > fileVersionCandidate.length))
|
|
2243
|
+
) {
|
|
2244
|
+
version = fileVersionCandidate;
|
|
2245
|
+
confidence = 0.3;
|
|
2246
|
+
technique = "filename";
|
|
2247
|
+
}
|
|
2248
|
+
if (!name || name === "") {
|
|
2249
|
+
name = nameCandidate;
|
|
2250
|
+
}
|
|
2251
|
+
if (
|
|
2252
|
+
!name?.length &&
|
|
2253
|
+
jarMetadata["Bundle-Name"] &&
|
|
2254
|
+
!jarMetadata["Bundle-Name"].includes(" ")
|
|
2255
|
+
) {
|
|
2256
|
+
name = jarMetadata["Bundle-Name"];
|
|
2257
|
+
} else if (
|
|
2258
|
+
!name?.length &&
|
|
2259
|
+
jarMetadata["Implementation-Title"] &&
|
|
2260
|
+
!jarMetadata["Implementation-Title"].includes(" ")
|
|
2261
|
+
) {
|
|
2262
|
+
name = jarMetadata["Implementation-Title"];
|
|
2263
|
+
}
|
|
2264
|
+
// Sometimes the group might already contain the name
|
|
2265
|
+
// Eg: group: org.checkerframework.checker.qual name: checker-qual
|
|
2266
|
+
group = trimJarGroupSuffix(group, name);
|
|
2267
|
+
// Patch the group string
|
|
2268
|
+
if (vendorAliases[name]) {
|
|
2269
|
+
group = vendorAliases[name];
|
|
2270
|
+
} else {
|
|
2271
|
+
for (const aprefix in vendorAliases) {
|
|
2272
|
+
if (name?.startsWith(aprefix) || name?.endsWith(`.${aprefix}`)) {
|
|
2273
|
+
group = vendorAliases[aprefix];
|
|
2274
|
+
if (name?.startsWith(`${group}.`)) {
|
|
2275
|
+
name = name.replace(`${group}.`, "");
|
|
2276
|
+
}
|
|
2277
|
+
break;
|
|
2278
|
+
}
|
|
2279
|
+
}
|
|
2280
|
+
}
|
|
2281
|
+
// if group is empty use name as group
|
|
2282
|
+
group = group === "." ? name : group || name;
|
|
2283
|
+
}
|
|
2284
|
+
if (name) {
|
|
2285
|
+
if (!version) {
|
|
2286
|
+
confidence = 0;
|
|
2287
|
+
}
|
|
2288
|
+
const properties = [
|
|
2289
|
+
{
|
|
2290
|
+
name: "internal:SrcFile",
|
|
2291
|
+
value: jf,
|
|
2292
|
+
},
|
|
2293
|
+
];
|
|
2294
|
+
const purl = build({
|
|
2295
|
+
type: "maven",
|
|
2296
|
+
namespace: group || null,
|
|
2297
|
+
name: name,
|
|
2298
|
+
version: version || null,
|
|
2299
|
+
qualifiers: { type: "jar" } || null,
|
|
2300
|
+
});
|
|
2301
|
+
let namespaceValues;
|
|
2302
|
+
let namespaceList;
|
|
2303
|
+
if (jarNSMapping?.[purl]?.namespaces) {
|
|
2304
|
+
namespaceList = jarNSMapping[purl].namespaces;
|
|
2305
|
+
namespaceValues = namespaceList.join("\n");
|
|
2306
|
+
properties.push({
|
|
2307
|
+
name: "internal:Namespaces",
|
|
2308
|
+
value: namespaceValues,
|
|
2309
|
+
});
|
|
2310
|
+
} else {
|
|
2311
|
+
const tmpJarNSMapping = await collectJarNS(jf);
|
|
2312
|
+
if (tmpJarNSMapping?.[jf]?.namespaces?.length) {
|
|
2313
|
+
namespaceList = tmpJarNSMapping[jf].namespaces;
|
|
2314
|
+
namespaceValues = namespaceList.join("\n");
|
|
2315
|
+
properties.push({
|
|
2316
|
+
name: "internal:Namespaces",
|
|
2317
|
+
value: namespaceValues,
|
|
2318
|
+
});
|
|
2319
|
+
}
|
|
2320
|
+
}
|
|
2321
|
+
// Are there any shaded classes
|
|
2322
|
+
if (
|
|
2323
|
+
namespaceValues?.includes(".shaded.") ||
|
|
2324
|
+
namespaceValues?.includes(".thirdparty.com.")
|
|
2325
|
+
) {
|
|
2326
|
+
properties.push({
|
|
2327
|
+
name: "cdx:maven:shaded",
|
|
2328
|
+
value: "true",
|
|
2329
|
+
});
|
|
2330
|
+
confidence = 0;
|
|
2331
|
+
const unshadedNS = new Set();
|
|
2332
|
+
for (const ans of namespaceList) {
|
|
2333
|
+
let tmpns;
|
|
2334
|
+
if (ans.includes(".shaded.")) {
|
|
2335
|
+
tmpns = ans.split(".shaded.").pop();
|
|
2336
|
+
} else if (ans.includes(".thirdparty.")) {
|
|
2337
|
+
tmpns = ans.split(".thirdparty.").pop();
|
|
2338
|
+
}
|
|
2339
|
+
if (tmpns?.search("[.]") > 3) {
|
|
2340
|
+
unshadedNS.add(tmpns.split("$")[0]);
|
|
2341
|
+
}
|
|
2342
|
+
}
|
|
2343
|
+
if (unshadedNS.size) {
|
|
2344
|
+
properties.push({
|
|
2345
|
+
name: "cdx:maven:unshadedNamespaces",
|
|
2346
|
+
value: Array.from(unshadedNS).join("\n"),
|
|
2347
|
+
});
|
|
2348
|
+
}
|
|
2349
|
+
}
|
|
2350
|
+
const apkg = {
|
|
2351
|
+
group: group ? encodeForPurl(group) : "",
|
|
2352
|
+
name: name ? encodeForPurl(name) : "",
|
|
2353
|
+
version,
|
|
2354
|
+
purl,
|
|
2355
|
+
evidence: {
|
|
2356
|
+
identity: {
|
|
2357
|
+
field: "purl",
|
|
2358
|
+
confidence: confidence,
|
|
2359
|
+
methods: [
|
|
2360
|
+
{
|
|
2361
|
+
technique: technique,
|
|
2362
|
+
confidence: confidence,
|
|
2363
|
+
value: jarname,
|
|
2364
|
+
},
|
|
2365
|
+
],
|
|
2366
|
+
},
|
|
2367
|
+
},
|
|
2368
|
+
properties,
|
|
2369
|
+
};
|
|
2370
|
+
pkgList.push(apkg);
|
|
2371
|
+
} else {
|
|
2372
|
+
unresolvedJars.push(jf);
|
|
2373
|
+
if (DEBUG_MODE) {
|
|
2374
|
+
console.log("Ignored jar %s", jarname, name, version);
|
|
2375
|
+
}
|
|
2376
|
+
}
|
|
2377
|
+
}
|
|
2378
|
+
try {
|
|
2379
|
+
if (safeExistsSync(join(tempDir, "META-INF"))) {
|
|
2380
|
+
// Clean up META-INF
|
|
2381
|
+
safeRmSync(join(tempDir, "META-INF"), {
|
|
2382
|
+
recursive: true,
|
|
2383
|
+
force: true,
|
|
2384
|
+
});
|
|
2385
|
+
}
|
|
2386
|
+
} catch (_err) {
|
|
2387
|
+
// ignore cleanup errors
|
|
2388
|
+
}
|
|
2389
|
+
} // for
|
|
2390
|
+
} // if
|
|
2391
|
+
if (unresolvedJars.length) {
|
|
2392
|
+
// An archive without readable Maven coordinates is still a real file in the
|
|
2393
|
+
// artifact. Reporting it as a file component keeps it in the inventory,
|
|
2394
|
+
// while zero-confidence identity evidence records that only its name and
|
|
2395
|
+
// hashes are known — the same treatment unpackaged executables and shared
|
|
2396
|
+
// libraries get.
|
|
2397
|
+
pkgList.push(
|
|
2398
|
+
...(await unresolvedJarComponents(unresolvedJars, jarFile, tempDir)),
|
|
2399
|
+
);
|
|
2400
|
+
thoughtLog(
|
|
2401
|
+
`I couldn't read Maven coordinates for ${unresolvedJars.length} archive(s) under ${basename(jarFile)}, so I recorded them as files with their hashes instead of dropping them.`,
|
|
2402
|
+
);
|
|
2403
|
+
}
|
|
2404
|
+
return pkgList;
|
|
2405
|
+
}
|
|
2406
|
+
|
|
2407
|
+
/**
|
|
2408
|
+
* Build file components for archives whose Maven coordinates could not be
|
|
2409
|
+
* determined.
|
|
2410
|
+
*
|
|
2411
|
+
* @param {string[]} unresolvedJars Paths of the archives on disk
|
|
2412
|
+
* @param {string} jarFile The archive originally passed for extraction
|
|
2413
|
+
* @param {string} tempDir Directory the archive was expanded into
|
|
2414
|
+
* @returns {Promise<object[]>} File components with zero-confidence identity
|
|
2415
|
+
*/
|
|
2416
|
+
async function unresolvedJarComponents(unresolvedJars, jarFile, tempDir) {
|
|
2417
|
+
const hashResults = await mapWithConcurrency(unresolvedJars, (jf) =>
|
|
2418
|
+
multiChecksumFile(["md5", "sha1", "sha256"], jf)
|
|
2419
|
+
.then((hashValues) => [
|
|
2420
|
+
{ alg: "MD5", content: hashValues.md5 },
|
|
2421
|
+
{ alg: "SHA-1", content: hashValues.sha1 },
|
|
2422
|
+
{ alg: "SHA-256", content: hashValues.sha256 },
|
|
2423
|
+
])
|
|
2424
|
+
.catch(() => undefined),
|
|
2425
|
+
);
|
|
2426
|
+
const components = [];
|
|
2427
|
+
for (let i = 0; i < unresolvedJars.length; i++) {
|
|
2428
|
+
const jf = unresolvedJars[i];
|
|
2429
|
+
const name = basename(jf);
|
|
2430
|
+
// A nested archive is identified by its entry path within the outer one,
|
|
2431
|
+
// which is what a reader can actually go and look at. The temp directory
|
|
2432
|
+
// the archive happened to be expanded into carries no information.
|
|
2433
|
+
const relativeEntry = relative(tempDir, jf);
|
|
2434
|
+
const srcFile =
|
|
2435
|
+
relativeEntry && !relativeEntry.startsWith("..") && relativeEntry !== name
|
|
2436
|
+
? `${jarFile}!/${relativeEntry.split(_sep).join("/")}`
|
|
2437
|
+
: jarFile;
|
|
2438
|
+
// The purl carries the name only. The archive is reached through a
|
|
2439
|
+
// temporary extraction directory whose name changes every run, and postgen
|
|
2440
|
+
// rewrites `internal:SrcFile` and `concludedValue` to scan-relative paths
|
|
2441
|
+
// but never a purl, since purls are join keys. Putting the location in the
|
|
2442
|
+
// purl would make it differ between two runs over the same input.
|
|
2443
|
+
const purl = genericPurl(name);
|
|
2444
|
+
components.push({
|
|
2445
|
+
name,
|
|
2446
|
+
type: "file",
|
|
2447
|
+
...(purl ? { purl, "bom-ref": purl } : {}),
|
|
2448
|
+
hashes: hashResults[i],
|
|
2449
|
+
properties: [
|
|
2450
|
+
{ name: "internal:SrcFile", value: srcFile },
|
|
2451
|
+
{ name: "internal:is_file", value: "true" },
|
|
2452
|
+
],
|
|
2453
|
+
evidence: {
|
|
2454
|
+
identity: [
|
|
2455
|
+
{
|
|
2456
|
+
field: "purl",
|
|
2457
|
+
confidence: 0,
|
|
2458
|
+
methods: [
|
|
2459
|
+
{
|
|
2460
|
+
technique: "filename",
|
|
2461
|
+
confidence: 0,
|
|
2462
|
+
value: srcFile,
|
|
2463
|
+
},
|
|
2464
|
+
],
|
|
2465
|
+
concludedValue: srcFile,
|
|
2466
|
+
},
|
|
2467
|
+
],
|
|
2468
|
+
},
|
|
2469
|
+
});
|
|
2470
|
+
}
|
|
2471
|
+
return components;
|
|
2472
|
+
}
|
|
2473
|
+
|
|
2474
|
+
async function getNugetUrl() {
|
|
2475
|
+
const req = "https://api.nuget.org/v3/index.json";
|
|
2476
|
+
const res = await cdxgenAgent.get(req, {
|
|
2477
|
+
responseType: "json",
|
|
2478
|
+
});
|
|
2479
|
+
const urls = res.body.resources;
|
|
2480
|
+
for (const resource of urls) {
|
|
2481
|
+
if (resource["@type"] === "RegistrationsBaseUrl/3.6.0") {
|
|
2482
|
+
return resource["@id"];
|
|
2483
|
+
}
|
|
2484
|
+
}
|
|
2485
|
+
return "https://api.nuget.org/v3/registration3/";
|
|
2486
|
+
}
|
|
2487
|
+
|
|
2488
|
+
/**
|
|
2489
|
+
* Prefetched NuGet registration index documents, populated by
|
|
2490
|
+
* `getNugetMetadata`'s batch round. The index is the first request
|
|
2491
|
+
* `queryNuget` makes for each package; batching it removes the serial round
|
|
2492
|
+
* trip per package. The follow-up request to a specific registration page
|
|
2493
|
+
* (when the index does not inline items) depends on the index response and
|
|
2494
|
+
* stays serial.
|
|
2495
|
+
*/
|
|
2496
|
+
const prefetchedNugetIndex = new Map();
|
|
2497
|
+
|
|
2498
|
+
async function queryNuget(p, NUGET_URL) {
|
|
2499
|
+
function setLatestVersion(upper) {
|
|
2500
|
+
// Handle special case for versions with more than 3 parts
|
|
2501
|
+
if (upper.split(".").length > 3) {
|
|
2502
|
+
const tmpVersionArray = upper.split("-")[0].split(".");
|
|
2503
|
+
// Compromise for versions such as 1.2.3.0-alpha
|
|
2504
|
+
// How to find latest proper release version?
|
|
2505
|
+
if (
|
|
2506
|
+
upper.split("-").length > 1 &&
|
|
2507
|
+
Number(tmpVersionArray.slice(-1)) === 0
|
|
2508
|
+
) {
|
|
2509
|
+
return upper;
|
|
2510
|
+
}
|
|
2511
|
+
if (upper.split("-").length > 1) {
|
|
2512
|
+
tmpVersionArray[tmpVersionArray.length - 1] = (
|
|
2513
|
+
Number(tmpVersionArray.slice(-1)) - 1
|
|
2514
|
+
).toString();
|
|
2515
|
+
}
|
|
2516
|
+
return tmpVersionArray.join(".");
|
|
2517
|
+
}
|
|
2518
|
+
const tmpVersion = parse(upper);
|
|
2519
|
+
let version = `${tmpVersion.major}.${tmpVersion.minor}.${tmpVersion.patch}`;
|
|
2520
|
+
if (compare(version, upper) === 1) {
|
|
2521
|
+
if (tmpVersion.patch > 0) {
|
|
2522
|
+
version = `${tmpVersion.major}.${tmpVersion.minor}.${(tmpVersion.patch - 1).toString()}`;
|
|
2523
|
+
}
|
|
2524
|
+
}
|
|
2525
|
+
return version;
|
|
2526
|
+
}
|
|
2527
|
+
|
|
2528
|
+
// Coerce only when missing patch/minor version
|
|
2529
|
+
function coerceUp(version) {
|
|
2530
|
+
return version.split(".").length < 3
|
|
2531
|
+
? coerce(version, { loose: true }).version
|
|
2532
|
+
: version;
|
|
2533
|
+
}
|
|
2534
|
+
|
|
2535
|
+
if (DEBUG_MODE) {
|
|
2536
|
+
console.log(`Querying nuget for ${p.name}`);
|
|
2537
|
+
}
|
|
2538
|
+
const np = JSON.parse(JSON.stringify(p));
|
|
2539
|
+
const body = [];
|
|
2540
|
+
const newBody = [];
|
|
2541
|
+
const indexUrl = `${NUGET_URL + np.name.toLowerCase()}/index.json`;
|
|
2542
|
+
let res =
|
|
2543
|
+
prefetchedResponse(prefetchedNugetIndex, indexUrl) ||
|
|
2544
|
+
(await cdxgenAgent.get(indexUrl, { responseType: "json" }));
|
|
2545
|
+
const items = res.body.items;
|
|
2546
|
+
if (!items?.[0]) {
|
|
2547
|
+
return [np, newBody, body];
|
|
2548
|
+
}
|
|
2549
|
+
if (items[0] && !items[0].items) {
|
|
2550
|
+
if (!p.version || p.version === "0.0.0" || p.version === "latest") {
|
|
2551
|
+
const upper = items[items.length - 1].upper;
|
|
2552
|
+
np.version = setLatestVersion(upper);
|
|
2553
|
+
}
|
|
2554
|
+
for (const item of items) {
|
|
2555
|
+
if (np.version) {
|
|
2556
|
+
const lower = compare(
|
|
2557
|
+
coerce(item.lower, { loose: true }),
|
|
2558
|
+
coerce(np.version, { loose: true }),
|
|
2559
|
+
);
|
|
2560
|
+
const upper = compare(
|
|
2561
|
+
coerce(item.upper, { loose: true }),
|
|
2562
|
+
coerce(np.version, { loose: true }),
|
|
2563
|
+
);
|
|
2564
|
+
if (lower !== 1 && upper !== -1) {
|
|
2565
|
+
res = await cdxgenAgent.get(item["@id"], { responseType: "json" });
|
|
2566
|
+
for (const i of res.body.items.reverse()) {
|
|
2567
|
+
if (
|
|
2568
|
+
i.catalogEntry &&
|
|
2569
|
+
i.catalogEntry.version === coerceUp(np.version)
|
|
2570
|
+
) {
|
|
2571
|
+
newBody.push(i);
|
|
2572
|
+
return [np, newBody];
|
|
2573
|
+
}
|
|
2574
|
+
}
|
|
2575
|
+
}
|
|
2576
|
+
}
|
|
2577
|
+
}
|
|
2578
|
+
} else {
|
|
2579
|
+
if (!p.version || p.version === "0.0.0" || p.version === "latest") {
|
|
2580
|
+
const upper = items[items.length - 1].upper;
|
|
2581
|
+
np.version = setLatestVersion(upper);
|
|
2582
|
+
}
|
|
2583
|
+
if (np.version) {
|
|
2584
|
+
for (const item of items) {
|
|
2585
|
+
const lower = compare(
|
|
2586
|
+
coerce(item.lower, { loose: true }),
|
|
2587
|
+
coerce(np.version, { loose: true }),
|
|
2588
|
+
);
|
|
2589
|
+
const upper = compare(
|
|
2590
|
+
coerce(item.upper, { loose: true }),
|
|
2591
|
+
coerce(np.version, { loose: true }),
|
|
2592
|
+
);
|
|
2593
|
+
if (lower !== 1 && upper !== -1) {
|
|
2594
|
+
for (const i of item.items.reverse()) {
|
|
2595
|
+
if (
|
|
2596
|
+
i.catalogEntry &&
|
|
2597
|
+
i.catalogEntry.version === coerceUp(np.version)
|
|
2598
|
+
) {
|
|
2599
|
+
newBody.push(i);
|
|
2600
|
+
return [np, newBody];
|
|
2601
|
+
}
|
|
2602
|
+
}
|
|
2603
|
+
}
|
|
2604
|
+
}
|
|
2605
|
+
}
|
|
2606
|
+
}
|
|
2607
|
+
return [np, newBody];
|
|
2608
|
+
}
|
|
2609
|
+
|
|
2610
|
+
/**
|
|
2611
|
+
* Method to retrieve metadata for nuget packages
|
|
2612
|
+
*
|
|
2613
|
+
* @param {Array} pkgList Package list
|
|
2614
|
+
* @param {Array} dependencies Dependencies
|
|
2615
|
+
*/
|
|
2616
|
+
export async function getNugetMetadata(pkgList, dependencies = undefined) {
|
|
2617
|
+
const NUGET_URL =
|
|
2618
|
+
readEnvironmentVariable("NUGET_URL") || (await getNugetUrl());
|
|
2619
|
+
const cdepList = [];
|
|
2620
|
+
const depRepList = {};
|
|
2621
|
+
// Batch the per-package registration index requests. queryNuget's first
|
|
2622
|
+
// request for each package is the index; the follow-up to a specific
|
|
2623
|
+
// registration page depends on the index response and stays serial. Only
|
|
2624
|
+
// packages that would actually be queried (not in metadata_cache, no prior
|
|
2625
|
+
// error) are prefetched, matching the loop's skip conditions.
|
|
2626
|
+
const batchUrls = [];
|
|
2627
|
+
const seenNugetUrls = new Set();
|
|
2628
|
+
for (const p of pkgList) {
|
|
2629
|
+
// A cached entry — a body or a recorded error — means the loop will not
|
|
2630
|
+
// reach the network for this package, so the batch must not either.
|
|
2631
|
+
if (metadata_cache[`${p.name}|${p.version}`]) {
|
|
2632
|
+
continue;
|
|
2633
|
+
}
|
|
2634
|
+
const indexUrl = `${NUGET_URL + p.name.toLowerCase()}/index.json`;
|
|
2635
|
+
if (!seenNugetUrls.has(indexUrl)) {
|
|
2636
|
+
seenNugetUrls.add(indexUrl);
|
|
2637
|
+
batchUrls.push({ url: indexUrl });
|
|
2638
|
+
}
|
|
2639
|
+
}
|
|
2640
|
+
const ownIndexUrls = [];
|
|
2641
|
+
if (batchUrls.length) {
|
|
2642
|
+
const fetched = await prefetchJson(batchUrls);
|
|
2643
|
+
for (const [key, value] of fetched) {
|
|
2644
|
+
prefetchedNugetIndex.set(key, value);
|
|
2645
|
+
ownIndexUrls.push(key);
|
|
2646
|
+
}
|
|
2647
|
+
}
|
|
2648
|
+
try {
|
|
2649
|
+
for (const p of pkgList) {
|
|
2650
|
+
let cacheKey;
|
|
2651
|
+
try {
|
|
2652
|
+
// If there is a version, we can safely use the cache to retrieve the license
|
|
2653
|
+
// See: https://github.com/cdxgen/cdxgen/issues/352
|
|
2654
|
+
cacheKey = `${p.name}|${p.version}`;
|
|
2655
|
+
let body = metadata_cache[cacheKey];
|
|
2656
|
+
|
|
2657
|
+
if (body?.error) {
|
|
2658
|
+
cdepList.push(p);
|
|
2659
|
+
continue;
|
|
2660
|
+
}
|
|
2661
|
+
if (!body) {
|
|
2662
|
+
let newBody = {};
|
|
2663
|
+
let np = {};
|
|
2664
|
+
[np, newBody] = await queryNuget(p, NUGET_URL);
|
|
2665
|
+
if (p.version !== np.version) {
|
|
2666
|
+
const oldRef = p["bom-ref"];
|
|
2667
|
+
p["bom-ref"] = decodeURIComponent(
|
|
2668
|
+
build({
|
|
2669
|
+
type: "nuget",
|
|
2670
|
+
namespace: "" || null,
|
|
2671
|
+
name: np.name,
|
|
2672
|
+
version: np.version || null,
|
|
2673
|
+
}),
|
|
2674
|
+
);
|
|
2675
|
+
depRepList[oldRef] = p["bom-ref"];
|
|
2676
|
+
p.version = np.version;
|
|
2677
|
+
}
|
|
2678
|
+
if (newBody && newBody.length > 0) {
|
|
2679
|
+
body = newBody[0];
|
|
2680
|
+
}
|
|
2681
|
+
if (body) {
|
|
2682
|
+
metadata_cache[cacheKey] = body;
|
|
2683
|
+
// Set the latest version in case it is missing
|
|
2684
|
+
if (!p.version && body.catalogEntry.version) {
|
|
2685
|
+
p.version = body.catalogEntry.version;
|
|
2686
|
+
}
|
|
2687
|
+
p.description = body.catalogEntry.description;
|
|
2688
|
+
if (body.catalogEntry.authors) {
|
|
2689
|
+
p.author = body.catalogEntry.authors.trim();
|
|
2690
|
+
}
|
|
2691
|
+
if (
|
|
2692
|
+
body.catalogEntry.licenseExpression &&
|
|
2693
|
+
body.catalogEntry.licenseExpression !== ""
|
|
2694
|
+
) {
|
|
2695
|
+
p.license = findLicenseId(body.catalogEntry.licenseExpression);
|
|
2696
|
+
} else if (body.catalogEntry.licenseUrl) {
|
|
2697
|
+
p.license = findLicenseId(body.catalogEntry.licenseUrl);
|
|
2698
|
+
if (typeof p.license === "string" && isGitHubUrl(p.license)) {
|
|
2699
|
+
p.license =
|
|
2700
|
+
(await getRepoLicense(p.license, undefined)) || p.license;
|
|
2701
|
+
}
|
|
2702
|
+
}
|
|
2703
|
+
// Capture the tags
|
|
2704
|
+
if (
|
|
2705
|
+
body.catalogEntry?.tags?.length &&
|
|
2706
|
+
Array.isArray(body.catalogEntry.tags)
|
|
2707
|
+
) {
|
|
2708
|
+
p.tags = body.catalogEntry.tags.map((t) =>
|
|
2709
|
+
t.toLowerCase().replaceAll(" ", "-"),
|
|
2710
|
+
);
|
|
2711
|
+
}
|
|
2712
|
+
if (body.catalogEntry.projectUrl) {
|
|
2713
|
+
p.repository = { url: body.catalogEntry.projectUrl };
|
|
2714
|
+
p.homepage = {
|
|
2715
|
+
url: `https://www.nuget.org/packages/${p.name}/${p.version}/`,
|
|
2716
|
+
};
|
|
2717
|
+
if (
|
|
2718
|
+
(!p.license || typeof p.license === "string") &&
|
|
2719
|
+
typeof p.repository.url === "string" &&
|
|
2720
|
+
isGitHubUrl(p.repository.url)
|
|
2721
|
+
) {
|
|
2722
|
+
// license couldn't be properly identified and is still a url,
|
|
2723
|
+
// therefore trying to resolve license via repository
|
|
2724
|
+
p.license =
|
|
2725
|
+
(await getRepoLicense(p.repository.url, undefined)) ||
|
|
2726
|
+
p.license;
|
|
2727
|
+
}
|
|
2728
|
+
}
|
|
2729
|
+
cdepList.push(p);
|
|
2730
|
+
}
|
|
2731
|
+
}
|
|
2732
|
+
} catch (err) {
|
|
2733
|
+
if (cacheKey) {
|
|
2734
|
+
metadata_cache[cacheKey] = { error: err.code };
|
|
2735
|
+
}
|
|
2736
|
+
cdepList.push(p);
|
|
2737
|
+
}
|
|
2738
|
+
}
|
|
2739
|
+
} finally {
|
|
2740
|
+
// Each index document is read once, by the loop above. Holding them past
|
|
2741
|
+
// that only grows the process, which matters in server mode where one
|
|
2742
|
+
// process serves many scans. Only the documents this call fetched are
|
|
2743
|
+
// dropped, so a scan running alongside this one keeps its own.
|
|
2744
|
+
for (const indexUrl of ownIndexUrls) {
|
|
2745
|
+
prefetchedNugetIndex.delete(indexUrl);
|
|
2746
|
+
}
|
|
2747
|
+
}
|
|
2748
|
+
const newDependencies = [].concat(dependencies);
|
|
2749
|
+
if (depRepList && newDependencies.length) {
|
|
2750
|
+
const changed = Object.keys(depRepList);
|
|
2751
|
+
// if (!parentComponent.version || parentComponent.version === "latest" || parentComponent.version === "0.0.0"){
|
|
2752
|
+
// if (changed.includes(parentComponent["bom-ref"])) {
|
|
2753
|
+
// parentComponent["bom-ref"] = depRepList[parentComponent["bom-ref"]["ref"]];
|
|
2754
|
+
// }
|
|
2755
|
+
// }
|
|
2756
|
+
for (const d of newDependencies) {
|
|
2757
|
+
if (changed.length > 0 && changed.includes(d["ref"])) {
|
|
2758
|
+
d["ref"] = depRepList[d["ref"]];
|
|
2759
|
+
}
|
|
2760
|
+
for (const dd in d["dependsOn"]) {
|
|
2761
|
+
if (changed.includes(d["dependsOn"][dd])) {
|
|
2762
|
+
const replace = d["dependsOn"][dd];
|
|
2763
|
+
d["dependsOn"][dd] = depRepList[replace];
|
|
2764
|
+
}
|
|
2765
|
+
}
|
|
2766
|
+
}
|
|
2767
|
+
}
|
|
2768
|
+
return {
|
|
2769
|
+
pkgList: cdepList,
|
|
2770
|
+
dependencies: newDependencies,
|
|
2771
|
+
};
|
|
2772
|
+
}
|