@cyclonedx/cdxgen 9.9.8 → 9.10.0

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 CHANGED
@@ -229,14 +229,14 @@ To generate SBOM for an older specification version, such as 1.4, pass the versi
229
229
  cdxgen -r -o bom.json --spec-version 1.4
230
230
  ```
231
231
 
232
- To generate SBOM for C or Python, ensure Java >= 17 is installed.
232
+ To generate SBOM for C or Python, ensure Java >= 21 is installed.
233
233
 
234
234
  ```shell
235
- # Install java >= 17
235
+ # Install java >= 21
236
236
  cdxgen -t c -o bom.json
237
237
  ```
238
238
 
239
- NOTE: cdxgen is known to freeze with Java 8 or 11, so ensure >= 17 is installed and JAVA_HOME environment variable is configured correctly. If in doubt, use the cdxgen container image.
239
+ NOTE: cdxgen is known to freeze with Java 8 or 11, so ensure >= 21 is installed and JAVA_HOME environment variable is configured correctly. If in doubt, use the cdxgen container image.
240
240
 
241
241
  ## Universal SBOM
242
242
 
@@ -371,6 +371,7 @@ cdxgen can retain the dependency tree under the `dependencies` attribute for a s
371
371
  | GRADLE_DEPENDENCY_TASK | By default cdxgen use the task "dependencies" to collect packages. Set to override the task name. |
372
372
  | SBT_CACHE_DIR | Specify sbt cache directory. Useful for class name resolving |
373
373
  | FETCH_LICENSE | Set this variable to `true` or `1` to fetch license information from the registry. npm and golang |
374
+ | SEARCH_MAVEN_ORG | If maven metadata is missing in jar file, a search is performed on search.maven.org. Set to `false` or `0` to disable search. |
374
375
  | USE_GOSUM | Set to `true` or `1` to generate BOMs for golang projects using go.sum as the dependency source of truth, instead of go.mod |
375
376
  | CDXGEN_TIMEOUT_MS | Default timeout for known execution involving maven, gradle or sbt |
376
377
  | CDXGEN_SERVER_TIMEOUT_MS | Default timeout in server mode |
package/evinser.js CHANGED
@@ -478,13 +478,15 @@ export const parseSliceUsages = async (
478
478
  purlLocationMap,
479
479
  purlImportsMap
480
480
  ) => {
481
- const usages = slice.usages;
482
- if (!usages || !usages.length) {
483
- return undefined;
484
- }
485
481
  const fileName = slice.fileName;
486
482
  const typesToLookup = new Set();
487
483
  const lKeyOverrides = {};
484
+ const usages = slice.usages || [];
485
+ // Annotations from usages
486
+ if (slice.signature && slice.signature.startsWith("@") && !usages.length) {
487
+ typesToLookup.add(slice.fullName);
488
+ addToOverrides(lKeyOverrides, slice.fullName, fileName, slice.lineNumber);
489
+ }
488
490
  for (const ausage of usages) {
489
491
  const ausageLine =
490
492
  ausage?.targetObj?.lineNumber || ausage?.definedBy?.lineNumber;
@@ -527,7 +529,17 @@ export const parseSliceUsages = async (
527
529
  .concat(ausage?.invokedCalls || [])
528
530
  .concat(ausage?.argToCalls || [])
529
531
  .concat(ausage?.procedures || [])) {
530
- if (acall.isExternal == false) {
532
+ if (acall.resolvedMethod && acall.resolvedMethod.startsWith("@")) {
533
+ typesToLookup.add(acall.callName);
534
+ if (acall.lineNumber) {
535
+ addToOverrides(
536
+ lKeyOverrides,
537
+ acall.callName,
538
+ fileName,
539
+ acall.lineNumber
540
+ );
541
+ }
542
+ } else if (acall.isExternal == false) {
531
543
  continue;
532
544
  }
533
545
  if (
package/index.js CHANGED
@@ -108,7 +108,8 @@ import {
108
108
  parseContainerFile,
109
109
  parseBitbucketPipelinesFile,
110
110
  getPyMetadata,
111
- addEvidenceForDotnet
111
+ addEvidenceForDotnet,
112
+ getSwiftPackageMetadata
112
113
  } from "./utils.js";
113
114
  import { spawnSync } from "node:child_process";
114
115
  import { fileURLToPath } from "node:url";
@@ -1085,7 +1086,7 @@ export const createJarBom = async (path, options) => {
1085
1086
  if (DEBUG_MODE) {
1086
1087
  console.log(`Parsing ${jar}`);
1087
1088
  }
1088
- const dlist = extractJarArchive(jar, tempDir);
1089
+ const dlist = await extractJarArchive(jar, tempDir);
1089
1090
  if (dlist && dlist.length) {
1090
1091
  pkgList = pkgList.concat(dlist);
1091
1092
  }
@@ -1127,7 +1128,7 @@ export const createJavaBom = async (path, options) => {
1127
1128
  }
1128
1129
  const tempDir = mkdtempSync(join(tmpdir(), "war-deps-"));
1129
1130
  jarNSMapping = collectJarNS(tempDir);
1130
- pkgList = extractJarArchive(path, tempDir, jarNSMapping);
1131
+ pkgList = await extractJarArchive(path, tempDir, jarNSMapping);
1131
1132
  if (pkgList.length) {
1132
1133
  pkgList = await getMvnMetadata(pkgList);
1133
1134
  }
@@ -1266,7 +1267,7 @@ export const createJavaBom = async (path, options) => {
1266
1267
  );
1267
1268
  } else {
1268
1269
  console.log(
1269
- "1. Java version requirement: cdxgen container image bundles Java 20 with maven 3.9 which might be incompatible."
1270
+ "1. Java version requirement: cdxgen container image bundles Java 21 with maven 3.9 which might be incompatible."
1270
1271
  );
1271
1272
  }
1272
1273
  console.log(
@@ -3169,7 +3170,7 @@ export const createCppBom = (path, options) => {
3169
3170
  }
3170
3171
  }
3171
3172
  }
3172
- // The need for java >= 17 with atom is causing confusions since there could be C projects
3173
+ // The need for java >= 21 with atom is causing confusions since there could be C projects
3173
3174
  // inside of other project types. So we currently limit this analyis only when -t argument
3174
3175
  // is used.
3175
3176
  if (
@@ -3560,7 +3561,7 @@ export const createJenkinsBom = async (path, options) => {
3560
3561
  if (DEBUG_MODE) {
3561
3562
  console.log(`Parsing ${f}`);
3562
3563
  }
3563
- const dlist = extractJarArchive(f, tempDir);
3564
+ const dlist = await extractJarArchive(f, tempDir);
3564
3565
  if (dlist && dlist.length) {
3565
3566
  pkgList = pkgList.concat(dlist);
3566
3567
  }
@@ -3628,7 +3629,7 @@ export const createHelmBom = (path, options) => {
3628
3629
  * @param path to the project
3629
3630
  * @param options Parse options from the cli
3630
3631
  */
3631
- export const createSwiftBom = (path, options) => {
3632
+ export const createSwiftBom = async (path, options) => {
3632
3633
  const swiftFiles = getAllFiles(
3633
3634
  path,
3634
3635
  (options.multiProject ? "**/" : "") + "Package*.swift",
@@ -3704,6 +3705,9 @@ export const createSwiftBom = (path, options) => {
3704
3705
  }
3705
3706
  }
3706
3707
  }
3708
+ if (FETCH_LICENSE) {
3709
+ pkgList = await getSwiftPackageMetadata(pkgList);
3710
+ }
3707
3711
  return buildBomNSData(options, pkgList, "swift", {
3708
3712
  src: path,
3709
3713
  filename: swiftFiles.join(", "),
@@ -4899,7 +4903,7 @@ export const createMultiXBom = async (pathList, options) => {
4899
4903
  )
4900
4904
  );
4901
4905
  }
4902
- bomData = createSwiftBom(path, options);
4906
+ bomData = await createSwiftBom(path, options);
4903
4907
  if (
4904
4908
  bomData &&
4905
4909
  bomData.bomJson &&
@@ -5329,7 +5333,7 @@ export const createXBom = async (path, options) => {
5329
5333
  options
5330
5334
  );
5331
5335
  if (swiftFiles.length || pkgResolvedFiles.length) {
5332
- return createSwiftBom(path, options);
5336
+ return await createSwiftBom(path, options);
5333
5337
  }
5334
5338
  };
5335
5339
 
@@ -5585,7 +5589,7 @@ export const createBom = async (path, options) => {
5585
5589
  case "cloudbuild":
5586
5590
  return createCloudBuildBom(path, options);
5587
5591
  case "swift":
5588
- return createSwiftBom(path, options);
5592
+ return await createSwiftBom(path, options);
5589
5593
  default:
5590
5594
  // In recurse mode return multi-language Bom
5591
5595
  // https://github.com/cyclonedx/cdxgen/issues/95
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyclonedx/cdxgen",
3
- "version": "9.9.8",
3
+ "version": "9.10.0",
4
4
  "description": "Creates CycloneDX Software Bill of Materials (SBOM) from source or container image",
5
5
  "homepage": "http://github.com/cyclonedx/cdxgen",
6
6
  "author": "Prabhu Subramanian <prabhu@appthreat.com>",
@@ -83,7 +83,7 @@
83
83
  "yargs": "^17.7.2"
84
84
  },
85
85
  "optionalDependencies": {
86
- "@appthreat/atom": "1.7.2",
86
+ "@appthreat/atom": "1.7.5",
87
87
  "@cyclonedx/cdxgen-plugins-bin": "^1.5.4",
88
88
  "@cyclonedx/cdxgen-plugins-bin-windows-amd64": "^1.5.4",
89
89
  "@cyclonedx/cdxgen-plugins-bin-arm64": "^1.5.4",
@@ -103,7 +103,7 @@
103
103
  "devDependencies": {
104
104
  "caxa": "^3.0.1",
105
105
  "docsify-cli": "^4.4.4",
106
- "eslint": "^8.55.0",
106
+ "eslint": "^8.56.0",
107
107
  "eslint-config-prettier": "^9.1.0",
108
108
  "eslint-plugin-prettier": "^5.0.1",
109
109
  "jest": "^29.7.0",
package/utils.js CHANGED
@@ -19,8 +19,10 @@ import {
19
19
  readFileSync,
20
20
  rmSync,
21
21
  unlinkSync,
22
- writeFileSync
22
+ writeFileSync,
23
+ createReadStream
23
24
  } from "node:fs";
25
+ import { createHash } from "node:crypto";
24
26
  import got from "got";
25
27
  import Arborist from "@npmcli/arborist";
26
28
  import path from "node:path";
@@ -120,6 +122,19 @@ export const FETCH_LICENSE =
120
122
  process.env.FETCH_LICENSE &&
121
123
  ["true", "1"].includes(process.env.FETCH_LICENSE);
122
124
 
125
+ // Wether search.maven.org will be used to identify jars without maven metadata; default, if unset shall be 'true'
126
+ export const SEARCH_MAVEN_ORG =
127
+ !process.env.SEARCH_MAVEN_ORG ||
128
+ ["true", "1"].includes(process.env.SEARCH_MAVEN_ORG);
129
+
130
+ // circuit breaker for search maven.org
131
+ let search_maven_org_errors = 0;
132
+ const MAX_SEARCH_MAVEN_ORG_ERRORS = 5;
133
+
134
+ // circuit breaker for get repo license
135
+ let get_repo_license_errors = 0;
136
+ const MAX_GET_REPO_LICENSE_ERRORS = 5;
137
+
123
138
  const MAX_LICENSE_ID_LENGTH = 100;
124
139
 
125
140
  let PYTHON_CMD = "python";
@@ -389,6 +404,34 @@ export function readLicenseText(
389
404
  return null;
390
405
  }
391
406
 
407
+ export const getSwiftPackageMetadata = async (pkgList) => {
408
+ const cdepList = [];
409
+ for (const p of pkgList) {
410
+ if (p.repository && p.repository.url) {
411
+ if (p.repository.url.includes("://github.com/")) {
412
+ try {
413
+ p.license = await getRepoLicense(p.repository.url, undefined);
414
+ } catch (e) {
415
+ console.error("error fetching repo license from", p.repository.url);
416
+ }
417
+ } else {
418
+ if (DEBUG_MODE) {
419
+ console.log(
420
+ p.repository.url,
421
+ "is currently not supported to fetch for licenses"
422
+ );
423
+ }
424
+ }
425
+ } else {
426
+ if (DEBUG_MODE) {
427
+ console.warn("no repository url found for", p.name);
428
+ }
429
+ }
430
+ cdepList.push(p);
431
+ }
432
+ return cdepList;
433
+ };
434
+
392
435
  /**
393
436
  * Method to retrieve metadata for npm packages by querying npmjs
394
437
  *
@@ -2092,7 +2135,7 @@ export const executeGradleProperties = function (dir, rootPath, subProject) {
2092
2135
  } else {
2093
2136
  console.error(result.stdout, result.stderr);
2094
2137
  console.log(
2095
- "1. Check if the correct version of java and gradle are installed and available in PATH. For example, some project might require Java 11 with gradle 7.\n cdxgen container image bundles Java 20 with gradle 8 which might be incompatible."
2138
+ "1. Check if the correct version of java and gradle are installed and available in PATH. For example, some project might require Java 11 with gradle 7.\n cdxgen container image bundles Java 21 with gradle 8 which might be incompatible."
2096
2139
  );
2097
2140
  }
2098
2141
  if (result.stderr.includes("not get unknown property")) {
@@ -3318,21 +3361,30 @@ export const repoMetadataToGitHubApiUrl = function (repoMetadata) {
3318
3361
  };
3319
3362
 
3320
3363
  /**
3321
- * Method to construct GitHub api url from repo metadata or one of multiple formats of repo URLs
3364
+ * Method to split GitHub url into its parts
3322
3365
  * @param {String} repoUrl Repository url
3323
- * @param {Object} repoMetadata Object containing group and package name strings
3324
- * @return {String|undefined} github api url (or undefined - if not a GitHub repo)
3366
+ * @return {[String]} parts from url
3325
3367
  */
3326
- export const toGitHubApiUrl = function (repoUrl, repoMetadata) {
3327
- if (!repoUrl || !repoUrl.includes("://github.com/")) {
3328
- return repoMetadataToGitHubApiUrl(repoMetadata);
3329
- }
3368
+ export const getGithubUrlParts = (repoUrl) => {
3330
3369
  if (repoUrl.toLowerCase().endsWith(".git")) {
3331
3370
  repoUrl = repoUrl.slice(0, -4);
3332
3371
  }
3333
3372
  repoUrl.replace(/\/$/, "");
3334
3373
  const parts = repoUrl.split("/");
3374
+ return parts;
3375
+ };
3335
3376
 
3377
+ /**
3378
+ * Method to construct GitHub api url from repo metadata or one of multiple formats of repo URLs
3379
+ * @param {String} repoUrl Repository url
3380
+ * @param {Object} repoMetadata Object containing group and package name strings
3381
+ * @return {String|undefined} github api url (or undefined - if not a GitHub repo)
3382
+ */
3383
+ export const toGitHubApiUrl = function (repoUrl, repoMetadata) {
3384
+ if (repoMetadata) {
3385
+ return repoMetadataToGitHubApiUrl(repoMetadata);
3386
+ }
3387
+ const parts = getGithubUrlParts(repoUrl);
3336
3388
  if (parts.length < 5 || parts[2] !== "github.com") {
3337
3389
  return undefined; // Not a valid GitHub repo URL
3338
3390
  } else {
@@ -3353,7 +3405,7 @@ export const toGitHubApiUrl = function (repoUrl, repoMetadata) {
3353
3405
  export const getRepoLicense = async function (repoUrl, repoMetadata) {
3354
3406
  let apiUrl = toGitHubApiUrl(repoUrl, repoMetadata);
3355
3407
  // Perform github lookups
3356
- if (apiUrl) {
3408
+ if (apiUrl && get_repo_license_errors < MAX_GET_REPO_LICENSE_ERRORS) {
3357
3409
  let licenseUrl = apiUrl + "/license";
3358
3410
  const headers = {};
3359
3411
  if (process.env.GITHUB_TOKEN) {
@@ -3399,8 +3451,10 @@ export const getRepoLicense = async function (repoUrl, repoMetadata) {
3399
3451
  "Please ensure GITHUB_TOKEN is set as environment variable. " +
3400
3452
  "See: https://docs.github.com/en/rest/overview/rate-limits-for-the-rest-api"
3401
3453
  );
3454
+ get_repo_license_errors++;
3402
3455
  } else if (!err.message.includes("404")) {
3403
3456
  console.log(err);
3457
+ get_repo_license_errors++;
3404
3458
  }
3405
3459
  }
3406
3460
  }
@@ -6105,87 +6159,89 @@ export const convertOSQueryResults = function (
6105
6159
  return pkgList;
6106
6160
  };
6107
6161
 
6108
- export const _swiftDepPkgList = (
6162
+ const purlFromUrlString = (type, repoUrl, version) => {
6163
+ let namespace = "",
6164
+ name;
6165
+ if (repoUrl && repoUrl.includes("://github.com/")) {
6166
+ const parts = getGithubUrlParts(repoUrl);
6167
+ if (parts.length < 5 || parts[2] !== "github.com") {
6168
+ return undefined; // Not a valid GitHub repo URL
6169
+ } else {
6170
+ namespace = parts[2] + "/" + parts[3];
6171
+ name = parts[4];
6172
+ }
6173
+ } else if (repoUrl && repoUrl.startsWith("/")) {
6174
+ const parts = repoUrl.split("/");
6175
+ name = parts[parts.length - 1];
6176
+ } else {
6177
+ if (DEBUG_MODE) {
6178
+ console.warn("unsupported repo url for swift type");
6179
+ }
6180
+ return undefined;
6181
+ }
6182
+
6183
+ const purl = new PackageURL(type, namespace, name, version, null, null);
6184
+ return purl;
6185
+ };
6186
+
6187
+ /**
6188
+ * Parse swift dependency tree output json object
6189
+ * @param {string} jsonObject Swift dependencies json object
6190
+ * @param {string} pkgFile Package.swift file
6191
+ */
6192
+ export const parseSwiftJsonTreeObject = (
6109
6193
  pkgList,
6110
6194
  dependenciesList,
6111
- depKeys,
6112
- jsonData
6195
+ jsonObject,
6196
+ pkgFile
6113
6197
  ) => {
6114
- if (jsonData && jsonData.dependencies) {
6115
- for (const adep of jsonData.dependencies) {
6116
- const urlOrPath = adep.url || adep.path;
6117
- const apkg = {
6118
- group: adep.identity || "",
6119
- name: adep.name,
6120
- version: adep.version
6121
- };
6122
- const purl = new PackageURL(
6123
- "swift",
6124
- apkg.group,
6125
- apkg.name,
6126
- apkg.version,
6127
- null,
6128
- null
6129
- );
6130
- const purlString = decodeURIComponent(purl.toString());
6131
- if (urlOrPath) {
6132
- if (urlOrPath.startsWith("http")) {
6133
- apkg.repository = { url: urlOrPath };
6134
- if (apkg.path) {
6135
- apkg.properties = [
6136
- {
6137
- name: "SrcPath",
6138
- value: apkg.path
6139
- }
6140
- ];
6141
- }
6142
- } else {
6143
- apkg.properties = [
6144
- {
6145
- name: "SrcPath",
6146
- value: urlOrPath
6147
- }
6148
- ];
6149
- }
6150
- }
6151
- pkgList.push(apkg);
6152
- // Handle the immediate dependencies before recursing
6153
- if (adep.dependencies && adep.dependencies.length) {
6154
- const deplist = [];
6155
- for (const cdep of adep.dependencies) {
6156
- const deppurl = new PackageURL(
6157
- "swift",
6158
- cdep.identity || "",
6159
- cdep.name,
6160
- cdep.version,
6161
- null,
6162
- null
6163
- );
6164
- const deppurlString = decodeURIComponent(deppurl.toString());
6165
- deplist.push(deppurlString);
6166
- }
6167
- if (!depKeys[purlString]) {
6168
- dependenciesList.push({
6169
- ref: purlString,
6170
- dependsOn: deplist
6171
- });
6172
- depKeys[purlString] = true;
6173
- }
6174
- if (adep.dependencies && adep.dependencies.length) {
6175
- _swiftDepPkgList(pkgList, dependenciesList, depKeys, adep);
6176
- }
6177
- } else {
6178
- if (!depKeys[purlString]) {
6179
- dependenciesList.push({
6180
- ref: purlString,
6181
- dependsOn: []
6182
- });
6183
- depKeys[purlString] = true;
6184
- }
6198
+ const urlOrPath = jsonObject.url || jsonObject.path;
6199
+ const version = jsonObject.version;
6200
+ const purl = purlFromUrlString("swift", urlOrPath, version);
6201
+ const purlString = decodeURIComponent(purl.toString());
6202
+ const rootPkg = {
6203
+ name: purl.name,
6204
+ group: purl.namespace,
6205
+ version: purl.version,
6206
+ purl: purlString,
6207
+ "bom-ref": purlString
6208
+ };
6209
+ if (urlOrPath) {
6210
+ if (urlOrPath.startsWith("http")) {
6211
+ rootPkg.repository = { url: urlOrPath };
6212
+ } else {
6213
+ const properties = [];
6214
+ properties.push({
6215
+ name: "SrcPath",
6216
+ value: urlOrPath
6217
+ });
6218
+ if (pkgFile) {
6219
+ properties.push({
6220
+ name: "SrcFile",
6221
+ value: pkgFile
6222
+ });
6185
6223
  }
6224
+ rootPkg.properties = properties;
6186
6225
  }
6187
6226
  }
6188
- return { pkgList, dependenciesList };
6227
+ pkgList.push(rootPkg);
6228
+ const depList = [];
6229
+ if (jsonObject.dependencies) {
6230
+ for (const dependency of jsonObject.dependencies) {
6231
+ const res = parseSwiftJsonTreeObject(
6232
+ pkgList,
6233
+ dependenciesList,
6234
+ dependency,
6235
+ pkgFile
6236
+ );
6237
+ depList.push(res);
6238
+ }
6239
+ }
6240
+ dependenciesList.push({
6241
+ ref: purlString,
6242
+ dependsOn: depList
6243
+ });
6244
+ return purlString;
6189
6245
  };
6190
6246
 
6191
6247
  /**
@@ -6199,64 +6255,9 @@ export const parseSwiftJsonTree = (rawOutput, pkgFile) => {
6199
6255
  }
6200
6256
  const pkgList = [];
6201
6257
  const dependenciesList = [];
6202
- const depKeys = {};
6203
- let rootPkg = {};
6204
- let jsonData = {};
6205
6258
  try {
6206
- jsonData = JSON.parse(rawOutput);
6207
- if (jsonData && jsonData.name) {
6208
- rootPkg = {
6209
- group: jsonData.identity || "",
6210
- name: jsonData.name,
6211
- version: jsonData.version
6212
- };
6213
- const urlOrPath = jsonData.url || jsonData.path;
6214
- if (urlOrPath) {
6215
- if (urlOrPath.startsWith("http")) {
6216
- rootPkg.repository = { url: urlOrPath };
6217
- } else {
6218
- rootPkg.properties = [
6219
- {
6220
- name: "SrcPath",
6221
- value: urlOrPath
6222
- },
6223
- {
6224
- name: "SrcFile",
6225
- value: pkgFile
6226
- }
6227
- ];
6228
- }
6229
- }
6230
- const purl = new PackageURL(
6231
- "swift",
6232
- rootPkg.group,
6233
- rootPkg.name,
6234
- rootPkg.version,
6235
- null,
6236
- null
6237
- );
6238
- const bomRefString = decodeURIComponent(purl.toString());
6239
- rootPkg["bom-ref"] = bomRefString;
6240
- pkgList.push(rootPkg);
6241
- const deplist = [];
6242
- for (const rd of jsonData.dependencies) {
6243
- const deppurl = new PackageURL(
6244
- "swift",
6245
- rd.identity || "",
6246
- rd.name,
6247
- rd.version,
6248
- null,
6249
- null
6250
- );
6251
- const deppurlString = decodeURIComponent(deppurl.toString());
6252
- deplist.push(deppurlString);
6253
- }
6254
- dependenciesList.push({
6255
- ref: bomRefString,
6256
- dependsOn: deplist
6257
- });
6258
- _swiftDepPkgList(pkgList, dependenciesList, depKeys, jsonData);
6259
- }
6259
+ const jsonData = JSON.parse(rawOutput);
6260
+ parseSwiftJsonTreeObject(pkgList, dependenciesList, jsonData, pkgFile);
6260
6261
  } catch (e) {
6261
6262
  if (DEBUG_MODE) {
6262
6263
  console.log(e);
@@ -6287,10 +6288,16 @@ export const parseSwiftResolved = (resolvedFile) => {
6287
6288
  resolvedList = pkgData.object.pins;
6288
6289
  }
6289
6290
  for (const adep of resolvedList) {
6290
- const apkg = {
6291
- name: adep.package || adep.identity,
6292
- group: "",
6293
- version: adep.state.version || adep.state.revision,
6291
+ const locationOrUrl = adep.location || adep.repositoryURL;
6292
+ const version = adep.state.version || adep.state.revision;
6293
+ const purl = purlFromUrlString("swift", locationOrUrl, version);
6294
+ const purlString = decodeURIComponent(purl.toString());
6295
+ const rootPkg = {
6296
+ name: purl.name,
6297
+ group: purl.namespace,
6298
+ version: purl.version,
6299
+ purl: purlString,
6300
+ "bom-ref": purlString,
6294
6301
  properties: [
6295
6302
  {
6296
6303
  name: "SrcFile",
@@ -6311,11 +6318,10 @@ export const parseSwiftResolved = (resolvedFile) => {
6311
6318
  }
6312
6319
  }
6313
6320
  };
6314
- const repLocation = adep.location || adep.repositoryURL;
6315
- if (repLocation) {
6316
- apkg.repository = { url: repLocation };
6321
+ if (locationOrUrl) {
6322
+ rootPkg.repository = { url: locationOrUrl };
6317
6323
  }
6318
- pkgList.push(apkg);
6324
+ pkgList.push(rootPkg);
6319
6325
  }
6320
6326
  } catch (err) {
6321
6327
  // continue regardless of error
@@ -6578,7 +6584,7 @@ export const collectJarNS = function (jarPath, pomPathMap = {}) {
6578
6584
  ) {
6579
6585
  jarCommandAvailable = false;
6580
6586
  console.log(
6581
- "jar command is not available in PATH. Ensure JDK >= 17 is installed and set the environment variables JAVA_HOME and PATH to the bin directory inside JAVA_HOME."
6587
+ "jar command is not available in PATH. Ensure JDK >= 21 is installed and set the environment variables JAVA_HOME and PATH to the bin directory inside JAVA_HOME."
6582
6588
  );
6583
6589
  }
6584
6590
  const consolelines = (jarResult.stdout || "").split("\n");
@@ -6775,6 +6781,22 @@ export const getPomPropertiesFromMavenDir = function (mavenDir) {
6775
6781
  return pomProperties;
6776
6782
  };
6777
6783
 
6784
+ /**
6785
+ *
6786
+ * @param {string} hashName name of hash algorithm
6787
+ * @param {string} path path to file
6788
+ * @returns {Promise<String>} hex value of hash
6789
+ */
6790
+ async function checksumFile(hashName, path) {
6791
+ return new Promise((resolve, reject) => {
6792
+ const hash = createHash(hashName);
6793
+ const stream = createReadStream(path);
6794
+ stream.on("error", (err) => reject(err));
6795
+ stream.on("data", (chunk) => hash.update(chunk));
6796
+ stream.on("end", () => resolve(hash.digest("hex")));
6797
+ });
6798
+ }
6799
+
6778
6800
  /**
6779
6801
  * Method to extract a war or ear file
6780
6802
  *
@@ -6784,7 +6806,7 @@ export const getPomPropertiesFromMavenDir = function (mavenDir) {
6784
6806
  *
6785
6807
  * @return pkgList Package list
6786
6808
  */
6787
- export const extractJarArchive = function (
6809
+ export const extractJarArchive = async function (
6788
6810
  jarFile,
6789
6811
  tempDir,
6790
6812
  jarNSMapping = {}
@@ -6882,6 +6904,35 @@ export const extractJarArchive = function (
6882
6904
  version = pomProperties["version"],
6883
6905
  confidence = 1,
6884
6906
  technique = "manifest-analysis";
6907
+ if (
6908
+ (!group || !name || !version) &&
6909
+ SEARCH_MAVEN_ORG &&
6910
+ search_maven_org_errors < MAX_SEARCH_MAVEN_ORG_ERRORS
6911
+ ) {
6912
+ try {
6913
+ const sha = await checksumFile("sha1", jf);
6914
+ const searchurl =
6915
+ "https://search.maven.org/solrsearch/select?q=1:%22" +
6916
+ sha +
6917
+ "%22&rows=20&wt=json";
6918
+ const res = await cdxgenAgent.get(searchurl, {
6919
+ responseType: "json"
6920
+ });
6921
+ const data = res && res.body ? res.body["response"] : undefined;
6922
+ if (data && data["numFound"] == 1) {
6923
+ const jarInfo = data["docs"][0];
6924
+ group = jarInfo["g"];
6925
+ name = jarInfo["a"];
6926
+ version = jarInfo["v"];
6927
+ technique = "hash-comparison";
6928
+ }
6929
+ } catch (err) {
6930
+ if (err && err.message && !err.message.includes("404")) {
6931
+ console.log(err);
6932
+ search_maven_org_errors++;
6933
+ }
6934
+ }
6935
+ }
6885
6936
  if ((!group || !name || !version) && existsSync(manifestFile)) {
6886
6937
  confidence = 0.8;
6887
6938
  const jarMetadata = parseJarManifest(
package/utils.test.js CHANGED
@@ -2919,28 +2919,29 @@ test("parse swift deps files", () => {
2919
2919
  );
2920
2920
  expect(retData.pkgList.length).toEqual(5);
2921
2921
  expect(retData.pkgList[0]).toEqual({
2922
- group: "swift-markdown",
2923
2922
  name: "swift-markdown",
2923
+ group: "",
2924
+ purl: "pkg:swift/swift-markdown@unspecified",
2924
2925
  version: "unspecified",
2925
2926
  properties: [
2926
2927
  { name: "SrcPath", value: "/Volumes/Work/sandbox/swift-markdown" },
2927
2928
  { name: "SrcFile", value: "./test/data/swift-deps.json" }
2928
2929
  ],
2929
- "bom-ref": "pkg:swift/swift-markdown/swift-markdown@unspecified"
2930
+ "bom-ref": "pkg:swift/swift-markdown@unspecified"
2930
2931
  });
2931
2932
  expect(retData.dependenciesList.length).toEqual(5);
2932
2933
  expect(retData.dependenciesList[0]).toEqual({
2933
- ref: "pkg:swift/swift-markdown/swift-markdown@unspecified",
2934
- dependsOn: [
2935
- "pkg:swift/swift-cmark/cmark-gfm@unspecified",
2936
- "pkg:swift/swift-argument-parser/swift-argument-parser@1.0.3",
2937
- "pkg:swift/swift-docc-plugin/SwiftDocCPlugin@1.1.0"
2938
- ]
2934
+ ref: "pkg:swift/github.com/apple/swift-cmark@unspecified",
2935
+ dependsOn: []
2939
2936
  });
2940
2937
  expect(retData.dependenciesList[retData.dependenciesList.length - 1]).toEqual(
2941
2938
  {
2942
- ref: "pkg:swift/swift-docc-symbolkit/SymbolKit@1.0.0",
2943
- dependsOn: []
2939
+ ref: "pkg:swift/swift-markdown@unspecified",
2940
+ dependsOn: [
2941
+ "pkg:swift/github.com/apple/swift-cmark@unspecified",
2942
+ "pkg:swift/github.com/apple/swift-argument-parser@1.0.3",
2943
+ "pkg:swift/github.com/apple/swift-docc-plugin@1.1.0"
2944
+ ]
2944
2945
  }
2945
2946
  );
2946
2947
  retData = parseSwiftJsonTree(
@@ -2949,8 +2950,9 @@ test("parse swift deps files", () => {
2949
2950
  );
2950
2951
  expect(retData.pkgList.length).toEqual(5);
2951
2952
  expect(retData.pkgList[0]).toEqual({
2952
- group: "swift-certificates",
2953
2953
  name: "swift-certificates",
2954
+ group: "",
2955
+ purl: "pkg:swift/swift-certificates@unspecified",
2954
2956
  version: "unspecified",
2955
2957
  properties: [
2956
2958
  {
@@ -2959,36 +2961,37 @@ test("parse swift deps files", () => {
2959
2961
  },
2960
2962
  { name: "SrcFile", value: "./test/data/swift-deps.json" }
2961
2963
  ],
2962
- "bom-ref": "pkg:swift/swift-certificates/swift-certificates@unspecified"
2964
+ "bom-ref": "pkg:swift/swift-certificates@unspecified"
2963
2965
  });
2964
2966
  expect(retData.dependenciesList).toEqual([
2965
2967
  {
2966
- ref: "pkg:swift/swift-certificates/swift-certificates@unspecified",
2967
- dependsOn: ["pkg:swift/swift-crypto/swift-crypto@2.4.0"]
2968
+ ref: "pkg:swift/github.com/apple/swift-docc-symbolkit@1.0.0",
2969
+ dependsOn: []
2968
2970
  },
2969
2971
  {
2970
- ref: "pkg:swift/swift-crypto/swift-crypto@2.4.0",
2971
- dependsOn: ["pkg:swift/swift-asn1/swift-asn1@0.7.0"]
2972
+ ref: "pkg:swift/github.com/apple/swift-docc-plugin@1.1.0",
2973
+ dependsOn: ["pkg:swift/github.com/apple/swift-docc-symbolkit@1.0.0"]
2972
2974
  },
2973
2975
  {
2974
- ref: "pkg:swift/swift-asn1/swift-asn1@0.7.0",
2975
- dependsOn: ["pkg:swift/swift-docc-plugin/SwiftDocCPlugin@1.1.0"]
2976
+ ref: "pkg:swift/github.com/apple/swift-asn1@0.7.0",
2977
+ dependsOn: ["pkg:swift/github.com/apple/swift-docc-plugin@1.1.0"]
2976
2978
  },
2977
2979
  {
2978
- ref: "pkg:swift/swift-docc-plugin/SwiftDocCPlugin@1.1.0",
2979
- dependsOn: ["pkg:swift/swift-docc-symbolkit/SymbolKit@1.0.0"]
2980
+ ref: "pkg:swift/github.com/apple/swift-crypto@2.4.0",
2981
+ dependsOn: ["pkg:swift/github.com/apple/swift-asn1@0.7.0"]
2980
2982
  },
2981
2983
  {
2982
- ref: "pkg:swift/swift-docc-symbolkit/SymbolKit@1.0.0",
2983
- dependsOn: []
2984
+ ref: "pkg:swift/swift-certificates@unspecified",
2985
+ dependsOn: ["pkg:swift/github.com/apple/swift-crypto@2.4.0"]
2984
2986
  }
2985
2987
  ]);
2986
2988
  let pkgList = parseSwiftResolved("./test/data/Package.resolved");
2987
2989
  expect(pkgList.length).toEqual(4);
2988
2990
  expect(pkgList[0]).toEqual({
2989
2991
  name: "swift-argument-parser",
2990
- group: "",
2992
+ group: "github.com/apple",
2991
2993
  version: "1.0.3",
2994
+ purl: "pkg:swift/github.com/apple/swift-argument-parser@1.0.3",
2992
2995
  properties: [{ name: "SrcFile", value: "./test/data/Package.resolved" }],
2993
2996
  evidence: {
2994
2997
  identity: {
@@ -3003,14 +3006,16 @@ test("parse swift deps files", () => {
3003
3006
  ]
3004
3007
  }
3005
3008
  },
3009
+ "bom-ref": "pkg:swift/github.com/apple/swift-argument-parser@1.0.3",
3006
3010
  repository: { url: "https://github.com/apple/swift-argument-parser" }
3007
3011
  });
3008
3012
  pkgList = parseSwiftResolved("./test/data/Package2.resolved");
3009
3013
  expect(pkgList.length).toEqual(4);
3010
3014
  expect(pkgList[0]).toEqual({
3011
3015
  name: "swift-argument-parser",
3012
- group: "",
3016
+ group: "github.com/apple",
3013
3017
  version: "1.2.2",
3018
+ purl: "pkg:swift/github.com/apple/swift-argument-parser@1.2.2",
3014
3019
  properties: [{ name: "SrcFile", value: "./test/data/Package2.resolved" }],
3015
3020
  evidence: {
3016
3021
  identity: {
@@ -3025,6 +3030,7 @@ test("parse swift deps files", () => {
3025
3030
  ]
3026
3031
  }
3027
3032
  },
3033
+ "bom-ref": "pkg:swift/github.com/apple/swift-argument-parser@1.2.2",
3028
3034
  repository: { url: "https://github.com/apple/swift-argument-parser.git" }
3029
3035
  });
3030
3036
  });