@cyclonedx/cdxgen 8.0.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/utils.js ADDED
@@ -0,0 +1,4284 @@
1
+ const glob = require("glob");
2
+ const os = require("os");
3
+ const path = require("path");
4
+ const parsePackageJsonName = require("parse-packagejson-name");
5
+ const fs = require("fs");
6
+ const got = require("got");
7
+ const convert = require("xml-js");
8
+ const licenseMapping = require("./lic-mapping.json");
9
+ const vendorAliases = require("./vendor-alias.json");
10
+ const spdxLicenses = require("./spdx-licenses.json");
11
+ const knownLicenses = require("./known-licenses.json");
12
+ const cheerio = require("cheerio");
13
+ const yaml = require("js-yaml");
14
+ const { spawnSync } = require("child_process");
15
+ const propertiesReader = require("properties-reader");
16
+ const semver = require("semver");
17
+ const StreamZip = require("node-stream-zip");
18
+ const ednDataLib = require("edn-data");
19
+ const { PackageURL } = require("packageurl-js");
20
+
21
+ // Debug mode flag
22
+ const DEBUG_MODE =
23
+ process.env.SCAN_DEBUG_MODE === "debug" ||
24
+ process.env.SHIFTLEFT_LOGGING_LEVEL === "debug";
25
+
26
+ // Metadata cache
27
+ let metadata_cache = {};
28
+
29
+ const MAX_LICENSE_ID_LENGTH = 100;
30
+
31
+ /**
32
+ * Method to get files matching a pattern
33
+ *
34
+ * @param {string} dirPath Root directory for search
35
+ * @param {string} pattern Glob pattern (eg: *.gradle)
36
+ */
37
+ const getAllFiles = function (dirPath, pattern) {
38
+ try {
39
+ return glob.sync(pattern, {
40
+ cwd: dirPath,
41
+ silent: true,
42
+ absolute: true,
43
+ nocase: true,
44
+ nodir: true,
45
+ strict: true,
46
+ dot: pattern.startsWith(".") ? true : false,
47
+ follow: false,
48
+ ignore: [
49
+ "node_modules",
50
+ ".hg",
51
+ ".git",
52
+ "venv",
53
+ "docs",
54
+ "examples",
55
+ "site-packages"
56
+ ]
57
+ });
58
+ } catch (err) {
59
+ if (DEBUG_MODE) {
60
+ console.error(err);
61
+ }
62
+ return [];
63
+ }
64
+ };
65
+ exports.getAllFiles = getAllFiles;
66
+
67
+ const toBase64 = (hexString) => {
68
+ return Buffer.from(hexString, "hex").toString("base64");
69
+ };
70
+
71
+ /**
72
+ * Performs a lookup + validation of the license specified in the
73
+ * package. If the license is a valid SPDX license ID, set the 'id'
74
+ * and url of the license object, otherwise, set the 'name' of the license
75
+ * object.
76
+ */
77
+ function getLicenses(pkg, format = "xml") {
78
+ let license = pkg.license && (pkg.license.type || pkg.license);
79
+ if (license) {
80
+ if (!Array.isArray(license)) {
81
+ license = [license];
82
+ }
83
+ return license
84
+ .map((l) => {
85
+ let licenseContent = {};
86
+ if (typeof l === "string" || l instanceof String) {
87
+ if (
88
+ spdxLicenses.some((v) => {
89
+ return l === v;
90
+ })
91
+ ) {
92
+ licenseContent.id = l;
93
+ licenseContent.url = "https://opensource.org/licenses/" + l;
94
+ } else if (l.startsWith("http")) {
95
+ if (!l.includes("opensource.org")) {
96
+ licenseContent.name = "CUSTOM";
97
+ }
98
+ if (l.includes("mit-license")) {
99
+ licenseContent.id = "MIT";
100
+ }
101
+ licenseContent.url = l;
102
+ } else {
103
+ licenseContent.name = l;
104
+ }
105
+ } else if (Object.keys(l).length) {
106
+ licenseContent = l;
107
+ } else {
108
+ return [];
109
+ }
110
+ if (!licenseContent.id) {
111
+ addLicenseText(pkg, l, licenseContent, format);
112
+ }
113
+ return licenseContent;
114
+ })
115
+ .map((l) => ({ license: l }));
116
+ }
117
+ return [];
118
+ }
119
+ exports.getLicenses = getLicenses;
120
+
121
+ /**
122
+ * Tries to find a file containing the license text based on commonly
123
+ * used naming and content types. If a candidate file is found, add
124
+ * the text to the license text object and stop.
125
+ */
126
+ function addLicenseText(pkg, l, licenseContent, format = "xml") {
127
+ let licenseFilenames = [
128
+ "LICENSE",
129
+ "License",
130
+ "license",
131
+ "LICENCE",
132
+ "Licence",
133
+ "licence",
134
+ "NOTICE",
135
+ "Notice",
136
+ "notice"
137
+ ];
138
+ let licenseContentTypes = {
139
+ "text/plain": "",
140
+ "text/txt": ".txt",
141
+ "text/markdown": ".md",
142
+ "text/xml": ".xml"
143
+ };
144
+ /* Loops over different name combinations starting from the license specified
145
+ naming (e.g., 'LICENSE.Apache-2.0') and proceeding towards more generic names. */
146
+ for (const licenseName of [`.${l}`, ""]) {
147
+ for (const licenseFilename of licenseFilenames) {
148
+ for (const [licenseContentType, fileExtension] of Object.entries(
149
+ licenseContentTypes
150
+ )) {
151
+ let licenseFilepath = `${pkg.realPath}/${licenseFilename}${licenseName}${fileExtension}`;
152
+ if (fs.existsSync(licenseFilepath)) {
153
+ licenseContent.text = readLicenseText(
154
+ licenseFilepath,
155
+ licenseContentType,
156
+ format
157
+ );
158
+ return;
159
+ }
160
+ }
161
+ }
162
+ }
163
+ }
164
+
165
+ /**
166
+ * Read the file from the given path to the license text object and includes
167
+ * content-type attribute, if not default. Returns the license text object.
168
+ */
169
+ function readLicenseText(licenseFilepath, licenseContentType, format = "xml") {
170
+ let licenseText = fs.readFileSync(licenseFilepath, "utf8");
171
+ if (licenseText) {
172
+ if (format === "xml") {
173
+ let licenseContentText = { "#cdata": licenseText };
174
+ if (licenseContentType !== "text/plain") {
175
+ licenseContentText["@content-type"] = licenseContentType;
176
+ }
177
+ return licenseContentText;
178
+ } else {
179
+ let licenseContentText = { content: licenseText };
180
+ if (licenseContentType !== "text/plain") {
181
+ licenseContentText["contentType"] = licenseContentType;
182
+ }
183
+ return licenseContentText;
184
+ }
185
+ }
186
+ return null;
187
+ }
188
+
189
+ /**
190
+ * Method to retrieve metadata for npm packages by querying npmjs
191
+ *
192
+ * @param {Array} pkgList Package list
193
+ */
194
+ const getNpmMetadata = async function (pkgList) {
195
+ const NPM_URL = "https://registry.npmjs.org/";
196
+ const cdepList = [];
197
+ for (const p of pkgList) {
198
+ try {
199
+ let key = p.name;
200
+ if (p.group && p.group !== "") {
201
+ let group = p.group;
202
+ if (!group.startsWith("@")) {
203
+ group = "@" + group;
204
+ }
205
+ key = group + "/" + p.name;
206
+ }
207
+ let body = {};
208
+ if (metadata_cache[key]) {
209
+ body = metadata_cache[key];
210
+ } else {
211
+ const res = await got.get(NPM_URL + key, {
212
+ responseType: "json"
213
+ });
214
+ body = res.body;
215
+ metadata_cache[key] = body;
216
+ }
217
+ p.description = body.description;
218
+ p.license = body.license;
219
+ if (body.repository && body.repository.url) {
220
+ p.repository = { url: body.repository.url };
221
+ }
222
+ if (body.homepage) {
223
+ p.homepage = { url: body.homepage };
224
+ }
225
+ cdepList.push(p);
226
+ } catch (err) {
227
+ cdepList.push(p);
228
+ if (DEBUG_MODE) {
229
+ console.error(err, p);
230
+ }
231
+ }
232
+ }
233
+ return cdepList;
234
+ };
235
+ exports.getNpmMetadata = getNpmMetadata;
236
+
237
+ const _getDepPkgList = async function (
238
+ pkgList,
239
+ dependenciesList,
240
+ depKeys,
241
+ pkg
242
+ ) {
243
+ if (pkg && pkg.dependencies) {
244
+ const pkgKeys = Object.keys(pkg.dependencies);
245
+ for (var k in pkgKeys) {
246
+ const name = pkgKeys[k];
247
+ const version = pkg.dependencies[name].version;
248
+ const purl = new PackageURL("npm", "", name, version, null, null);
249
+ const purlString = decodeURIComponent(purl.toString());
250
+ let scope = pkg.dependencies[name].dev === true ? "optional" : undefined;
251
+ const apkg = {
252
+ name,
253
+ version,
254
+ _integrity: pkg.dependencies[name].integrity,
255
+ scope
256
+ };
257
+ pkgList.push(apkg);
258
+ if (pkg.dependencies[name].dependencies) {
259
+ // Include child dependencies
260
+ const dependencies = pkg.dependencies[name].dependencies;
261
+ const pkgDepKeys = Object.keys(dependencies);
262
+ const deplist = [];
263
+ for (const j in pkgDepKeys) {
264
+ const depName = pkgDepKeys[j];
265
+ const depVersion = dependencies[depName].version;
266
+ const deppurl = new PackageURL(
267
+ "npm",
268
+ "",
269
+ depName,
270
+ depVersion,
271
+ null,
272
+ null
273
+ );
274
+ const deppurlString = decodeURIComponent(deppurl.toString());
275
+ deplist.push(deppurlString);
276
+ }
277
+ if (!depKeys[purlString]) {
278
+ dependenciesList.push({
279
+ ref: purlString,
280
+ dependsOn: deplist
281
+ });
282
+ depKeys[purlString] = true;
283
+ }
284
+ await _getDepPkgList(
285
+ pkgList,
286
+ dependenciesList,
287
+ depKeys,
288
+ pkg.dependencies[name]
289
+ );
290
+ } else {
291
+ if (!depKeys[purlString]) {
292
+ dependenciesList.push({
293
+ ref: purlString,
294
+ dependsOn: []
295
+ });
296
+ depKeys[purlString] = true;
297
+ }
298
+ }
299
+ }
300
+ }
301
+ return pkgList;
302
+ };
303
+
304
+ /**
305
+ * Parse nodejs package json file
306
+ *
307
+ * @param {string} pkgJsonFile package.json file
308
+ */
309
+ const parsePkgJson = async (pkgJsonFile) => {
310
+ const pkgList = [];
311
+ if (fs.existsSync(pkgJsonFile)) {
312
+ try {
313
+ const pkgData = JSON.parse(fs.readFileSync(pkgJsonFile, "utf8"));
314
+ const pkgIdentifier = parsePackageJsonName(pkgData.name);
315
+ pkgList.push({
316
+ name: pkgIdentifier.fullName || pkgData.name,
317
+ group: pkgIdentifier.scope || "",
318
+ version: pkgData.version,
319
+ properties: [
320
+ {
321
+ name: "SrcFile",
322
+ value: pkgJsonFile
323
+ }
324
+ ]
325
+ });
326
+ } catch (err) {
327
+ // continue regardless of error
328
+ }
329
+ }
330
+ if (process.env.FETCH_LICENSE) {
331
+ if (DEBUG_MODE) {
332
+ console.log(
333
+ `About to fetch license information for ${pkgList.length} packages`
334
+ );
335
+ }
336
+ return await getNpmMetadata(pkgList);
337
+ }
338
+ return pkgList;
339
+ };
340
+ exports.parsePkgJson = parsePkgJson;
341
+
342
+ /**
343
+ * Parse nodejs package lock file
344
+ *
345
+ * @param {string} pkgLockFile package-lock.json file
346
+ */
347
+ const parsePkgLock = async (pkgLockFile) => {
348
+ let pkgList = [];
349
+ let dependenciesList = [];
350
+ let depKeys = {};
351
+ let rootPkg = undefined;
352
+ if (fs.existsSync(pkgLockFile)) {
353
+ const lockData = JSON.parse(fs.readFileSync(pkgLockFile, "utf8"));
354
+ // lockfile v2 onwards
355
+ if (lockData.name && lockData.packages && lockData.packages[""]) {
356
+ // Build the initial dependency tree for the root package
357
+ rootPkg = {
358
+ group: "",
359
+ name: lockData.name,
360
+ version: lockData.version,
361
+ type: "application"
362
+ };
363
+ } else if (lockData.lockfileVersion === 1) {
364
+ let dirName = path.dirname(pkgLockFile);
365
+ const tmpA = dirName.split(path.sep);
366
+ dirName = tmpA[tmpA.length - 1];
367
+ // v1 lock file
368
+ rootPkg = {
369
+ group: "",
370
+ name: lockData.name || dirName,
371
+ version: lockData.version || "",
372
+ type: "application"
373
+ };
374
+ }
375
+ if (rootPkg) {
376
+ const purl = new PackageURL(
377
+ "application",
378
+ "",
379
+ rootPkg.name,
380
+ rootPkg.version,
381
+ null,
382
+ null
383
+ );
384
+ const purlString = decodeURIComponent(purl.toString());
385
+ rootPkg["bom-ref"] = purlString;
386
+ pkgList.push(rootPkg);
387
+ // npm ls command seems to include both dependencies and devDependencies
388
+ // For tree purposes, including only the dependencies should be enough
389
+ let rootPkgDeps = undefined;
390
+ if (
391
+ lockData.packages &&
392
+ lockData.packages[""] &&
393
+ lockData.packages[""].dependencies
394
+ ) {
395
+ rootPkgDeps =
396
+ Object.keys(lockData.packages[""].dependencies || {}) || [];
397
+ } else if (lockData.dependencies) {
398
+ rootPkgDeps = Object.keys(lockData.dependencies || {}) || [];
399
+ }
400
+ const deplist = [];
401
+ for (const rd of rootPkgDeps) {
402
+ let resolvedVersion = undefined;
403
+ if (lockData.packages) {
404
+ resolvedVersion = (lockData.packages[`node_modules/${rd}`] || {})
405
+ .version;
406
+ } else if (lockData.dependencies) {
407
+ resolvedVersion = lockData.dependencies[rd].version;
408
+ }
409
+ if (resolvedVersion) {
410
+ const dpurl = decodeURIComponent(
411
+ new PackageURL(
412
+ "npm",
413
+ "",
414
+ rd,
415
+ resolvedVersion,
416
+ null,
417
+ null
418
+ ).toString()
419
+ );
420
+ deplist.push(dpurl);
421
+ }
422
+ }
423
+ dependenciesList.push({
424
+ ref: purlString,
425
+ dependsOn: deplist
426
+ });
427
+ }
428
+ pkgList = await _getDepPkgList(
429
+ pkgList,
430
+ dependenciesList,
431
+ depKeys,
432
+ lockData
433
+ );
434
+ }
435
+ if (process.env.FETCH_LICENSE) {
436
+ if (DEBUG_MODE) {
437
+ console.log(
438
+ `About to fetch license information for ${pkgList.length} packages`
439
+ );
440
+ }
441
+ pkgList = await getNpmMetadata(pkgList);
442
+ return { pkgList, dependenciesList };
443
+ }
444
+ return {
445
+ pkgList,
446
+ dependenciesList
447
+ };
448
+ };
449
+ exports.parsePkgLock = parsePkgLock;
450
+
451
+ /**
452
+ * Given a lock file this method would return an Object with the identiy as the key and parsed name and value
453
+ * eg: "@actions/core@^1.2.6", "@actions/core@^1.6.0":
454
+ * version "1.6.0"
455
+ * would result in two entries
456
+ *
457
+ * @param {string} lockData Yarn Lockfile data
458
+ */
459
+ const yarnLockToIdentMap = function (lockData) {
460
+ const identMap = {};
461
+ let currentIdents = [];
462
+ lockData.split("\n").forEach((l) => {
463
+ if (l === "\n" || l.startsWith("#")) {
464
+ return;
465
+ }
466
+ // "@actions/core@^1.2.6", "@actions/core@^1.6.0":
467
+ if (!l.startsWith(" ") && l.trim().length > 0) {
468
+ const tmpA = l.replace(/["']/g, "").split(", ");
469
+ if (tmpA && tmpA.length) {
470
+ for (let s of tmpA) {
471
+ if (!s.startsWith("__")) {
472
+ if (s.endsWith(":")) {
473
+ s = s.substring(0, s.length - 1);
474
+ }
475
+ // Non-strict mode parsing
476
+ const match = s.match(/^(?:@([^/]+?)\/)?([^/]+?)(?:@(.+))?$/);
477
+ if (!match) {
478
+ continue;
479
+ }
480
+ let [, group, name, range] = match;
481
+ if (group) {
482
+ group = `${group}/`;
483
+ }
484
+ if (range.startsWith("npm:")) {
485
+ range = range.replace("npm:", "");
486
+ }
487
+ currentIdents.push(`${group || ""}${name}@${range}`);
488
+ }
489
+ }
490
+ }
491
+ } else if (l.startsWith(" version") && currentIdents.length) {
492
+ const tmpA = l.replace(/["']/g, "").split(" ");
493
+ const version = tmpA[tmpA.length - 1].trim();
494
+ for (const id of currentIdents) {
495
+ identMap[id] = version;
496
+ }
497
+ currentIdents = [];
498
+ }
499
+ });
500
+ return identMap;
501
+ };
502
+ exports.yarnLockToIdentMap = yarnLockToIdentMap;
503
+
504
+ /**
505
+ * Parse nodejs yarn lock file
506
+ *
507
+ * @param {string} yarnLockFile yarn.lock file
508
+ */
509
+ const parseYarnLock = async function (yarnLockFile) {
510
+ let pkgList = [];
511
+ const dependenciesList = [];
512
+ const depKeys = {};
513
+ if (fs.existsSync(yarnLockFile)) {
514
+ const lockData = fs.readFileSync(yarnLockFile, "utf8");
515
+ let name = "";
516
+ let group = "";
517
+ let version = "";
518
+ let integrity = "";
519
+ let depsMode = false;
520
+ let purlString = "";
521
+ let deplist = [];
522
+ // This would have the keys and the resolved version required to solve the dependency tree
523
+ const identMap = yarnLockToIdentMap(lockData);
524
+ lockData.split("\n").forEach((l) => {
525
+ if (l === "\n" || l.startsWith("#")) {
526
+ return;
527
+ }
528
+ if (!l.startsWith(" ")) {
529
+ // Create an entry for the package and reset variables
530
+ if (
531
+ name !== "" &&
532
+ version !== "" &&
533
+ (integrity !== "" || version.includes("local"))
534
+ ) {
535
+ // Create a purl ref for the current package
536
+ purlString = new PackageURL(
537
+ "npm",
538
+ group,
539
+ name,
540
+ version,
541
+ null,
542
+ null
543
+ ).toString();
544
+ pkgList.push({
545
+ group: group || "",
546
+ name: name,
547
+ version: version,
548
+ _integrity: integrity,
549
+ properties: [
550
+ {
551
+ name: "SrcFile",
552
+ value: yarnLockFile
553
+ }
554
+ ]
555
+ });
556
+ // Reset all the variables
557
+ group = "";
558
+ name = "";
559
+ version = "";
560
+ integrity = "";
561
+ }
562
+ if (purlString && purlString !== "" && !depKeys[purlString]) {
563
+ // Create an entry for dependencies
564
+ dependenciesList.push({
565
+ ref: decodeURIComponent(purlString),
566
+ dependsOn: deplist
567
+ });
568
+ depKeys[purlString] = true;
569
+ deplist = [];
570
+ purlString = "";
571
+ depsMode = false;
572
+ }
573
+ // Collect the group and the name
574
+ const tmpA = l.replace(/["']/g, "").split("@");
575
+ // ignore possible leading empty strings
576
+ if (tmpA[0] === "") {
577
+ tmpA.shift();
578
+ }
579
+ if (tmpA.length >= 2) {
580
+ const fullName = tmpA[0];
581
+ if (fullName.indexOf("/") > -1) {
582
+ const parts = fullName.split("/");
583
+ group = parts[0];
584
+ name = parts[1];
585
+ } else {
586
+ name = fullName;
587
+ }
588
+ }
589
+ } else if (name !== "" && l.startsWith(" dependencies:")) {
590
+ depsMode = true;
591
+ } else if (depsMode && l.startsWith(" ")) {
592
+ // Given "@actions/http-client" "^1.0.11"
593
+ // We need the resolved version from identMap
594
+ const tmpA = l.trim().replace(/["']/g, "").split(" ");
595
+ if (tmpA && tmpA.length === 2) {
596
+ let dgroupname = tmpA[0];
597
+ if (dgroupname.startsWith("@")) {
598
+ dgroupname = dgroupname.substring(1);
599
+ }
600
+ if (dgroupname.endsWith(":")) {
601
+ dgroupname = dgroupname.substring(0, dgroupname.length - 1);
602
+ }
603
+ const resolvedVersion = identMap[`${dgroupname}@${tmpA[1]}`];
604
+ const depPurlString = new PackageURL(
605
+ "npm",
606
+ null,
607
+ dgroupname,
608
+ resolvedVersion,
609
+ null,
610
+ null
611
+ ).toString();
612
+ deplist.push(decodeURIComponent(depPurlString));
613
+ }
614
+ } else if (name !== "") {
615
+ if (!l.startsWith(" ")) {
616
+ depsMode = false;
617
+ }
618
+ l = l.trim();
619
+ const parts = l.split(" ");
620
+ if (l.startsWith("version")) {
621
+ version = parts[1].replace(/"/g, "");
622
+ }
623
+ if (l.startsWith("integrity")) {
624
+ integrity = parts[1];
625
+ }
626
+ // checksum used by yarn 2/3 is hex encoded
627
+ if (l.startsWith("checksum")) {
628
+ integrity =
629
+ "sha512-" + Buffer.from(parts[1], "hex").toString("base64");
630
+ }
631
+ if (l.startsWith("resolved")) {
632
+ const tmpB = parts[1].split("#");
633
+ if (tmpB.length > 1) {
634
+ const digest = tmpB[1].replace(/"/g, "");
635
+ integrity = "sha256-" + digest;
636
+ }
637
+ }
638
+ }
639
+ });
640
+ }
641
+ if (process.env.FETCH_LICENSE) {
642
+ if (DEBUG_MODE) {
643
+ console.log(
644
+ `About to fetch license information for ${pkgList.length} packages`
645
+ );
646
+ }
647
+ pkgList = await getNpmMetadata(pkgList);
648
+ return {
649
+ pkgList,
650
+ dependenciesList
651
+ };
652
+ }
653
+ return {
654
+ pkgList,
655
+ dependenciesList
656
+ };
657
+ };
658
+ exports.parseYarnLock = parseYarnLock;
659
+
660
+ /**
661
+ * Parse nodejs shrinkwrap deps file
662
+ *
663
+ * @param {string} swFile shrinkwrap-deps.json file
664
+ */
665
+ const parseNodeShrinkwrap = async function (swFile) {
666
+ const pkgList = [];
667
+ if (fs.existsSync(swFile)) {
668
+ const lockData = JSON.parse(fs.readFileSync(swFile, "utf8"));
669
+ const pkgKeys = Object.keys(lockData);
670
+ for (var k in pkgKeys) {
671
+ const fullName = pkgKeys[k];
672
+ const integrity = lockData[fullName];
673
+ const parts = fullName.split("@");
674
+ if (parts && parts.length) {
675
+ let name = "";
676
+ let version = "";
677
+ let group = "";
678
+ if (parts.length === 2) {
679
+ name = parts[0];
680
+ version = parts[1];
681
+ } else if (parts.length === 3) {
682
+ if (parts[0] === "") {
683
+ let gnameparts = parts[1].split("/");
684
+ group = gnameparts[0];
685
+ name = gnameparts[1];
686
+ } else {
687
+ name = parts[0];
688
+ }
689
+ version = parts[2];
690
+ }
691
+ if (group !== "@types") {
692
+ pkgList.push({
693
+ group: group,
694
+ name: name,
695
+ version: version,
696
+ _integrity: integrity,
697
+ properties: [
698
+ {
699
+ name: "SrcFile",
700
+ value: swFile
701
+ }
702
+ ]
703
+ });
704
+ }
705
+ }
706
+ }
707
+ }
708
+ if (process.env.FETCH_LICENSE) {
709
+ if (DEBUG_MODE) {
710
+ console.log(
711
+ `About to fetch license information for ${pkgList.length} packages`
712
+ );
713
+ }
714
+ return await getNpmMetadata(pkgList);
715
+ }
716
+ return pkgList;
717
+ };
718
+ exports.parseNodeShrinkwrap = parseNodeShrinkwrap;
719
+
720
+ /**
721
+ * Parse nodejs pnpm lock file
722
+ *
723
+ * @param {string} pnpmLock pnpm-lock.yaml file
724
+ */
725
+ const parsePnpmLock = async function (pnpmLock, parentComponent = null) {
726
+ let pkgList = [];
727
+ const dependenciesList = [];
728
+ let ppurl = "";
729
+ if (parentComponent && parentComponent.name) {
730
+ ppurl =
731
+ parentComponent.purl ||
732
+ new PackageURL(
733
+ "application",
734
+ parentComponent.group,
735
+ parentComponent.name,
736
+ parentComponent.version,
737
+ null,
738
+ null
739
+ ).toString();
740
+ }
741
+ if (fs.existsSync(pnpmLock)) {
742
+ const lockData = fs.readFileSync(pnpmLock, "utf8");
743
+ const yamlObj = yaml.load(lockData);
744
+ if (!yamlObj) {
745
+ return {};
746
+ }
747
+ // This logic matches the pnpm list command to include only direct dependencies
748
+ if (ppurl !== "") {
749
+ const ddeps = yamlObj.dependencies || {};
750
+ const ddeplist = [];
751
+ for (const dk of Object.keys(ddeps)) {
752
+ const dpurl = new PackageURL(
753
+ "npm",
754
+ "",
755
+ dk,
756
+ ddeps[dk],
757
+ null,
758
+ null
759
+ ).toString();
760
+ ddeplist.push(decodeURIComponent(dpurl));
761
+ }
762
+ dependenciesList.push({
763
+ ref: decodeURIComponent(ppurl),
764
+ dependsOn: ddeplist
765
+ });
766
+ }
767
+ const packages = yamlObj.packages;
768
+ const pkgKeys = Object.keys(packages);
769
+ for (var k in pkgKeys) {
770
+ // Eg: @babel/code-frame/7.10.1
771
+ const fullName = pkgKeys[k].replace("/@", "@");
772
+ const parts = fullName.split("/");
773
+ const integrity = packages[pkgKeys[k]].resolution.integrity;
774
+ const deps = packages[pkgKeys[k]].dependencies || [];
775
+ let scope = packages[pkgKeys[k]].dev === true ? "optional" : undefined;
776
+ if (parts && parts.length) {
777
+ let name = "";
778
+ let version = "";
779
+ let group = "";
780
+ if (parts.length === 2) {
781
+ name = parts[0];
782
+ version = parts[1];
783
+ } else if (parts.length === 3) {
784
+ group = parts[0];
785
+ name = parts[1];
786
+ version = parts[2];
787
+ }
788
+ if (group !== "@types" && name.indexOf("file:") !== 0) {
789
+ const purlString = new PackageURL(
790
+ "npm",
791
+ group,
792
+ name,
793
+ version,
794
+ null,
795
+ null
796
+ ).toString();
797
+ const deplist = [];
798
+ for (const dpkgName of Object.keys(deps)) {
799
+ const dpurlString = new PackageURL(
800
+ "npm",
801
+ "",
802
+ dpkgName,
803
+ deps[dpkgName],
804
+ null,
805
+ null
806
+ ).toString();
807
+ deplist.push(decodeURIComponent(dpurlString));
808
+ }
809
+ dependenciesList.push({
810
+ ref: decodeURIComponent(purlString),
811
+ dependsOn: deplist
812
+ });
813
+ pkgList.push({
814
+ group: group,
815
+ name: name,
816
+ version: version,
817
+ scope,
818
+ _integrity: integrity,
819
+ properties: [
820
+ {
821
+ name: "SrcFile",
822
+ value: pnpmLock
823
+ }
824
+ ]
825
+ });
826
+ }
827
+ }
828
+ }
829
+ }
830
+ if (process.env.FETCH_LICENSE) {
831
+ if (DEBUG_MODE) {
832
+ console.log(
833
+ `About to fetch license information for ${pkgList.length} packages`
834
+ );
835
+ }
836
+ pkgList = await getNpmMetadata(pkgList);
837
+ return {
838
+ pkgList,
839
+ dependenciesList
840
+ };
841
+ }
842
+ return {
843
+ pkgList,
844
+ dependenciesList
845
+ };
846
+ };
847
+ exports.parsePnpmLock = parsePnpmLock;
848
+
849
+ /**
850
+ * Parse bower json file
851
+ *
852
+ * @param {string} bowerJsonFile bower.json file
853
+ */
854
+ const parseBowerJson = async (bowerJsonFile) => {
855
+ const pkgList = [];
856
+ if (fs.existsSync(bowerJsonFile)) {
857
+ try {
858
+ const pkgData = JSON.parse(fs.readFileSync(bowerJsonFile, "utf8"));
859
+ const pkgIdentifier = parsePackageJsonName(pkgData.name);
860
+ pkgList.push({
861
+ name: pkgIdentifier.fullName || pkgData.name,
862
+ group: pkgIdentifier.scope || "",
863
+ version: pkgData.version || "",
864
+ description: pkgData.description || "",
865
+ license: pkgData.license || "",
866
+ properties: [
867
+ {
868
+ name: "SrcFile",
869
+ value: bowerJsonFile
870
+ }
871
+ ]
872
+ });
873
+ } catch (err) {
874
+ // continue regardless of error
875
+ }
876
+ }
877
+ if (process.env.FETCH_LICENSE) {
878
+ if (DEBUG_MODE) {
879
+ console.log(
880
+ `About to fetch license information for ${pkgList.length} packages`
881
+ );
882
+ }
883
+ return await getNpmMetadata(pkgList);
884
+ }
885
+ return pkgList;
886
+ };
887
+ exports.parseBowerJson = parseBowerJson;
888
+
889
+ /**
890
+ * Parse minified js file
891
+ *
892
+ * @param {string} minJsFile min.js file
893
+ */
894
+ const parseMinJs = async (minJsFile) => {
895
+ const pkgList = [];
896
+ if (fs.existsSync(minJsFile)) {
897
+ try {
898
+ const rawData = fs.readFileSync(minJsFile, { encoding: "utf-8" });
899
+ const tmpA = rawData.split("\n");
900
+ tmpA.forEach((l) => {
901
+ if ((l.startsWith("/*!") || l.startsWith(" * ")) && l.length < 500) {
902
+ let delimiter = " * ";
903
+ if (!l.includes(delimiter) && l.includes("/*!")) {
904
+ delimiter = "/*!";
905
+ }
906
+ if (!l.includes(delimiter) && l.includes(" - ")) {
907
+ delimiter = " - ";
908
+ }
909
+ const tmpPV = l.split(delimiter);
910
+ if (!tmpPV || tmpPV.length < 2) {
911
+ return;
912
+ }
913
+ // Eg: jQuery v3.6.0
914
+ const pkgNameVer = tmpPV[1]
915
+ .replace("/*!", "")
916
+ .replace(" * ", "")
917
+ .trim();
918
+ const tmpB = pkgNameVer.includes(" - ")
919
+ ? pkgNameVer.split(" - ")
920
+ : pkgNameVer.split(" ");
921
+ if (tmpB && tmpB.length > 1) {
922
+ // Fix #223 - lowercase parsed package name
923
+ let name = tmpB[0].replace(/ /g, "-").trim().toLowerCase();
924
+ if (
925
+ ["copyright", "author", "licensed"].includes(name.toLowerCase())
926
+ ) {
927
+ return;
928
+ }
929
+ const pkgIdentifier = parsePackageJsonName(name);
930
+ if (pkgIdentifier.fullName != "") {
931
+ pkgList.push({
932
+ name: pkgIdentifier.fullName,
933
+ group: pkgIdentifier.scope || "",
934
+ version: tmpB[1].replace(/^v/, "") || "",
935
+ properties: [
936
+ {
937
+ name: "SrcFile",
938
+ value: minJsFile
939
+ }
940
+ ]
941
+ });
942
+ }
943
+ return;
944
+ }
945
+ }
946
+ });
947
+ } catch (err) {
948
+ // continue regardless of error
949
+ }
950
+ }
951
+ if (process.env.FETCH_LICENSE) {
952
+ if (DEBUG_MODE) {
953
+ console.log(
954
+ `About to fetch license information for ${pkgList.length} packages`
955
+ );
956
+ }
957
+ return await getNpmMetadata(pkgList);
958
+ }
959
+ return pkgList;
960
+ };
961
+ exports.parseMinJs = parseMinJs;
962
+
963
+ /**
964
+ * Parse pom file
965
+ *
966
+ * @param {string} pom file to parse
967
+ */
968
+ const parsePom = function (pomFile) {
969
+ const deps = [];
970
+ const xmlData = fs.readFileSync(pomFile);
971
+ const project = convert.xml2js(xmlData, {
972
+ compact: true,
973
+ spaces: 4,
974
+ textKey: "_",
975
+ attributesKey: "$",
976
+ commentKey: "value"
977
+ }).project;
978
+ if (project && project.dependencies) {
979
+ let dependencies = project.dependencies.dependency;
980
+ // Convert to an array
981
+ if (dependencies && !Array.isArray(dependencies)) {
982
+ dependencies = [dependencies];
983
+ }
984
+ for (let adep of dependencies) {
985
+ const version = adep.version;
986
+ let versionStr = undefined;
987
+ if (version && version._ && version._.indexOf("$") == -1) {
988
+ versionStr = version._;
989
+ deps.push({
990
+ group: adep.groupId ? adep.groupId._ : "",
991
+ name: adep.artifactId ? adep.artifactId._ : "",
992
+ version: versionStr,
993
+ qualifiers: { type: "jar" },
994
+ properties: [
995
+ {
996
+ name: "SrcFile",
997
+ value: pomFile
998
+ }
999
+ ]
1000
+ });
1001
+ }
1002
+ }
1003
+ }
1004
+ return deps;
1005
+ };
1006
+ exports.parsePom = parsePom;
1007
+
1008
+ /**
1009
+ * Parse maven tree output
1010
+ * @param {string} rawOutput Raw string output
1011
+ */
1012
+ const parseMavenTree = function (rawOutput) {
1013
+ if (!rawOutput) {
1014
+ return [];
1015
+ }
1016
+ const deps = [];
1017
+ const dependenciesList = [];
1018
+ const keys_cache = {};
1019
+ const level_trees = {};
1020
+ const tmpA = rawOutput.split("\n");
1021
+ let last_level = 0;
1022
+ let last_purl = "";
1023
+ let stack = [];
1024
+ tmpA.forEach((l) => {
1025
+ if (l.endsWith(":test")) {
1026
+ return;
1027
+ }
1028
+ let level = 0;
1029
+ const tmpline = l.split(" ");
1030
+ if (tmpline && tmpline.length) {
1031
+ if (l.includes(" ")) {
1032
+ level = l.replace(tmpline[tmpline.length - 1], "").length / 3;
1033
+ }
1034
+ l = tmpline[tmpline.length - 1];
1035
+ const pkgArr = l.split(":");
1036
+ if (pkgArr && pkgArr.length > 2) {
1037
+ let versionStr = pkgArr[pkgArr.length - 2];
1038
+ if (pkgArr.length == 4) {
1039
+ versionStr = pkgArr[pkgArr.length - 1];
1040
+ }
1041
+ const key = pkgArr[0] + "-" + pkgArr[1] + "-" + versionStr;
1042
+ if (!keys_cache[key]) {
1043
+ keys_cache[key] = key;
1044
+ let purlString = new PackageURL(
1045
+ "maven",
1046
+ pkgArr[0],
1047
+ pkgArr[1],
1048
+ versionStr,
1049
+ { type: "jar" },
1050
+ null
1051
+ ).toString();
1052
+ purlString = decodeURIComponent(purlString);
1053
+ deps.push({
1054
+ group: pkgArr[0],
1055
+ name: pkgArr[1],
1056
+ version: versionStr,
1057
+ qualifiers: { type: "jar" }
1058
+ });
1059
+ if (!level_trees[purlString]) {
1060
+ level_trees[purlString] = [];
1061
+ }
1062
+ if (level == 0 || last_purl === "") {
1063
+ stack.push(purlString);
1064
+ } else if (level > last_level) {
1065
+ const cnodes = level_trees[last_purl] || [];
1066
+ cnodes.push(purlString);
1067
+ level_trees[last_purl] = cnodes;
1068
+ if (stack[stack.length - 1] !== purlString) {
1069
+ stack.push(purlString);
1070
+ }
1071
+ } else {
1072
+ for (let i = level; i <= last_level; i++) {
1073
+ stack.pop();
1074
+ }
1075
+ const last_stack = stack[stack.length - 1];
1076
+ const cnodes = level_trees[last_stack] || [];
1077
+ cnodes.push(purlString);
1078
+ level_trees[last_stack] = cnodes;
1079
+ stack.push(purlString);
1080
+ }
1081
+ last_level = level;
1082
+ last_purl = purlString;
1083
+ }
1084
+ }
1085
+ }
1086
+ });
1087
+ for (const lk of Object.keys(level_trees)) {
1088
+ dependenciesList.push({
1089
+ ref: lk,
1090
+ dependsOn: level_trees[lk]
1091
+ });
1092
+ }
1093
+ return {
1094
+ pkgList: deps,
1095
+ dependenciesList
1096
+ };
1097
+ };
1098
+ exports.parseMavenTree = parseMavenTree;
1099
+
1100
+ /**
1101
+ * Parse gradle dependencies output
1102
+ * @param {string} rawOutput Raw string output
1103
+ */
1104
+ const parseGradleDep = function (rawOutput) {
1105
+ if (typeof rawOutput === "string") {
1106
+ let match = "";
1107
+ // To render dependency tree we need a root project
1108
+ const rootProject = {
1109
+ group: "",
1110
+ name: "root",
1111
+ version: "latest",
1112
+ type: "maven",
1113
+ qualifiers: { type: "jar" }
1114
+ };
1115
+ const deps = [rootProject];
1116
+ const dependenciesList = [];
1117
+ const keys_cache = {};
1118
+ let last_level = 0;
1119
+ let last_purl = "pkg:maven/root@latest?type=jar";
1120
+ const level_trees = {};
1121
+ level_trees[last_purl] = [];
1122
+ let stack = [last_purl];
1123
+ const depRegex =
1124
+ /^.*?--- +(?<group>[^\s:]+):(?<name>[^\s:]+)(?::(?:{strictly )?(?<versionspecified>[^\s:}]+))?(?:})?(?: +-> +(?<versionoverride>[^\s:]+))?/gm;
1125
+ while ((match = depRegex.exec(rawOutput))) {
1126
+ const [line, group, name, versionspecified, versionoverride] = match;
1127
+ const version = versionoverride || versionspecified;
1128
+ const level = line.split(group)[0].length / 5;
1129
+ if (version !== undefined) {
1130
+ let purlString = new PackageURL(
1131
+ "maven",
1132
+ group,
1133
+ name,
1134
+ version,
1135
+ { type: "jar" },
1136
+ null
1137
+ ).toString();
1138
+ purlString = decodeURIComponent(purlString);
1139
+ // Filter duplicates
1140
+ if (!keys_cache[purlString]) {
1141
+ keys_cache[purlString] = true;
1142
+ if (group !== "project") {
1143
+ deps.push({
1144
+ group,
1145
+ name: name,
1146
+ version: version,
1147
+ qualifiers: { type: "jar" }
1148
+ });
1149
+ if (!level_trees[purlString]) {
1150
+ level_trees[purlString] = [];
1151
+ }
1152
+ if (level == 0 || last_purl === "") {
1153
+ stack.push(purlString);
1154
+ } else if (level > last_level) {
1155
+ const cnodes = level_trees[last_purl] || [];
1156
+ cnodes.push(purlString);
1157
+ level_trees[last_purl] = cnodes;
1158
+ if (stack[stack.length - 1] !== purlString) {
1159
+ stack.push(purlString);
1160
+ }
1161
+ } else {
1162
+ for (let i = level; i <= last_level; i++) {
1163
+ stack.pop();
1164
+ }
1165
+ const last_stack = stack[stack.length - 1];
1166
+ const cnodes = level_trees[last_stack] || [];
1167
+ cnodes.push(purlString);
1168
+ level_trees[last_stack] = cnodes;
1169
+ stack.push(purlString);
1170
+ }
1171
+ last_level = level;
1172
+ last_purl = purlString;
1173
+ }
1174
+ }
1175
+ }
1176
+ }
1177
+ for (const lk of Object.keys(level_trees)) {
1178
+ dependenciesList.push({
1179
+ ref: lk,
1180
+ dependsOn: level_trees[lk]
1181
+ });
1182
+ }
1183
+ return {
1184
+ pkgList: deps,
1185
+ dependenciesList
1186
+ };
1187
+ }
1188
+ return {};
1189
+ };
1190
+ exports.parseGradleDep = parseGradleDep;
1191
+
1192
+ /**
1193
+ * Parse clojure cli dependencies output
1194
+ * @param {string} rawOutput Raw string output
1195
+ */
1196
+ const parseCljDep = function (rawOutput) {
1197
+ if (typeof rawOutput === "string") {
1198
+ const deps = [];
1199
+ const keys_cache = {};
1200
+ const tmpA = rawOutput.split("\n");
1201
+ tmpA.forEach((l) => {
1202
+ l = l.trim();
1203
+ if (!l.startsWith("Downloading") || !l.startsWith("X ")) {
1204
+ if (l.startsWith(". ")) {
1205
+ l = l.replace(". ", "");
1206
+ }
1207
+ const tmpArr = l.split(" ");
1208
+ if (tmpArr.length == 2) {
1209
+ let group = path.dirname(tmpArr[0]);
1210
+ if (group === ".") {
1211
+ group = "";
1212
+ }
1213
+ const name = path.basename(tmpArr[0]);
1214
+ const version = tmpArr[1];
1215
+ const cacheKey = group + "-" + name + "-" + version;
1216
+ if (!keys_cache[cacheKey]) {
1217
+ keys_cache[cacheKey] = true;
1218
+ deps.push({
1219
+ group,
1220
+ name,
1221
+ version
1222
+ });
1223
+ }
1224
+ }
1225
+ }
1226
+ });
1227
+ return deps;
1228
+ }
1229
+ return [];
1230
+ };
1231
+ exports.parseCljDep = parseCljDep;
1232
+
1233
+ /**
1234
+ * Parse lein dependency tree output
1235
+ * @param {string} rawOutput Raw string output
1236
+ */
1237
+ const parseLeinDep = function (rawOutput) {
1238
+ if (typeof rawOutput === "string") {
1239
+ const deps = [];
1240
+ const keys_cache = {};
1241
+ if (rawOutput.includes("{[") && !rawOutput.startsWith("{[")) {
1242
+ rawOutput = "{[" + rawOutput.split("{[")[1];
1243
+ }
1244
+ const ednData = ednDataLib.parseEDNString(rawOutput);
1245
+ return parseLeinMap(ednData, keys_cache, deps);
1246
+ }
1247
+ return [];
1248
+ };
1249
+ exports.parseLeinDep = parseLeinDep;
1250
+
1251
+ const parseLeinMap = function (node, keys_cache, deps) {
1252
+ if (node["map"]) {
1253
+ for (let n of node["map"]) {
1254
+ if (n.length === 2) {
1255
+ const rootNode = n[0];
1256
+ let psym = rootNode[0].sym;
1257
+ let version = rootNode[1];
1258
+ let group = path.dirname(psym);
1259
+ if (group === ".") {
1260
+ group = "";
1261
+ }
1262
+ let name = path.basename(psym);
1263
+ let cacheKey = group + "-" + name + "-" + version;
1264
+ if (!keys_cache[cacheKey]) {
1265
+ keys_cache[cacheKey] = true;
1266
+ deps.push({ group, name, version });
1267
+ }
1268
+ if (n[1]) {
1269
+ parseLeinMap(n[1], keys_cache, deps);
1270
+ }
1271
+ }
1272
+ }
1273
+ }
1274
+ return deps;
1275
+ };
1276
+ exports.parseLeinMap = parseLeinMap;
1277
+
1278
+ /**
1279
+ * Parse gradle projects output
1280
+ * @param {string} rawOutput Raw string output
1281
+ */
1282
+ const parseGradleProjects = function (rawOutput) {
1283
+ if (typeof rawOutput === "string") {
1284
+ const projects = [];
1285
+ const tmpA = rawOutput.split("\n");
1286
+ tmpA.forEach((l) => {
1287
+ if (l.startsWith("+--- Project") || l.startsWith("\\--- Project")) {
1288
+ let projName = l
1289
+ .replace("+--- Project ", "")
1290
+ .replace("\\--- Project ", "")
1291
+ .split(" ")[0];
1292
+ projName = projName.replace(/'/g, "");
1293
+ if (
1294
+ !projName.startsWith(":test") &&
1295
+ !projName.startsWith(":docs") &&
1296
+ !projName.startsWith(":qa")
1297
+ ) {
1298
+ projects.push(projName);
1299
+ }
1300
+ }
1301
+ });
1302
+ return projects;
1303
+ }
1304
+ return [];
1305
+ };
1306
+ exports.parseGradleProjects = parseGradleProjects;
1307
+
1308
+ /**
1309
+ * Parse bazel skyframe state output
1310
+ * @param {string} rawOutput Raw string output
1311
+ */
1312
+ const parseBazelSkyframe = function (rawOutput) {
1313
+ if (typeof rawOutput === "string") {
1314
+ const deps = [];
1315
+ const keys_cache = {};
1316
+ const tmpA = rawOutput.split("\n");
1317
+ tmpA.forEach((l) => {
1318
+ if (l.indexOf("external/maven") >= 0) {
1319
+ l = l.replace("arguments: ", "").replace(/"/g, "");
1320
+ // Skyframe could have duplicate entries
1321
+ if (l.includes("@@maven//")) {
1322
+ l = l.split(",")[0];
1323
+ }
1324
+ const mparts = l.split("external/maven/v1/");
1325
+ if (mparts && mparts[mparts.length - 1].endsWith(".jar")) {
1326
+ // Example
1327
+ // https/jcenter.bintray.com/com/google/guava/failureaccess/1.0.1/failureaccess-1.0.1.jar
1328
+ // https/repo1.maven.org/maven2/org/simpleflatmapper/sfm-util/8.2.2/header_sfmutil-8.2.2.jar
1329
+ const jarPath = mparts[mparts.length - 1];
1330
+ let jarPathParts = jarPath.split("/");
1331
+ if (jarPathParts.length) {
1332
+ // Remove the protocol, registry url and then file name
1333
+ let prefix_slice_count = 2;
1334
+ // Bug: #169
1335
+ if (l.includes("/maven2/")) {
1336
+ prefix_slice_count = 3;
1337
+ }
1338
+ jarPathParts = jarPathParts.slice(prefix_slice_count, -1);
1339
+ // The last part would be the version
1340
+ const version = jarPathParts[jarPathParts.length - 1];
1341
+ // Last but one would be the name
1342
+ const name = jarPathParts[jarPathParts.length - 2].toLowerCase();
1343
+ // Rest would be the group
1344
+ const group = jarPathParts.slice(0, -2).join(".").toLowerCase();
1345
+ const key = `${group}:${name}:${version}`;
1346
+ if (!keys_cache[key]) {
1347
+ keys_cache[key] = true;
1348
+ deps.push({
1349
+ group,
1350
+ name,
1351
+ version,
1352
+ qualifiers: { type: "jar" }
1353
+ });
1354
+ }
1355
+ }
1356
+ }
1357
+ }
1358
+ });
1359
+ return deps;
1360
+ }
1361
+ return [];
1362
+ };
1363
+ exports.parseBazelSkyframe = parseBazelSkyframe;
1364
+
1365
+ /**
1366
+ * Parse bazel BUILD file
1367
+ * @param {string} rawOutput Raw string output
1368
+ */
1369
+ const parseBazelBuild = function (rawOutput) {
1370
+ if (typeof rawOutput === "string") {
1371
+ const projs = [];
1372
+ const tmpA = rawOutput.split("\n");
1373
+ tmpA.forEach((l) => {
1374
+ if (l.includes("name =")) {
1375
+ const name = l.split("name =")[1].replace(/[","]/g, "").trim();
1376
+ if (!name.includes("test")) {
1377
+ projs.push(name);
1378
+ }
1379
+ }
1380
+ });
1381
+ return projs;
1382
+ }
1383
+ return [];
1384
+ };
1385
+ exports.parseBazelBuild = parseBazelBuild;
1386
+
1387
+ /**
1388
+ * Parse dependencies in Key:Value format
1389
+ */
1390
+ const parseKVDep = function (rawOutput) {
1391
+ if (typeof rawOutput === "string") {
1392
+ const deps = [];
1393
+ rawOutput.split("\n").forEach((l) => {
1394
+ const tmpA = l.split(":");
1395
+ if (tmpA.length === 3) {
1396
+ deps.push({
1397
+ group: tmpA[0],
1398
+ name: tmpA[1],
1399
+ version: tmpA[2],
1400
+ qualifiers: { type: "jar" }
1401
+ });
1402
+ } else if (tmpA.length === 2) {
1403
+ deps.push({
1404
+ group: "",
1405
+ name: tmpA[0],
1406
+ version: tmpA[1],
1407
+ qualifiers: { type: "jar" }
1408
+ });
1409
+ }
1410
+ });
1411
+ return deps;
1412
+ }
1413
+ return [];
1414
+ };
1415
+ exports.parseKVDep = parseKVDep;
1416
+
1417
+ /**
1418
+ * Method to find the spdx license id from name
1419
+ *
1420
+ * @param {string} name License full name
1421
+ */
1422
+ const findLicenseId = function (name) {
1423
+ for (let l of licenseMapping) {
1424
+ if (l.names.includes(name)) {
1425
+ return l.exp;
1426
+ }
1427
+ }
1428
+ return name && (name.includes("\n") || name.length > MAX_LICENSE_ID_LENGTH)
1429
+ ? guessLicenseId(name)
1430
+ : name;
1431
+ };
1432
+ exports.findLicenseId = findLicenseId;
1433
+
1434
+ /**
1435
+ * Method to guess the spdx license id from license contents
1436
+ *
1437
+ * @param {string} name License file contents
1438
+ */
1439
+ const guessLicenseId = function (content) {
1440
+ content = content.replace(/\n/g, " ");
1441
+ for (let l of licenseMapping) {
1442
+ for (let j in l.names) {
1443
+ if (content.toUpperCase().indexOf(l.names[j].toUpperCase()) > -1) {
1444
+ return l.exp;
1445
+ }
1446
+ }
1447
+ }
1448
+ return undefined;
1449
+ };
1450
+ exports.guessLicenseId = guessLicenseId;
1451
+
1452
+ /**
1453
+ * Method to retrieve metadata for maven packages by querying maven central
1454
+ *
1455
+ * @param {Array} pkgList Package list
1456
+ */
1457
+ const getMvnMetadata = async function (pkgList) {
1458
+ const MAVEN_CENTRAL_URL = "https://repo1.maven.org/maven2/";
1459
+ const ANDROID_MAVEN = "https://maven.google.com/";
1460
+ const cdepList = [];
1461
+ if (!pkgList || !pkgList.length) {
1462
+ return pkgList;
1463
+ }
1464
+ if (DEBUG_MODE) {
1465
+ console.log(`About to query maven for ${pkgList.length} packages`);
1466
+ }
1467
+ for (const p of pkgList) {
1468
+ // If the package already has key metadata skip querying maven
1469
+ if (p.group && p.name && p.version && !process.env.FETCH_LICENSE) {
1470
+ cdepList.push(p);
1471
+ continue;
1472
+ }
1473
+ let urlPrefix = MAVEN_CENTRAL_URL;
1474
+ // Ideally we should try one resolver after the other. But it increases the time taken
1475
+ if (p.group.indexOf("android") !== -1) {
1476
+ urlPrefix = ANDROID_MAVEN;
1477
+ }
1478
+ let groupPart = p.group.replace(/\./g, "/");
1479
+ // Querying maven requires a valid group name
1480
+ if (!groupPart || groupPart === "") {
1481
+ cdepList.push(p);
1482
+ continue;
1483
+ }
1484
+ const fullUrl =
1485
+ urlPrefix +
1486
+ groupPart +
1487
+ "/" +
1488
+ p.name +
1489
+ "/" +
1490
+ p.version +
1491
+ "/" +
1492
+ p.name +
1493
+ "-" +
1494
+ p.version +
1495
+ ".pom";
1496
+ try {
1497
+ if (DEBUG_MODE) {
1498
+ console.log(`Querying ${fullUrl}`);
1499
+ }
1500
+ const res = await got.get(fullUrl);
1501
+ const bodyJson = convert.xml2js(res.body, {
1502
+ compact: true,
1503
+ spaces: 4,
1504
+ textKey: "_",
1505
+ attributesKey: "$",
1506
+ commentKey: "value"
1507
+ }).project;
1508
+ if (bodyJson && bodyJson.licenses && bodyJson.licenses.license) {
1509
+ if (Array.isArray(bodyJson.licenses.license)) {
1510
+ p.license = bodyJson.licenses.license.map((l) => {
1511
+ return findLicenseId(l.name._);
1512
+ });
1513
+ } else if (Object.keys(bodyJson.licenses.license).length) {
1514
+ const l = bodyJson.licenses.license;
1515
+ p.license = [findLicenseId(l.name._)];
1516
+ }
1517
+ }
1518
+ p.publisher =
1519
+ bodyJson.organization && bodyJson.organization.name
1520
+ ? bodyJson.organization.name._
1521
+ : "";
1522
+ p.description = bodyJson.description ? bodyJson.description._ : "";
1523
+ if (bodyJson.scm && bodyJson.scm.url) {
1524
+ p.repository = { url: bodyJson.scm.url._ };
1525
+ }
1526
+ cdepList.push(p);
1527
+ } catch (err) {
1528
+ if (DEBUG_MODE) {
1529
+ console.log(
1530
+ "Unable to find metadata for",
1531
+ p.group,
1532
+ p.name,
1533
+ p.version,
1534
+ fullUrl
1535
+ );
1536
+ }
1537
+ cdepList.push(p);
1538
+ }
1539
+ }
1540
+ return cdepList;
1541
+ };
1542
+ exports.getMvnMetadata = getMvnMetadata;
1543
+
1544
+ /**
1545
+ * Method to parse python requires_dist attribute found in pypi setup.py
1546
+ *
1547
+ * @param requires_dist string
1548
+ */
1549
+ const parsePyRequiresDist = function (dist_string) {
1550
+ if (!dist_string) {
1551
+ return undefined;
1552
+ }
1553
+ const tmpA = dist_string.split(" ");
1554
+ let name = "";
1555
+ let version = "";
1556
+ if (!tmpA) {
1557
+ return undefined;
1558
+ } else if (tmpA.length == 1) {
1559
+ name = tmpA[0];
1560
+ } else if (tmpA.length > 1) {
1561
+ name = tmpA[0];
1562
+ let tmpVersion = tmpA[1];
1563
+ version = tmpVersion.split(",")[0].replace(/[();=&glt><]/g, "");
1564
+ }
1565
+ return {
1566
+ name,
1567
+ version
1568
+ };
1569
+ };
1570
+ exports.parsePyRequiresDist = parsePyRequiresDist;
1571
+
1572
+ /**
1573
+ * Method to retrieve metadata for python packages by querying pypi
1574
+ *
1575
+ * @param {Array} pkgList Package list
1576
+ * @param {Boolean} fetchIndirectDeps Should we also fetch data about indirect dependencies from pypi
1577
+ */
1578
+ const getPyMetadata = async function (pkgList, fetchIndirectDeps) {
1579
+ if (!process.env.FETCH_LICENSE && !fetchIndirectDeps) {
1580
+ return pkgList;
1581
+ }
1582
+ const PYPI_URL = "https://pypi.org/pypi/";
1583
+ let cdepList = [];
1584
+ let indirectDeps = [];
1585
+ for (const p of pkgList) {
1586
+ if (!p || !p.name) {
1587
+ continue;
1588
+ }
1589
+ try {
1590
+ if (p.name.includes("https")) {
1591
+ cdepList.push(p);
1592
+ continue;
1593
+ }
1594
+ // Some packages support extra modules
1595
+ if (p.name.includes("[")) {
1596
+ p.name = p.name.split("[")[0];
1597
+ }
1598
+ const res = await got.get(PYPI_URL + p.name + "/json", {
1599
+ responseType: "json"
1600
+ });
1601
+ const body = res.body;
1602
+ p.description = body.info.summary;
1603
+ p.license = findLicenseId(body.info.license);
1604
+ if (body.info.home_page) {
1605
+ if (body.info.home_page.indexOf("git") > -1) {
1606
+ p.repository = { url: body.info.home_page };
1607
+ } else {
1608
+ p.homepage = { url: body.info.home_page };
1609
+ }
1610
+ }
1611
+ // Use the latest version if none specified
1612
+ if (
1613
+ !p.version ||
1614
+ p.version.includes("*") ||
1615
+ p.version.includes("<") ||
1616
+ p.version.includes(">")
1617
+ ) {
1618
+ p.version = body.info.version;
1619
+ }
1620
+ const requires_dist = body.info.requires_dist;
1621
+ if (requires_dist && requires_dist.length) {
1622
+ indirectDeps = indirectDeps.concat(
1623
+ requires_dist.map(parsePyRequiresDist)
1624
+ );
1625
+ }
1626
+ if (body.releases && body.releases[p.version]) {
1627
+ const digest = body.releases[p.version][0].digests;
1628
+ if (digest["sha256"]) {
1629
+ p._integrity = "sha256-" + digest["sha256"];
1630
+ } else if (digest["md5"]) {
1631
+ p._integrity = "md5-" + digest["md5"];
1632
+ }
1633
+ }
1634
+ cdepList.push(p);
1635
+ } catch (err) {
1636
+ cdepList.push(p);
1637
+ if (DEBUG_MODE) {
1638
+ console.error(p.name, err);
1639
+ }
1640
+ }
1641
+ }
1642
+ if (indirectDeps.length && fetchIndirectDeps) {
1643
+ if (DEBUG_MODE) {
1644
+ console.log("Fetching metadata for indirect dependencies");
1645
+ }
1646
+ const extraList = await getPyMetadata(indirectDeps, false);
1647
+ cdepList = cdepList.concat(extraList);
1648
+ }
1649
+ return cdepList;
1650
+ };
1651
+ exports.getPyMetadata = getPyMetadata;
1652
+
1653
+ /**
1654
+ * Method to parse bdist_wheel metadata
1655
+ *
1656
+ * @param {Object} mData bdist_wheel metadata
1657
+ */
1658
+ const parseBdistMetadata = function (mData) {
1659
+ const pkg = {};
1660
+ mData.split("\n").forEach((l) => {
1661
+ if (l.indexOf("Name: ") > -1) {
1662
+ pkg.name = l.split("Name: ")[1];
1663
+ } else if (l.indexOf("Version: ") > -1) {
1664
+ pkg.version = l.split("Version: ")[1];
1665
+ } else if (l.indexOf("Summary: ") > -1) {
1666
+ pkg.description = l.split("Summary: ")[1];
1667
+ } else if (l.indexOf("Home-page: ") > -1) {
1668
+ pkg.homepage = { url: l.split("Home-page: ")[1] };
1669
+ } else if (l.indexOf("Project-URL: Source Code, ") > -1) {
1670
+ pkg.repository = { url: l.split("Project-URL: Source Code, ")[1] };
1671
+ } else if (l.indexOf("Author: ") > -1) {
1672
+ pkg.publisher = l.split("Author: ")[1];
1673
+ }
1674
+ });
1675
+ return [pkg];
1676
+ };
1677
+ exports.parseBdistMetadata = parseBdistMetadata;
1678
+
1679
+ /**
1680
+ * Method to parse pipfile.lock data
1681
+ *
1682
+ * @param {Object} lockData JSON data from Pipfile.lock
1683
+ */
1684
+ const parsePiplockData = async function (lockData) {
1685
+ const pkgList = [];
1686
+ Object.keys(lockData)
1687
+ .filter((i) => i !== "_meta")
1688
+ .forEach((k) => {
1689
+ const depBlock = lockData[k];
1690
+ Object.keys(depBlock).forEach((p) => {
1691
+ const pkg = depBlock[p];
1692
+ if (Object.prototype.hasOwnProperty.call(pkg, "version")) {
1693
+ let versionStr = pkg.version.replace("==", "");
1694
+ pkgList.push({ name: p, version: versionStr });
1695
+ }
1696
+ });
1697
+ });
1698
+ return await getPyMetadata(pkgList, false);
1699
+ };
1700
+ exports.parsePiplockData = parsePiplockData;
1701
+
1702
+ /**
1703
+ * Method to parse poetry.lock data
1704
+ *
1705
+ * @param {Object} lockData JSON data from poetry.lock
1706
+ */
1707
+ const parsePoetrylockData = async function (lockData) {
1708
+ const pkgList = [];
1709
+ let pkg = null;
1710
+ if (!lockData) {
1711
+ return pkgList;
1712
+ }
1713
+ lockData.split("\n").forEach((l) => {
1714
+ let key = null;
1715
+ let value = null;
1716
+ // Package section starts with this marker
1717
+ if (l.indexOf("[[package]]") > -1) {
1718
+ if (pkg && pkg.name && pkg.version) {
1719
+ pkgList.push(pkg);
1720
+ }
1721
+ pkg = {};
1722
+ }
1723
+ if (l.indexOf("=") > -1) {
1724
+ const tmpA = l.split("=");
1725
+ key = tmpA[0].trim();
1726
+ value = tmpA[1].trim().replace(/"/g, "");
1727
+ switch (key) {
1728
+ case "description":
1729
+ pkg.description = value;
1730
+ break;
1731
+ case "name":
1732
+ pkg.name = value;
1733
+ break;
1734
+ case "version":
1735
+ pkg.version = value;
1736
+ break;
1737
+ }
1738
+ }
1739
+ });
1740
+ return await getPyMetadata(pkgList, false);
1741
+ };
1742
+ exports.parsePoetrylockData = parsePoetrylockData;
1743
+
1744
+ /**
1745
+ * Method to parse requirements.txt data
1746
+ *
1747
+ * @param {Object} reqData Requirements.txt data
1748
+ */
1749
+ const parseReqFile = async function (reqData) {
1750
+ const pkgList = [];
1751
+ let fetchIndirectDeps = false;
1752
+ reqData.split("\n").forEach((l) => {
1753
+ if (!l.startsWith("#")) {
1754
+ if (l.indexOf("=") > -1) {
1755
+ let tmpA = l.split(/(==|<=|~=|>=)/);
1756
+ if (tmpA.includes("#")) {
1757
+ tmpA = tmpA.split("#")[0];
1758
+ }
1759
+ let versionStr = tmpA[tmpA.length - 1].trim().replace("*", "0");
1760
+ if (versionStr.indexOf(" ") > -1) {
1761
+ versionStr = versionStr.split(" ")[0];
1762
+ }
1763
+ if (versionStr === "0") {
1764
+ versionStr = null;
1765
+ }
1766
+ if (!tmpA[0].includes("=") && !tmpA[0].trim().includes(" ")) {
1767
+ pkgList.push({
1768
+ name: tmpA[0].trim(),
1769
+ version: versionStr
1770
+ });
1771
+ }
1772
+ } else if (/[>|[|@]/.test(l)) {
1773
+ let tmpA = l.split(/(>|\[|@)/);
1774
+ if (tmpA.includes("#")) {
1775
+ tmpA = tmpA.split("#")[0];
1776
+ }
1777
+ if (!tmpA[0].trim().includes(" ")) {
1778
+ pkgList.push({
1779
+ name: tmpA[0].trim(),
1780
+ version: null
1781
+ });
1782
+ }
1783
+ } else if (l) {
1784
+ if (l.includes("#")) {
1785
+ l = l.split("#")[0];
1786
+ }
1787
+ l = l.trim();
1788
+ if (!l.includes(" ")) {
1789
+ pkgList.push({
1790
+ name: l,
1791
+ version: null
1792
+ });
1793
+ }
1794
+ }
1795
+ }
1796
+ });
1797
+ return await getPyMetadata(pkgList, fetchIndirectDeps);
1798
+ };
1799
+ exports.parseReqFile = parseReqFile;
1800
+
1801
+ /**
1802
+ * Method to parse setup.py data
1803
+ *
1804
+ * @param {Object} setupPyData Contents of setup.py
1805
+ */
1806
+ const parseSetupPyFile = async function (setupPyData) {
1807
+ let lines = [];
1808
+ let requires_found = false;
1809
+ let should_break = false;
1810
+ setupPyData.split("\n").forEach((l) => {
1811
+ l = l.trim();
1812
+ if (l.includes("install_requires")) {
1813
+ l = l.replace("install_requires=[", "");
1814
+ requires_found = true;
1815
+ }
1816
+ if (l.length && requires_found && !should_break) {
1817
+ if (l.includes("]")) {
1818
+ should_break = true;
1819
+ l = l.replace("],", "").replace("]", "");
1820
+ }
1821
+ let tmpA = l.replace(/['"]/g, "").split(",");
1822
+ tmpA = tmpA.filter((v) => v.length);
1823
+ lines = lines.concat(tmpA);
1824
+ }
1825
+ });
1826
+ return await parseReqFile(lines.join("\n"));
1827
+ };
1828
+ exports.parseSetupPyFile = parseSetupPyFile;
1829
+
1830
+ /**
1831
+ * Method to construct a github url for the given repo
1832
+ * @param {Object} repoMetadata Repo metadata with group and name
1833
+ */
1834
+ const toGitHubUrl = function (repoMetadata) {
1835
+ if (repoMetadata) {
1836
+ const group = repoMetadata.group;
1837
+ const name = repoMetadata.name;
1838
+ let ghUrl = "https://github.com";
1839
+ if (group && group !== "." && group != "") {
1840
+ ghUrl = ghUrl + "/" + group.replace("github.com/", "");
1841
+ }
1842
+ ghUrl = ghUrl + "/" + name;
1843
+ return ghUrl;
1844
+ } else {
1845
+ return undefined;
1846
+ }
1847
+ };
1848
+ exports.toGitHubUrl = toGitHubUrl;
1849
+
1850
+ /**
1851
+ * Method to retrieve repo license by querying github api
1852
+ *
1853
+ * @param {String} repoUrl Repository url
1854
+ * @param {Object} repoMetadata Object containing group and package name strings
1855
+ * @return {String} SPDX license id
1856
+ */
1857
+ const getRepoLicense = async function (repoUrl, repoMetadata) {
1858
+ if (!repoUrl) {
1859
+ repoUrl = toGitHubUrl(repoMetadata);
1860
+ }
1861
+ // Perform github lookups
1862
+ if (repoUrl.indexOf("github.com") > -1) {
1863
+ let apiUrl = repoUrl.replace(
1864
+ "https://github.com",
1865
+ "https://api.github.com/repos"
1866
+ );
1867
+ apiUrl += "/license";
1868
+ const headers = {};
1869
+ if (process.env.GITHUB_TOKEN) {
1870
+ headers["Authorization"] = "Bearer " + process.env.GITHUB_TOKEN;
1871
+ }
1872
+ try {
1873
+ const res = await got.get(apiUrl, {
1874
+ responseType: "json",
1875
+ headers: headers
1876
+ });
1877
+ if (res && res.body) {
1878
+ const license = res.body.license;
1879
+ let licenseId = license.spdx_id;
1880
+ const licObj = {
1881
+ url: res.body.html_url
1882
+ };
1883
+ if (license.spdx_id === "NOASSERTION") {
1884
+ if (res.body.content) {
1885
+ const content = Buffer.from(res.body.content, "base64").toString(
1886
+ "ascii"
1887
+ );
1888
+ licenseId = guessLicenseId(content);
1889
+ }
1890
+ // If content match fails attempt to find by name
1891
+ if (!licenseId && license.name.toLowerCase() !== "other") {
1892
+ licenseId = findLicenseId(license.name);
1893
+ licObj["name"] = license.name;
1894
+ }
1895
+ }
1896
+ licObj["id"] = licenseId;
1897
+ return licObj;
1898
+ }
1899
+ } catch (err) {
1900
+ return undefined;
1901
+ }
1902
+ } else if (repoMetadata) {
1903
+ const group = repoMetadata.group;
1904
+ const name = repoMetadata.name;
1905
+ if (group && name) {
1906
+ for (let akLic of knownLicenses) {
1907
+ if (akLic.group === "." && akLic.name === name) {
1908
+ return akLic.license;
1909
+ } else if (
1910
+ group.includes(akLic.group) &&
1911
+ (akLic.name === name || akLic.name === "*")
1912
+ ) {
1913
+ return akLic.license;
1914
+ }
1915
+ }
1916
+ }
1917
+ }
1918
+ return undefined;
1919
+ };
1920
+ exports.getRepoLicense = getRepoLicense;
1921
+
1922
+ /**
1923
+ * Method to get go pkg license from go.dev site.
1924
+ *
1925
+ * @param {Object} repoMetadata Repo metadata
1926
+ */
1927
+ const getGoPkgLicense = async function (repoMetadata) {
1928
+ const group = repoMetadata.group;
1929
+ const name = repoMetadata.name;
1930
+ let pkgUrlPrefix = "https://pkg.go.dev/";
1931
+ if (group && group !== "." && group !== name) {
1932
+ pkgUrlPrefix = pkgUrlPrefix + group + "/";
1933
+ }
1934
+ pkgUrlPrefix = pkgUrlPrefix + name + "?tab=licenses";
1935
+ // Check the metadata cache first
1936
+ if (metadata_cache[pkgUrlPrefix]) {
1937
+ return metadata_cache[pkgUrlPrefix];
1938
+ }
1939
+ try {
1940
+ const res = await got.get(pkgUrlPrefix);
1941
+ if (res && res.body) {
1942
+ const $ = cheerio.load(res.body);
1943
+ let licenses = $("#LICENSE > h2").text().trim();
1944
+ if (licenses === "") {
1945
+ licenses = $("section.License > h2").text().trim();
1946
+ }
1947
+ const licenseIds = licenses.split(", ");
1948
+ const licList = [];
1949
+ for (let id of licenseIds) {
1950
+ const alicense = {
1951
+ id: id
1952
+ };
1953
+ alicense["url"] = pkgUrlPrefix;
1954
+ licList.push(alicense);
1955
+ }
1956
+ metadata_cache[pkgUrlPrefix] = licList;
1957
+ return licList;
1958
+ }
1959
+ } catch (err) {
1960
+ return undefined;
1961
+ }
1962
+ if (group.indexOf("github.com") > -1) {
1963
+ return await getRepoLicense(undefined, repoMetadata);
1964
+ }
1965
+ return undefined;
1966
+ };
1967
+ exports.getGoPkgLicense = getGoPkgLicense;
1968
+
1969
+ const getGoPkgComponent = async function (group, name, version, hash) {
1970
+ let pkg = {};
1971
+ let license = undefined;
1972
+ if (process.env.FETCH_LICENSE) {
1973
+ if (DEBUG_MODE) {
1974
+ console.log(
1975
+ `About to fetch go package license information for ${group}:${name}`
1976
+ );
1977
+ }
1978
+ license = await getGoPkgLicense({
1979
+ group: group,
1980
+ name: name
1981
+ });
1982
+ }
1983
+ pkg = {
1984
+ group: group,
1985
+ name: name,
1986
+ version: version,
1987
+ _integrity: hash,
1988
+ license: license
1989
+ };
1990
+ return pkg;
1991
+ };
1992
+ exports.getGoPkgComponent = getGoPkgComponent;
1993
+
1994
+ const parseGoModData = async function (goModData, gosumMap) {
1995
+ const pkgComponentsList = [];
1996
+ let isModReplacement = false;
1997
+
1998
+ if (!goModData) {
1999
+ return pkgComponentsList;
2000
+ }
2001
+
2002
+ const pkgs = goModData.split("\n");
2003
+ for (let l of pkgs) {
2004
+ // Skip go.mod file headers, whitespace, and/or comments
2005
+ if (
2006
+ l.startsWith("module ") ||
2007
+ l.startsWith("go ") ||
2008
+ l.includes(")") ||
2009
+ l.trim() === "" ||
2010
+ l.trim().startsWith("//")
2011
+ ) {
2012
+ continue;
2013
+ }
2014
+
2015
+ // Handle required modules separately from replacement modules to ensure accuracy when parsing component data.
2016
+ if (l.includes("require (")) {
2017
+ isModReplacement = false;
2018
+ continue;
2019
+ } else if (l.includes("replace (")) {
2020
+ isModReplacement = true;
2021
+ continue;
2022
+ } else if (l.includes("replace ")) {
2023
+ // If this is an inline replacement, drop the word replace
2024
+ // (eg; "replace google.golang.org/grpc => google.golang.org/grpc v1.21.0" becomes " google.golang.org/grpc => google.golang.org/grpc v1.21.0")
2025
+ l = l.replace("replace", "");
2026
+ isModReplacement = true;
2027
+ }
2028
+
2029
+ const tmpA = l.trim().split(" ");
2030
+
2031
+ if (!isModReplacement) {
2032
+ // Add group, name and version component properties for required modules
2033
+ let group = path.dirname(tmpA[0]);
2034
+ const name = path.basename(tmpA[0]);
2035
+ if (group === ".") {
2036
+ group = name;
2037
+ }
2038
+ const version = tmpA[1];
2039
+ let gosumHash = gosumMap[`${group}/${name}/${version}`];
2040
+ // The hash for this version was not found in go.sum, so skip as it is most likely being replaced.
2041
+ if (gosumHash === undefined) {
2042
+ continue;
2043
+ }
2044
+ let component = await getGoPkgComponent(group, name, version, gosumHash);
2045
+ pkgComponentsList.push(component);
2046
+ } else {
2047
+ // Add group, name and version component properties for replacement modules
2048
+ let group = path.dirname(tmpA[2]);
2049
+ const name = path.basename(tmpA[2]);
2050
+ if (group === ".") {
2051
+ group = name;
2052
+ }
2053
+ const version = tmpA[3];
2054
+
2055
+ let gosumHash = gosumMap[`${group}/${name}/${version}`];
2056
+ // The hash for this version was not found in go.sum, so skip.
2057
+ if (gosumHash === undefined) {
2058
+ continue;
2059
+ }
2060
+ let component = await getGoPkgComponent(group, name, version, gosumHash);
2061
+ pkgComponentsList.push(component);
2062
+ }
2063
+ }
2064
+ // Clear the cache
2065
+ metadata_cache = {};
2066
+ return pkgComponentsList;
2067
+ };
2068
+ exports.parseGoModData = parseGoModData;
2069
+
2070
+ /**
2071
+ * Parse go list output
2072
+ *
2073
+ * @param {string} rawOutput Output from go list invocation
2074
+ * @returns List of packages
2075
+ */
2076
+ const parseGoListDep = async function (rawOutput, gosumMap) {
2077
+ if (typeof rawOutput === "string") {
2078
+ const deps = [];
2079
+ const keys_cache = {};
2080
+ const pkgs = rawOutput.split("\n");
2081
+ for (let l of pkgs) {
2082
+ const verArr = l.trim().replace(new RegExp("[\"']", "g"), "").split(" ");
2083
+ if (verArr && verArr.length === 2) {
2084
+ const key = verArr[0] + "-" + verArr[1];
2085
+ // Filter duplicates
2086
+ if (!keys_cache[key]) {
2087
+ keys_cache[key] = key;
2088
+ let group = path.dirname(verArr[0]);
2089
+ const name = path.basename(verArr[0]);
2090
+ const version = verArr[1];
2091
+ if (group === ".") {
2092
+ group = name;
2093
+ }
2094
+ let gosumHash = gosumMap[`${group}/${name}/${version}`];
2095
+ let component = await getGoPkgComponent(
2096
+ group,
2097
+ name,
2098
+ version,
2099
+ gosumHash
2100
+ );
2101
+ deps.push(component);
2102
+ }
2103
+ }
2104
+ }
2105
+ return deps;
2106
+ }
2107
+ return [];
2108
+ };
2109
+ exports.parseGoListDep = parseGoListDep;
2110
+
2111
+ /**
2112
+ * Parse go mod why output
2113
+ * @param {string} rawOutput Output from go mod why
2114
+ * @returns package name or none
2115
+ */
2116
+ const parseGoModWhy = function (rawOutput) {
2117
+ if (typeof rawOutput === "string") {
2118
+ let pkg_name = undefined;
2119
+ const tmpA = rawOutput.split("\n");
2120
+ tmpA.forEach((l) => {
2121
+ if (l && !l.startsWith("#") && !l.startsWith("(")) {
2122
+ pkg_name = l.trim();
2123
+ }
2124
+ });
2125
+ return pkg_name;
2126
+ }
2127
+ return undefined;
2128
+ };
2129
+ exports.parseGoModWhy = parseGoModWhy;
2130
+
2131
+ const parseGosumData = async function (gosumData) {
2132
+ const pkgList = [];
2133
+ if (!gosumData) {
2134
+ return pkgList;
2135
+ }
2136
+ const pkgs = gosumData.split("\n");
2137
+ for (let l of pkgs) {
2138
+ // look for lines containing go.mod
2139
+ if (l.indexOf("go.mod") > -1) {
2140
+ const tmpA = l.split(" ");
2141
+ let group = path.dirname(tmpA[0]);
2142
+ const name = path.basename(tmpA[0]);
2143
+ if (group === ".") {
2144
+ group = name;
2145
+ }
2146
+ const version = tmpA[1].replace("/go.mod", "");
2147
+ const hash = tmpA[tmpA.length - 1].replace("h1:", "sha256-");
2148
+ let license = undefined;
2149
+ if (process.env.FETCH_LICENSE) {
2150
+ if (DEBUG_MODE) {
2151
+ console.log(
2152
+ `About to fetch go package license information for ${group}:${name}`
2153
+ );
2154
+ }
2155
+ license = await getGoPkgLicense({
2156
+ group: group,
2157
+ name: name
2158
+ });
2159
+ }
2160
+ pkgList.push({
2161
+ group: group,
2162
+ name: name,
2163
+ version: version,
2164
+ _integrity: hash,
2165
+ license: license
2166
+ });
2167
+ }
2168
+ }
2169
+ return pkgList;
2170
+ };
2171
+ exports.parseGosumData = parseGosumData;
2172
+
2173
+ const parseGopkgData = async function (gopkgData) {
2174
+ const pkgList = [];
2175
+ if (!gopkgData) {
2176
+ return pkgList;
2177
+ }
2178
+ let pkg = null;
2179
+ const pkgs = gopkgData.split("\n");
2180
+ for (let l of pkgs) {
2181
+ let key = null;
2182
+ let value = null;
2183
+ if (l.indexOf("[[projects]]") > -1) {
2184
+ if (pkg) {
2185
+ pkgList.push(pkg);
2186
+ }
2187
+ pkg = {};
2188
+ }
2189
+ if (l.indexOf("=") > -1) {
2190
+ const tmpA = l.split("=");
2191
+ key = tmpA[0].trim();
2192
+ value = tmpA[1].trim().replace(/"/g, "");
2193
+ let digestStr = undefined;
2194
+ switch (key) {
2195
+ case "digest":
2196
+ digestStr = value.replace("1:", "");
2197
+ pkg._integrity = "sha256-" + toBase64(digestStr);
2198
+ break;
2199
+ case "name":
2200
+ pkg.group = path.dirname(value);
2201
+ pkg.name = path.basename(value);
2202
+ if (pkg.group === ".") {
2203
+ pkg.group = pkg.name;
2204
+ }
2205
+ if (process.env.FETCH_LICENSE) {
2206
+ pkg.license = await getGoPkgLicense({
2207
+ group: pkg.group,
2208
+ name: pkg.name
2209
+ });
2210
+ }
2211
+ break;
2212
+ case "version":
2213
+ pkg.version = value;
2214
+ break;
2215
+ case "revision":
2216
+ if (!pkg.version) {
2217
+ pkg.version = value;
2218
+ }
2219
+ }
2220
+ }
2221
+ }
2222
+ return pkgList;
2223
+ };
2224
+ exports.parseGopkgData = parseGopkgData;
2225
+
2226
+ const parseGoVersionData = async function (buildInfoData) {
2227
+ const pkgList = [];
2228
+ if (!buildInfoData) {
2229
+ return pkgList;
2230
+ }
2231
+ const pkgs = buildInfoData.split("\n");
2232
+ for (let i in pkgs) {
2233
+ const l = pkgs[i].trim().replace(/\t/g, " ");
2234
+ if (!l.startsWith("dep")) {
2235
+ continue;
2236
+ }
2237
+ const tmpA = l.split(" ");
2238
+ if (!tmpA || tmpA.length < 3) {
2239
+ continue;
2240
+ }
2241
+ let group = path.dirname(tmpA[1].trim());
2242
+ const name = path.basename(tmpA[1].trim());
2243
+ if (group === ".") {
2244
+ group = name;
2245
+ }
2246
+ let hash = "";
2247
+ if (tmpA.length == 4) {
2248
+ hash = tmpA[tmpA.length - 1].replace("h1:", "sha256-");
2249
+ }
2250
+ let component = await getGoPkgComponent(group, name, tmpA[2].trim(), hash);
2251
+ pkgList.push(component);
2252
+ }
2253
+ return pkgList;
2254
+ };
2255
+ exports.parseGoVersionData = parseGoVersionData;
2256
+
2257
+ /**
2258
+ * Method to query rubygems api for gems details
2259
+ *
2260
+ * @param {*} pkgList List of packages with metadata
2261
+ */
2262
+ const getRubyGemsMetadata = async function (pkgList) {
2263
+ const RUBYGEMS_URL = "https://rubygems.org/api/v1/versions/";
2264
+ const rdepList = [];
2265
+ for (const p of pkgList) {
2266
+ try {
2267
+ if (DEBUG_MODE) {
2268
+ console.log(`Querying rubygems.org for ${p.name}`);
2269
+ }
2270
+ const res = await got.get(RUBYGEMS_URL + p.name + ".json", {
2271
+ responseType: "json"
2272
+ });
2273
+ let body = res.body;
2274
+ if (body && body.length) {
2275
+ body = body[0];
2276
+ }
2277
+ p.description = body.description || body.summary || "";
2278
+ if (body.licenses) {
2279
+ p.license = body.licenses;
2280
+ }
2281
+ if (body.metadata) {
2282
+ if (body.metadata.source_code_uri) {
2283
+ p.repository = { url: body.metadata.source_code_uri };
2284
+ }
2285
+ if (body.metadata.bug_tracker_uri) {
2286
+ p.homepage = { url: body.metadata.bug_tracker_uri };
2287
+ }
2288
+ }
2289
+ if (body.sha) {
2290
+ p._integrity = "sha256-" + body.sha;
2291
+ }
2292
+ // Use the latest version if none specified
2293
+ if (!p.version) {
2294
+ p.version = body.number;
2295
+ }
2296
+ rdepList.push(p);
2297
+ } catch (err) {
2298
+ rdepList.push(p);
2299
+ if (DEBUG_MODE) {
2300
+ console.error(p, err);
2301
+ }
2302
+ }
2303
+ }
2304
+ return rdepList;
2305
+ };
2306
+ exports.getRubyGemsMetadata = getRubyGemsMetadata;
2307
+
2308
+ /**
2309
+ * Method to parse Gemspec
2310
+ *
2311
+ * @param {*} gemspecData Gemspec data
2312
+ */
2313
+ const parseGemspecData = async function (gemspecData) {
2314
+ let pkgList = [];
2315
+ const pkg = {};
2316
+ if (!gemspecData) {
2317
+ return pkgList;
2318
+ }
2319
+ gemspecData.split("\n").forEach((l) => {
2320
+ l = l.trim();
2321
+ if (l.includes(".name = ")) {
2322
+ pkg.name = l
2323
+ .split(".name = ")[1]
2324
+ .replace(".freeze", "")
2325
+ .replace(/"/g, "");
2326
+ } else if (l.includes(".version = ")) {
2327
+ pkg.version = l
2328
+ .split(".version = ")[1]
2329
+ .replace(".freeze", "")
2330
+ .replace(/"/g, "");
2331
+ } else if (l.includes(".description = ")) {
2332
+ pkg.description = l
2333
+ .split(".description = ")[1]
2334
+ .replace(".freeze", "")
2335
+ .replace(/"/g, "");
2336
+ }
2337
+ });
2338
+ pkgList = [pkg];
2339
+ if (process.env.FETCH_LICENSE) {
2340
+ return await getRubyGemsMetadata(pkgList);
2341
+ } else {
2342
+ return pkgList;
2343
+ }
2344
+ };
2345
+ exports.parseGemspecData = parseGemspecData;
2346
+
2347
+ /**
2348
+ * Method to parse Gemfile.lock
2349
+ *
2350
+ * @param {*} gemLockData Gemfile.lock data
2351
+ */
2352
+ const parseGemfileLockData = async function (gemLockData) {
2353
+ const pkgList = [];
2354
+ const pkgnames = {};
2355
+ if (!gemLockData) {
2356
+ return pkgList;
2357
+ }
2358
+ let specsFound = false;
2359
+ gemLockData.split("\n").forEach((l) => {
2360
+ l = l.trim();
2361
+ if (specsFound) {
2362
+ const tmpA = l.split(" ");
2363
+ if (tmpA && tmpA.length == 2) {
2364
+ const name = tmpA[0];
2365
+ if (!pkgnames[name]) {
2366
+ let version = tmpA[1].split(", ")[0];
2367
+ version = version.replace(/[(>=<)~ ]/g, "");
2368
+ pkgList.push({
2369
+ name,
2370
+ version
2371
+ });
2372
+ pkgnames[name] = true;
2373
+ }
2374
+ }
2375
+ }
2376
+ if (l === "specs:") {
2377
+ specsFound = true;
2378
+ }
2379
+ if (
2380
+ l === "PLATFORMS" ||
2381
+ l === "DEPENDENCIES" ||
2382
+ l === "RUBY VERSION" ||
2383
+ l === "BUNDLED WITH"
2384
+ ) {
2385
+ specsFound = false;
2386
+ }
2387
+ });
2388
+ if (process.env.FETCH_LICENSE) {
2389
+ return await getRubyGemsMetadata(pkgList);
2390
+ } else {
2391
+ return pkgList;
2392
+ }
2393
+ };
2394
+ exports.parseGemfileLockData = parseGemfileLockData;
2395
+
2396
+ /**
2397
+ * Method to retrieve metadata for rust packages by querying crates
2398
+ *
2399
+ * @param {Array} pkgList Package list
2400
+ */
2401
+ const getCratesMetadata = async function (pkgList) {
2402
+ const CRATES_URL = "https://crates.io/api/v1/crates/";
2403
+ const cdepList = [];
2404
+ for (const p of pkgList) {
2405
+ try {
2406
+ if (DEBUG_MODE) {
2407
+ console.log(`Querying crates.io for ${p.name}`);
2408
+ }
2409
+ const res = await got.get(CRATES_URL + p.name, { responseType: "json" });
2410
+ const body = res.body.crate;
2411
+ p.description = body.description;
2412
+ if (res.body.versions) {
2413
+ const licenseString = res.body.versions[0].license;
2414
+ p.license = licenseString.split("/");
2415
+ }
2416
+ if (body.repository) {
2417
+ p.repository = { url: body.repository };
2418
+ }
2419
+ if (body.homepage) {
2420
+ p.homepage = { url: body.homepage };
2421
+ }
2422
+ // Use the latest version if none specified
2423
+ if (!p.version) {
2424
+ p.version = body.newest_version;
2425
+ }
2426
+ cdepList.push(p);
2427
+ } catch (err) {
2428
+ cdepList.push(p);
2429
+ }
2430
+ }
2431
+ return cdepList;
2432
+ };
2433
+ exports.getCratesMetadata = getCratesMetadata;
2434
+
2435
+ /**
2436
+ * Method to retrieve metadata for dart packages by querying pub.dev
2437
+ *
2438
+ * @param {Array} pkgList Package list
2439
+ */
2440
+ const getDartMetadata = async function (pkgList) {
2441
+ const PUB_DEV_URL = "https://pub.dev/api/packages/";
2442
+ const cdepList = [];
2443
+ for (const p of pkgList) {
2444
+ try {
2445
+ if (DEBUG_MODE) {
2446
+ console.log(`Querying pub.dev for ${p.name}`);
2447
+ }
2448
+ const res = await got.get(PUB_DEV_URL + p.name, {
2449
+ responseType: "json",
2450
+ Accept: "application/vnd.pub.v2+json"
2451
+ });
2452
+ if (res && res.body) {
2453
+ const versions = res.body.versions;
2454
+ for (let v of versions) {
2455
+ if (p.version === v.version) {
2456
+ const pubspec = v.pubspec;
2457
+ p.description = pubspec.description;
2458
+ if (pubspec.repository) {
2459
+ p.repository = { url: pubspec.repository };
2460
+ }
2461
+ if (pubspec.homepage) {
2462
+ p.homepage = { url: pubspec.homepage };
2463
+ }
2464
+ p.license = "https://pub.dev/packages/" + p.name + "/license";
2465
+ cdepList.push(p);
2466
+ break;
2467
+ }
2468
+ }
2469
+ }
2470
+ } catch (err) {
2471
+ cdepList.push(p);
2472
+ }
2473
+ }
2474
+ return cdepList;
2475
+ };
2476
+ exports.getDartMetadata = getDartMetadata;
2477
+
2478
+ const parseCargoTomlData = async function (cargoData) {
2479
+ let pkgList = [];
2480
+ if (!cargoData) {
2481
+ return pkgList;
2482
+ }
2483
+ let pkg = null;
2484
+ let dependencyMode = false;
2485
+ let packageMode = false;
2486
+ cargoData.split("\n").forEach((l) => {
2487
+ let key = null;
2488
+ let value = null;
2489
+ if (l.indexOf("[package]") > -1) {
2490
+ packageMode = true;
2491
+ if (pkg) {
2492
+ pkgList.push(pkg);
2493
+ }
2494
+ pkg = {};
2495
+ }
2496
+ if (l.startsWith("[dependencies]")) {
2497
+ dependencyMode = true;
2498
+ packageMode = false;
2499
+ }
2500
+ if (l.startsWith("[") && !l.startsWith("[dependencies]") && !packageMode) {
2501
+ dependencyMode = false;
2502
+ packageMode = false;
2503
+ }
2504
+ if (packageMode && l.indexOf("=") > -1) {
2505
+ const tmpA = l.split("=");
2506
+ key = tmpA[0].trim();
2507
+ value = tmpA[1].trim().replace(/"/g, "");
2508
+ switch (key) {
2509
+ case "checksum":
2510
+ pkg._integrity = "sha384-" + value;
2511
+ break;
2512
+ case "name":
2513
+ pkg.group = path.dirname(value);
2514
+ if (pkg.group === ".") {
2515
+ pkg.group = "";
2516
+ }
2517
+ pkg.name = path.basename(value);
2518
+ break;
2519
+ case "version":
2520
+ pkg.version = value;
2521
+ break;
2522
+ }
2523
+ } else if (dependencyMode && l.indexOf("=") > -1) {
2524
+ if (pkg) {
2525
+ pkgList.push(pkg);
2526
+ }
2527
+ pkg = undefined;
2528
+ let tmpA = l.split(" = ");
2529
+ let tmpB = undefined;
2530
+ let name = tmpA[0];
2531
+ let version = undefined;
2532
+ if (l.indexOf("version =") > -1) {
2533
+ tmpB = l.split(" { version = ");
2534
+ if (tmpB && tmpB.length > 1) {
2535
+ version = tmpB[1].split(",")[0];
2536
+ }
2537
+ } else if (l.includes("git =")) {
2538
+ tmpB = l.split(" { git = ");
2539
+ if (tmpB && tmpB.length > 1) {
2540
+ version = "git+" + tmpB[1].split(" }")[0];
2541
+ }
2542
+ } else if (l.indexOf("path =") == -1 && tmpA.length > 1) {
2543
+ version = tmpA[1];
2544
+ }
2545
+ if (name && version) {
2546
+ name = name.replace(new RegExp("[\"']", "g"), "");
2547
+ version = version.replace(new RegExp("[\"']", "g"), "");
2548
+ pkgList.push({ name, version });
2549
+ }
2550
+ }
2551
+ });
2552
+ if (pkg) {
2553
+ pkgList.push(pkg);
2554
+ }
2555
+ if (process.env.FETCH_LICENSE) {
2556
+ return await getCratesMetadata(pkgList);
2557
+ } else {
2558
+ return pkgList;
2559
+ }
2560
+ };
2561
+ exports.parseCargoTomlData = parseCargoTomlData;
2562
+
2563
+ const parseCargoData = async function (cargoData) {
2564
+ const pkgList = [];
2565
+ if (!cargoData) {
2566
+ return pkgList;
2567
+ }
2568
+ let pkg = null;
2569
+ cargoData.split("\n").forEach((l) => {
2570
+ let key = null;
2571
+ let value = null;
2572
+ // Ignore version = 3 found at the top of newer lock files
2573
+ if (!pkg && l.startsWith("version =")) {
2574
+ return;
2575
+ }
2576
+ if (l.indexOf("[[package]]") > -1) {
2577
+ if (pkg) {
2578
+ pkgList.push(pkg);
2579
+ }
2580
+ pkg = {};
2581
+ }
2582
+ if (l.indexOf("=") > -1) {
2583
+ const tmpA = l.split("=");
2584
+ key = tmpA[0].trim();
2585
+ value = tmpA[1].trim().replace(/"/g, "");
2586
+ switch (key) {
2587
+ case "checksum":
2588
+ pkg._integrity = "sha384-" + value;
2589
+ break;
2590
+ case "name":
2591
+ pkg.group = path.dirname(value);
2592
+ if (pkg.group === ".") {
2593
+ pkg.group = "";
2594
+ }
2595
+ pkg.name = path.basename(value);
2596
+ break;
2597
+ case "version":
2598
+ pkg.version = value;
2599
+ break;
2600
+ }
2601
+ }
2602
+ });
2603
+ if (process.env.FETCH_LICENSE) {
2604
+ return await getCratesMetadata(pkgList);
2605
+ } else {
2606
+ return pkgList;
2607
+ }
2608
+ };
2609
+ exports.parseCargoData = parseCargoData;
2610
+
2611
+ const parseCargoAuditableData = async function (cargoData) {
2612
+ const pkgList = [];
2613
+ if (!cargoData) {
2614
+ return pkgList;
2615
+ }
2616
+ cargoData.split("\n").forEach((l) => {
2617
+ const tmpA = l.split("\t");
2618
+ if (tmpA && tmpA.length > 2) {
2619
+ let group = path.dirname(tmpA[0].trim());
2620
+ const name = path.basename(tmpA[0].trim());
2621
+ if (group === ".") {
2622
+ group = "";
2623
+ }
2624
+ const version = tmpA[1];
2625
+ pkgList.push({
2626
+ group,
2627
+ name,
2628
+ version
2629
+ });
2630
+ }
2631
+ });
2632
+ if (process.env.FETCH_LICENSE) {
2633
+ return await getCratesMetadata(pkgList);
2634
+ } else {
2635
+ return pkgList;
2636
+ }
2637
+ };
2638
+ exports.parseCargoAuditableData = parseCargoAuditableData;
2639
+
2640
+ const parsePubLockData = async function (pubLockData) {
2641
+ const pkgList = [];
2642
+ if (!pubLockData) {
2643
+ return pkgList;
2644
+ }
2645
+ let pkg = null;
2646
+ pubLockData.split("\n").forEach((l) => {
2647
+ let key = null;
2648
+ let value = null;
2649
+ if (!pkg && (l.startsWith("sdks:") || !l.startsWith(" "))) {
2650
+ return;
2651
+ }
2652
+ if (l.startsWith(" ") && !l.startsWith(" ")) {
2653
+ pkg = {
2654
+ name: l.trim().replace(":", "")
2655
+ };
2656
+ }
2657
+ if (l.startsWith(" ")) {
2658
+ const tmpA = l.split(":");
2659
+ key = tmpA[0].trim();
2660
+ value = tmpA[1].trim().replace(/"/g, "");
2661
+ switch (key) {
2662
+ case "version":
2663
+ pkg.version = value;
2664
+ if (pkg.name) {
2665
+ pkgList.push(pkg);
2666
+ }
2667
+ pkg = {};
2668
+ break;
2669
+ }
2670
+ }
2671
+ });
2672
+ if (process.env.FETCH_LICENSE) {
2673
+ return await getDartMetadata(pkgList);
2674
+ } else {
2675
+ return pkgList;
2676
+ }
2677
+ };
2678
+ exports.parsePubLockData = parsePubLockData;
2679
+
2680
+ const parsePubYamlData = async function (pubYamlData) {
2681
+ const pkgList = [];
2682
+ let yamlObj = undefined;
2683
+ try {
2684
+ yamlObj = yaml.load(pubYamlData);
2685
+ } catch (err) {
2686
+ // continue regardless of error
2687
+ }
2688
+ if (!yamlObj) {
2689
+ return pkgList;
2690
+ }
2691
+ pkgList.push({
2692
+ name: yamlObj.name,
2693
+ description: yamlObj.description,
2694
+ version: yamlObj.version,
2695
+ homepage: { url: yamlObj.homepage }
2696
+ });
2697
+ return pkgList;
2698
+ };
2699
+ exports.parsePubYamlData = parsePubYamlData;
2700
+
2701
+ const parseHelmYamlData = async function (helmData) {
2702
+ const pkgList = [];
2703
+ let yamlObj = undefined;
2704
+ try {
2705
+ yamlObj = yaml.load(helmData);
2706
+ } catch (err) {
2707
+ // continue regardless of error
2708
+ }
2709
+ if (!yamlObj) {
2710
+ return pkgList;
2711
+ }
2712
+ if (yamlObj.name && yamlObj.version) {
2713
+ let pkg = {
2714
+ name: yamlObj.name,
2715
+ description: yamlObj.description || "",
2716
+ version: yamlObj.version
2717
+ };
2718
+ if (yamlObj.home) {
2719
+ pkg["homepage"] = { url: yamlObj.home };
2720
+ }
2721
+ pkgList.push(pkg);
2722
+ }
2723
+ if (yamlObj.dependencies) {
2724
+ for (const hd of yamlObj.dependencies) {
2725
+ let pkg = {
2726
+ name: hd.name,
2727
+ version: hd.version // This could have * so not precise
2728
+ };
2729
+ if (hd.repository) {
2730
+ pkg["repository"] = { url: hd.repository };
2731
+ }
2732
+ pkgList.push(pkg);
2733
+ }
2734
+ }
2735
+ if (yamlObj.entries) {
2736
+ for (const he of Object.keys(yamlObj.entries)) {
2737
+ for (const key of Object.keys(yamlObj.entries[he])) {
2738
+ const hd = yamlObj.entries[he][key];
2739
+ if (hd.name && hd.version) {
2740
+ let pkg = {
2741
+ name: hd.name,
2742
+ version: hd.version,
2743
+ description: hd.description || ""
2744
+ };
2745
+ if (hd.sources && Array.isArray(hd.sources) && hd.sources.length) {
2746
+ pkg["repository"] = { url: hd.sources[0] };
2747
+ if (hd.home && hd.home !== hd.sources[0]) {
2748
+ pkg["homepage"] = { url: hd.home };
2749
+ }
2750
+ }
2751
+ if (hd.home && !pkg["homepage"]) {
2752
+ pkg["homepage"] = { url: hd.home };
2753
+ }
2754
+ if (hd.digest) {
2755
+ pkg._integrity = "sha256-" + hd.digest;
2756
+ }
2757
+
2758
+ pkgList.push(pkg);
2759
+ }
2760
+ }
2761
+ }
2762
+ }
2763
+ return pkgList;
2764
+ };
2765
+ exports.parseHelmYamlData = parseHelmYamlData;
2766
+
2767
+ const recurseImageNameLookup = (keyValueObj, pkgList, imgList) => {
2768
+ if (typeof keyValueObj === "string" || keyValueObj instanceof String) {
2769
+ return imgList;
2770
+ }
2771
+ if (Array.isArray(keyValueObj)) {
2772
+ for (const ele of keyValueObj) {
2773
+ if (typeof ele !== "string") {
2774
+ recurseImageNameLookup(ele, pkgList, imgList);
2775
+ }
2776
+ }
2777
+ } else if (Object.keys(keyValueObj).length) {
2778
+ let imageLike =
2779
+ keyValueObj.image ||
2780
+ keyValueObj.repository ||
2781
+ keyValueObj.dockerImage ||
2782
+ keyValueObj.mavenImage ||
2783
+ keyValueObj.gradleImage ||
2784
+ keyValueObj.packImage ||
2785
+ keyValueObj.koImage ||
2786
+ keyValueObj.kanikoImage;
2787
+ if (keyValueObj.name && keyValueObj.name.includes("/")) {
2788
+ imageLike = keyValueObj.name;
2789
+ }
2790
+ if (
2791
+ imageLike &&
2792
+ typeof imageLike === "string" &&
2793
+ !imgList.includes(imageLike)
2794
+ ) {
2795
+ if (imageLike.includes(":${VERSION:")) {
2796
+ imageLike = imageLike
2797
+ .replace(":${VERSION:-", ":")
2798
+ .replace(":${VERSION:", ":")
2799
+ .replace("}", "");
2800
+ }
2801
+ pkgList.push({ image: imageLike });
2802
+ pkgList.push({ service: keyValueObj.name || imageLike });
2803
+ imgList.push(imageLike);
2804
+ }
2805
+ for (const key of Object.keys(keyValueObj)) {
2806
+ // Skip unwanted blocks to improve performance
2807
+ if (["schema", "openAPIV3Schema", "names", "status"].includes(key)) {
2808
+ continue;
2809
+ }
2810
+ const valueObj = keyValueObj[key];
2811
+ if (!valueObj) {
2812
+ continue;
2813
+ }
2814
+ if (Object.keys(valueObj).length && typeof valueObj !== "string") {
2815
+ recurseImageNameLookup(valueObj, pkgList, imgList);
2816
+ }
2817
+ if (Array.isArray(valueObj)) {
2818
+ for (const ele of valueObj) {
2819
+ if (typeof ele !== "string") {
2820
+ recurseImageNameLookup(ele, pkgList, imgList);
2821
+ }
2822
+ }
2823
+ }
2824
+ }
2825
+ }
2826
+ return imgList;
2827
+ };
2828
+ exports.recurseImageNameLookup = recurseImageNameLookup;
2829
+
2830
+ const parseContainerSpecData = async function (dcData) {
2831
+ const pkgList = [];
2832
+ const imgList = [];
2833
+ if (!dcData.includes("image") && !dcData.includes("kind")) {
2834
+ return pkgList;
2835
+ }
2836
+ let dcDataList = [dcData];
2837
+ if (dcData.includes("---")) {
2838
+ dcDataList = dcData.split("---");
2839
+ }
2840
+ for (const dcData of dcDataList) {
2841
+ let yamlObj = undefined;
2842
+ try {
2843
+ yamlObj = yaml.load(dcData);
2844
+ } catch (err) {
2845
+ if (DEBUG_MODE) {
2846
+ console.log(err);
2847
+ }
2848
+ }
2849
+ if (!yamlObj) {
2850
+ continue;
2851
+ }
2852
+ if (yamlObj.services) {
2853
+ for (const serv of Object.keys(yamlObj.services)) {
2854
+ pkgList.push({
2855
+ service: serv
2856
+ });
2857
+ const aservice = yamlObj.services[serv];
2858
+ // Track locally built images
2859
+ if (aservice.build) {
2860
+ if (Object.keys(aservice.build).length && aservice.build.dockerfile) {
2861
+ pkgList.push({
2862
+ ociSpec: aservice.build.dockerfile
2863
+ });
2864
+ } else {
2865
+ if (aservice.build === "." || aservice.build === "./") {
2866
+ pkgList.push({
2867
+ ociSpec: "Dockerfile"
2868
+ });
2869
+ } else {
2870
+ pkgList.push({
2871
+ ociSpec: aservice.build
2872
+ });
2873
+ }
2874
+ }
2875
+ } else if (aservice.image && !imgList.includes(aservice.image)) {
2876
+ let imgFullName = aservice.image;
2877
+ if (imgFullName.includes(":${VERSION:")) {
2878
+ imgFullName = imgFullName
2879
+ .replace(":${VERSION:-", ":")
2880
+ .replace(":${VERSION:", ":")
2881
+ .replace("}", "");
2882
+ }
2883
+ pkgList.push({
2884
+ image: imgFullName
2885
+ });
2886
+ imgList.push(imgFullName);
2887
+ }
2888
+ }
2889
+ }
2890
+ // Tekton tasks and kustomize have spec. Skaffold has build
2891
+ const recurseBlock = yamlObj.spec || yamlObj.build || yamlObj.images;
2892
+ if (recurseBlock) {
2893
+ recurseImageNameLookup(recurseBlock, pkgList, imgList);
2894
+ }
2895
+ }
2896
+ return pkgList;
2897
+ };
2898
+ exports.parseContainerSpecData = parseContainerSpecData;
2899
+
2900
+ const identifyFlow = function (processingObj) {
2901
+ let flow = "unknown";
2902
+ if (processingObj.sinkId) {
2903
+ let sinkId = processingObj.sinkId.toLowerCase();
2904
+ if (sinkId.endsWith("write")) {
2905
+ flow = "inbound";
2906
+ } else if (sinkId.endsWith("read")) {
2907
+ flow = "outbound";
2908
+ } else if (sinkId.includes("http") || sinkId.includes("grpc")) {
2909
+ flow = "bi-directional";
2910
+ }
2911
+ }
2912
+ return flow;
2913
+ };
2914
+
2915
+ const convertProcessing = function (processing_list) {
2916
+ const data_list = [];
2917
+ for (const p of processing_list) {
2918
+ data_list.push({
2919
+ classification: p.sourceId || p.sinkId,
2920
+ flow: identifyFlow(p)
2921
+ });
2922
+ }
2923
+ return data_list;
2924
+ };
2925
+
2926
+ const parsePrivadoFile = function (f) {
2927
+ const pData = fs.readFileSync(f, { encoding: "utf-8" });
2928
+ const servlist = [];
2929
+ if (!pData) {
2930
+ return servlist;
2931
+ }
2932
+ const jsonData = JSON.parse(pData);
2933
+ let aservice = {
2934
+ "x-trust-boundary": false,
2935
+ properties: [],
2936
+ data: [],
2937
+ endpoints: []
2938
+ };
2939
+ if (jsonData.repoName) {
2940
+ aservice.name = jsonData.repoName;
2941
+ aservice.properties = [
2942
+ {
2943
+ name: "SrcFile",
2944
+ value: f
2945
+ }
2946
+ ];
2947
+ // Capture git metadata info
2948
+ if (jsonData.gitMetadata) {
2949
+ aservice.version = jsonData.gitMetadata.commitId || "";
2950
+ aservice.properties.push({
2951
+ name: "privadoCoreVersion",
2952
+ value: jsonData.privadoCoreVersion || ""
2953
+ });
2954
+ aservice.properties.push({
2955
+ name: "privadoCLIVersion",
2956
+ value: jsonData.privadoCLIVersion || ""
2957
+ });
2958
+ aservice.properties.push({
2959
+ name: "localScanPath",
2960
+ value: jsonData.localScanPath || ""
2961
+ });
2962
+ }
2963
+ // Capture processing
2964
+ if (jsonData.processing && jsonData.processing.length) {
2965
+ aservice.data = aservice.data.concat(
2966
+ convertProcessing(jsonData.processing)
2967
+ );
2968
+ }
2969
+ // Capture sink processing
2970
+ if (jsonData.sinkProcessing && jsonData.sinkProcessing.length) {
2971
+ aservice.data = aservice.data.concat(
2972
+ convertProcessing(jsonData.sinkProcessing)
2973
+ );
2974
+ }
2975
+ // Find endpoints
2976
+ if (jsonData.collections) {
2977
+ const endpoints = [];
2978
+ for (let c of jsonData.collections) {
2979
+ for (let occ of c.collections) {
2980
+ for (let e of occ.occurrences) {
2981
+ if (e.endPoint) {
2982
+ endpoints.push(e.endPoint);
2983
+ }
2984
+ }
2985
+ }
2986
+ }
2987
+ aservice.endpoints = endpoints;
2988
+ }
2989
+ // Capture violations
2990
+ if (jsonData.violations) {
2991
+ for (let v of jsonData.violations) {
2992
+ aservice.properties.push({
2993
+ name: "privado_violations",
2994
+ value: v.policyId
2995
+ });
2996
+ }
2997
+ }
2998
+ // If there are third party libraries detected, then there are cross boundary calls happening
2999
+ if (
3000
+ jsonData.dataFlow &&
3001
+ jsonData.dataFlow.third_parties &&
3002
+ jsonData.dataFlow.third_parties.length
3003
+ ) {
3004
+ aservice["x-trust-boundary"] = true;
3005
+ }
3006
+ servlist.push(aservice);
3007
+ }
3008
+ return servlist;
3009
+ };
3010
+ exports.parsePrivadoFile = parsePrivadoFile;
3011
+
3012
+ const parseOpenapiSpecData = async function (oaData) {
3013
+ const servlist = [];
3014
+ if (!oaData) {
3015
+ return servlist;
3016
+ }
3017
+ try {
3018
+ if (oaData.startsWith("openapi:")) {
3019
+ oaData = yaml.load(oaData);
3020
+ } else {
3021
+ oaData = JSON.parse(oaData);
3022
+ }
3023
+ } catch (e) {
3024
+ console.error(e);
3025
+ return servlist;
3026
+ }
3027
+ const name = oaData.info.title.replace(/ /g, "-");
3028
+ const version = oaData.info.version || "latest";
3029
+ const aservice = {
3030
+ "bom-ref": `urn:service:${name}:${version}`,
3031
+ name,
3032
+ description: oaData.description || "",
3033
+ version
3034
+ };
3035
+ let serverName = [];
3036
+ if (oaData.servers && oaData.servers.length && oaData.servers[0].url) {
3037
+ serverName = oaData.servers[0].url;
3038
+ if (!serverName.startsWith("http") || !serverName.includes("//")) {
3039
+ serverName = "http://" + serverName;
3040
+ }
3041
+ }
3042
+ if (oaData.paths) {
3043
+ const endpoints = [];
3044
+ for (const route of Object.keys(oaData.paths)) {
3045
+ let sep = "";
3046
+ if (!route.startsWith("/")) {
3047
+ sep = "/";
3048
+ }
3049
+ endpoints.push(`${serverName}${sep}${route}`);
3050
+ }
3051
+ aservice.endpoints = endpoints;
3052
+ }
3053
+ let authenticated = false;
3054
+ if (oaData.components && oaData.components.securitySchemes) {
3055
+ authenticated = true;
3056
+ }
3057
+ aservice.authenticated = authenticated;
3058
+ servlist.push(aservice);
3059
+ return servlist;
3060
+ };
3061
+ exports.parseOpenapiSpecData = parseOpenapiSpecData;
3062
+
3063
+ const parseCabalData = async function (cabalData) {
3064
+ const pkgList = [];
3065
+ if (!cabalData) {
3066
+ return pkgList;
3067
+ }
3068
+ cabalData.split("\n").forEach((l) => {
3069
+ if (!l.includes(" ==")) {
3070
+ return;
3071
+ }
3072
+ if (l.includes(" ==")) {
3073
+ const tmpA = l.split(" ==");
3074
+ const name = tmpA[0]
3075
+ .replace("constraints: ", "")
3076
+ .replace("any.", "")
3077
+ .trim();
3078
+ const version = tmpA[1].replace(",", "").trim();
3079
+ if (name && version) {
3080
+ pkgList.push({
3081
+ name,
3082
+ version
3083
+ });
3084
+ }
3085
+ }
3086
+ });
3087
+ return pkgList;
3088
+ };
3089
+ exports.parseCabalData = parseCabalData;
3090
+
3091
+ const parseMixLockData = async function (mixData) {
3092
+ const pkgList = [];
3093
+ if (!mixData) {
3094
+ return pkgList;
3095
+ }
3096
+ mixData.split("\n").forEach((l) => {
3097
+ if (!l.includes(":hex")) {
3098
+ return;
3099
+ }
3100
+ if (l.includes(":hex")) {
3101
+ const tmpA = l.split(",");
3102
+ if (tmpA.length > 3) {
3103
+ const name = tmpA[1].replace(":", "").trim();
3104
+ const version = tmpA[2].trim().replace(/"/g, "");
3105
+ if (name && version) {
3106
+ pkgList.push({
3107
+ name,
3108
+ version
3109
+ });
3110
+ }
3111
+ }
3112
+ }
3113
+ });
3114
+ return pkgList;
3115
+ };
3116
+ exports.parseMixLockData = parseMixLockData;
3117
+
3118
+ const parseGitHubWorkflowData = async function (ghwData) {
3119
+ const pkgList = [];
3120
+ const keys_cache = {};
3121
+ if (!ghwData) {
3122
+ return pkgList;
3123
+ }
3124
+ const yamlObj = yaml.load(ghwData);
3125
+ if (!yamlObj) {
3126
+ return pkgList;
3127
+ }
3128
+ for (const jobName of Object.keys(yamlObj.jobs)) {
3129
+ if (yamlObj.jobs[jobName].steps) {
3130
+ for (const step of yamlObj.jobs[jobName].steps) {
3131
+ if (step.uses) {
3132
+ const tmpA = step.uses.split("@");
3133
+ if (tmpA.length === 2) {
3134
+ const groupName = tmpA[0];
3135
+ let name = groupName;
3136
+ let group = "";
3137
+ const version = tmpA[1];
3138
+ const tmpB = groupName.split("/");
3139
+ if (tmpB.length == 2) {
3140
+ name = tmpB[1];
3141
+ group = tmpB[0];
3142
+ }
3143
+ const key = group + "-" + name + "-" + version;
3144
+ if (!keys_cache[key] && name && version) {
3145
+ keys_cache[key] = key;
3146
+ pkgList.push({
3147
+ group,
3148
+ name,
3149
+ version
3150
+ });
3151
+ }
3152
+ }
3153
+ }
3154
+ }
3155
+ }
3156
+ }
3157
+ return pkgList;
3158
+ };
3159
+ exports.parseGitHubWorkflowData = parseGitHubWorkflowData;
3160
+
3161
+ const parseCloudBuildData = async function (cbwData) {
3162
+ const pkgList = [];
3163
+ const keys_cache = {};
3164
+ if (!cbwData) {
3165
+ return pkgList;
3166
+ }
3167
+ const yamlObj = yaml.load(cbwData);
3168
+ if (!yamlObj) {
3169
+ return pkgList;
3170
+ }
3171
+ if (yamlObj.steps) {
3172
+ for (const step of yamlObj.steps) {
3173
+ if (step.name) {
3174
+ const tmpA = step.name.split(":");
3175
+ if (tmpA.length === 2) {
3176
+ let group = path.dirname(tmpA[0]);
3177
+ let name = path.basename(tmpA[0]);
3178
+ if (group === ".") {
3179
+ group = "";
3180
+ }
3181
+ const version = tmpA[1];
3182
+ const key = group + "-" + name + "-" + version;
3183
+ if (!keys_cache[key] && name && version) {
3184
+ keys_cache[key] = key;
3185
+ pkgList.push({
3186
+ group,
3187
+ name,
3188
+ version
3189
+ });
3190
+ }
3191
+ }
3192
+ }
3193
+ }
3194
+ }
3195
+ return pkgList;
3196
+ };
3197
+ exports.parseCloudBuildData = parseCloudBuildData;
3198
+
3199
+ const parseConanLockData = async function (conanLockData) {
3200
+ const pkgList = [];
3201
+ if (!conanLockData) {
3202
+ return pkgList;
3203
+ }
3204
+ const graphLock = JSON.parse(conanLockData);
3205
+ if (!graphLock || !graphLock.graph_lock || !graphLock.graph_lock.nodes) {
3206
+ return pkgList;
3207
+ }
3208
+ const nodes = graphLock.graph_lock.nodes;
3209
+ for (let nk of Object.keys(nodes)) {
3210
+ if (nodes[nk].ref) {
3211
+ const tmpA = nodes[nk].ref.split("/");
3212
+ if (tmpA.length === 2) {
3213
+ pkgList.push({ name: tmpA[0], version: tmpA[1] });
3214
+ }
3215
+ }
3216
+ }
3217
+ return pkgList;
3218
+ };
3219
+ exports.parseConanLockData = parseConanLockData;
3220
+
3221
+ const parseConanData = async function (conanData) {
3222
+ const pkgList = [];
3223
+ if (!conanData) {
3224
+ return pkgList;
3225
+ }
3226
+ conanData.split("\n").forEach((l) => {
3227
+ if (!l.includes("/")) {
3228
+ return;
3229
+ }
3230
+ if (l.includes("/")) {
3231
+ const tmpA = l.split("/");
3232
+ if (tmpA.length === 2) {
3233
+ pkgList.push({ name: tmpA[0], version: tmpA[1] });
3234
+ }
3235
+ }
3236
+ });
3237
+ return pkgList;
3238
+ };
3239
+ exports.parseConanData = parseConanData;
3240
+
3241
+ const parseLeiningenData = function (leinData) {
3242
+ const pkgList = [];
3243
+ if (!leinData) {
3244
+ return pkgList;
3245
+ }
3246
+ const tmpArr = leinData.split("(defproject");
3247
+ if (tmpArr.length > 1) {
3248
+ leinData = "(defproject" + tmpArr[1];
3249
+ }
3250
+ const ednData = ednDataLib.parseEDNString(leinData);
3251
+ for (let k of Object.keys(ednData)) {
3252
+ if (k === "list") {
3253
+ ednData[k].forEach((jk) => {
3254
+ if (Array.isArray(jk)) {
3255
+ jk.forEach((pobjl) => {
3256
+ if (Array.isArray(pobjl) && pobjl.length > 1) {
3257
+ const psym = pobjl[0].sym;
3258
+ if (psym) {
3259
+ let group = path.dirname(psym) || "";
3260
+ if (group === ".") {
3261
+ group = "";
3262
+ }
3263
+ const name = path.basename(psym);
3264
+ pkgList.push({ group, name, version: pobjl[1] });
3265
+ }
3266
+ }
3267
+ });
3268
+ }
3269
+ });
3270
+ }
3271
+ }
3272
+ return pkgList;
3273
+ };
3274
+ exports.parseLeiningenData = parseLeiningenData;
3275
+
3276
+ const parseEdnData = function (rawEdnData) {
3277
+ const pkgList = [];
3278
+ if (!rawEdnData) {
3279
+ return pkgList;
3280
+ }
3281
+ const ednData = ednDataLib.parseEDNString(rawEdnData);
3282
+ const pkgCache = {};
3283
+ for (let k of Object.keys(ednData)) {
3284
+ if (k === "map") {
3285
+ ednData[k].forEach((jk) => {
3286
+ if (Array.isArray(jk)) {
3287
+ if (Array.isArray(jk)) {
3288
+ if (jk.length > 1) {
3289
+ if (jk[0].key === "deps") {
3290
+ const deps = jk[1].map;
3291
+ if (deps) {
3292
+ deps.forEach((d) => {
3293
+ if (Array.isArray(d)) {
3294
+ let psym = "";
3295
+ d.forEach((e) => {
3296
+ if (e.sym) {
3297
+ psym = e.sym;
3298
+ }
3299
+ if (e["map"]) {
3300
+ if (e["map"][0].length > 1) {
3301
+ const version = e["map"][0][1];
3302
+ let group = path.dirname(psym) || "";
3303
+ if (group === ".") {
3304
+ group = "";
3305
+ }
3306
+ const name = path.basename(psym);
3307
+ const cacheKey = group + "-" + name + "-" + version;
3308
+ if (!pkgCache[cacheKey]) {
3309
+ pkgList.push({ group, name, version });
3310
+ pkgCache[cacheKey] = true;
3311
+ }
3312
+ }
3313
+ }
3314
+ });
3315
+ }
3316
+ });
3317
+ }
3318
+ }
3319
+ }
3320
+ }
3321
+ }
3322
+ });
3323
+ }
3324
+ }
3325
+ return pkgList;
3326
+ };
3327
+ exports.parseEdnData = parseEdnData;
3328
+
3329
+ const parseNupkg = async function (nupkgFile) {
3330
+ const pkgList = [];
3331
+ let pkg = { group: "" };
3332
+ let nuspecData = await readZipEntry(nupkgFile, ".nuspec");
3333
+ // Remove byte order mark
3334
+ if (nuspecData.charCodeAt(0) === 0xfeff) {
3335
+ nuspecData = nuspecData.slice(1);
3336
+ }
3337
+ let npkg = undefined;
3338
+ try {
3339
+ npkg = convert.xml2js(nuspecData, {
3340
+ compact: true,
3341
+ alwaysArray: false,
3342
+ spaces: 4,
3343
+ textKey: "_",
3344
+ attributesKey: "$",
3345
+ commentKey: "value"
3346
+ }).package;
3347
+ } catch (e) {
3348
+ // If we are parsing with invalid encoding unicode replacement character is used
3349
+ if (nuspecData.charCodeAt(0) === 65533) {
3350
+ console.log(`Unable to parse ${nupkgFile} in utf-8 mode`);
3351
+ }
3352
+ }
3353
+ if (!npkg) {
3354
+ return pkgList;
3355
+ }
3356
+ const m = npkg.metadata;
3357
+ pkg.name = m.id._;
3358
+ pkg.version = m.version._;
3359
+ pkg.description = m.description._;
3360
+ pkg.properties = [
3361
+ {
3362
+ name: "SrcFile",
3363
+ value: nupkgFile
3364
+ }
3365
+ ];
3366
+ pkgList.push(pkg);
3367
+ if (process.env.FETCH_LICENSE) {
3368
+ return await getNugetMetadata(pkgList);
3369
+ } else {
3370
+ return pkgList;
3371
+ }
3372
+ };
3373
+ exports.parseNupkg = parseNupkg;
3374
+
3375
+ const parseCsPkgData = async function (pkgData) {
3376
+ const pkgList = [];
3377
+ if (!pkgData) {
3378
+ return pkgList;
3379
+ }
3380
+ let packages = convert.xml2js(pkgData, {
3381
+ compact: true,
3382
+ alwaysArray: true,
3383
+ spaces: 4,
3384
+ textKey: "_",
3385
+ attributesKey: "$",
3386
+ commentKey: "value"
3387
+ }).packages;
3388
+ if (packages.length == 0) {
3389
+ return pkgList;
3390
+ }
3391
+ packages = packages[0].package;
3392
+ for (let i in packages) {
3393
+ const p = packages[i].$;
3394
+ let pkg = { group: "" };
3395
+ pkg.name = p.id;
3396
+ pkg.version = p.version;
3397
+ pkgList.push(pkg);
3398
+ }
3399
+ if (process.env.FETCH_LICENSE) {
3400
+ return await getNugetMetadata(pkgList);
3401
+ } else {
3402
+ return pkgList;
3403
+ }
3404
+ };
3405
+ exports.parseCsPkgData = parseCsPkgData;
3406
+
3407
+ const parseCsProjData = async function (csProjData) {
3408
+ const pkgList = [];
3409
+ if (!csProjData) {
3410
+ return pkgList;
3411
+ }
3412
+ const projects = convert.xml2js(csProjData, {
3413
+ compact: true,
3414
+ alwaysArray: true,
3415
+ spaces: 4,
3416
+ textKey: "_",
3417
+ attributesKey: "$",
3418
+ commentKey: "value"
3419
+ }).Project;
3420
+ if (projects.length == 0) {
3421
+ return pkgList;
3422
+ }
3423
+ const project = projects[0];
3424
+ if (project.ItemGroup && project.ItemGroup.length) {
3425
+ for (let i in project.ItemGroup) {
3426
+ const item = project.ItemGroup[i];
3427
+ // .net core use PackageReference
3428
+ for (let j in item.PackageReference) {
3429
+ const pref = item.PackageReference[j].$;
3430
+ let pkg = { group: "" };
3431
+ if (!pref.Include || pref.Include.includes(".csproj")) {
3432
+ continue;
3433
+ }
3434
+ pkg.name = pref.Include;
3435
+ pkg.version = pref.Version;
3436
+ pkgList.push(pkg);
3437
+ }
3438
+ // .net framework use Reference
3439
+ for (let j in item.Reference) {
3440
+ const pref = item.Reference[j].$;
3441
+ let pkg = { group: "" };
3442
+ if (!pref.Include || pref.Include.includes(".csproj")) {
3443
+ continue;
3444
+ }
3445
+ const incParts = pref.Include.split(",");
3446
+ pkg.name = incParts[0];
3447
+ if (incParts.length > 1 && incParts[1].includes("Version")) {
3448
+ pkg.version = incParts[1].replace("Version=", "").trim();
3449
+ }
3450
+ pkgList.push(pkg);
3451
+ }
3452
+ }
3453
+ }
3454
+ if (process.env.FETCH_LICENSE) {
3455
+ return await getNugetMetadata(pkgList);
3456
+ } else {
3457
+ return pkgList;
3458
+ }
3459
+ };
3460
+ exports.parseCsProjData = parseCsProjData;
3461
+
3462
+ const parseCsProjAssetsData = async function (csProjData) {
3463
+ const pkgList = [];
3464
+ let pkg = null;
3465
+ if (!csProjData) {
3466
+ return pkgList;
3467
+ }
3468
+ const assetData = JSON.parse(csProjData);
3469
+ if (!assetData || !assetData.libraries) {
3470
+ return pkgList;
3471
+ }
3472
+ for (let alib of Object.keys(assetData.libraries)) {
3473
+ // Skip os runtime packages
3474
+ if (alib.startsWith("runtime")) {
3475
+ continue;
3476
+ }
3477
+ const tmpA = alib.split("/");
3478
+ const libData = assetData.libraries[alib];
3479
+ if (tmpA.length > 1) {
3480
+ pkg = {
3481
+ group: "",
3482
+ name: tmpA[0],
3483
+ version: tmpA[tmpA.length - 1]
3484
+ };
3485
+ if (libData.sha256) {
3486
+ pkg._integrity = "sha256-" + libData.sha256;
3487
+ }
3488
+ if (libData.sha512) {
3489
+ pkg._integrity = "sha512-" + libData.sha512;
3490
+ }
3491
+ pkgList.push(pkg);
3492
+ }
3493
+ }
3494
+ if (process.env.FETCH_LICENSE) {
3495
+ return await getNugetMetadata(pkgList);
3496
+ } else {
3497
+ return pkgList;
3498
+ }
3499
+ };
3500
+ exports.parseCsProjAssetsData = parseCsProjAssetsData;
3501
+
3502
+ const parseCsPkgLockData = async function (csLockData) {
3503
+ const pkgList = [];
3504
+ let pkg = null;
3505
+ if (!csLockData) {
3506
+ return pkgList;
3507
+ }
3508
+ const assetData = JSON.parse(csLockData);
3509
+ if (!assetData || !assetData.dependencies) {
3510
+ return pkgList;
3511
+ }
3512
+ for (let aversion of Object.keys(assetData.dependencies)) {
3513
+ for (let alib of Object.keys(assetData.dependencies[aversion])) {
3514
+ const libData = assetData.dependencies[aversion][alib];
3515
+ pkg = {
3516
+ group: "",
3517
+ name: alib,
3518
+ version: libData.resolved
3519
+ };
3520
+ pkgList.push(pkg);
3521
+ }
3522
+ }
3523
+ if (process.env.FETCH_LICENSE) {
3524
+ return await getNugetMetadata(pkgList);
3525
+ } else {
3526
+ return pkgList;
3527
+ }
3528
+ };
3529
+ exports.parseCsPkgLockData = parseCsPkgLockData;
3530
+
3531
+ /**
3532
+ * Method to retrieve metadata for nuget packages
3533
+ *
3534
+ * @param {Array} pkgList Package list
3535
+ */
3536
+ const getNugetMetadata = async function (pkgList) {
3537
+ const NUGET_URL = "https://api.nuget.org/v3/registration3/";
3538
+ const cdepList = [];
3539
+ for (const p of pkgList) {
3540
+ try {
3541
+ if (DEBUG_MODE) {
3542
+ console.log(`Querying nuget for ${p.name}`);
3543
+ }
3544
+ const res = await got.get(
3545
+ NUGET_URL +
3546
+ p.group.toLowerCase() +
3547
+ (p.group !== "" ? "." : "") +
3548
+ p.name.toLowerCase() +
3549
+ "/index.json",
3550
+ { responseType: "json" }
3551
+ );
3552
+ const items = res.body.items;
3553
+ if (!items || !items[0] || !items[0].items) {
3554
+ continue;
3555
+ }
3556
+ const firstItem = items[0];
3557
+ const body = firstItem.items[firstItem.items.length - 1];
3558
+ // Set the latest version in case it is missing
3559
+ if (!p.version && body.catalogEntry.version) {
3560
+ p.version = body.catalogEntry.version;
3561
+ }
3562
+ p.description = body.catalogEntry.description;
3563
+ if (
3564
+ body.catalogEntry.licenseExpression &&
3565
+ body.catalogEntry.licenseExpression !== ""
3566
+ ) {
3567
+ p.license = findLicenseId(body.catalogEntry.licenseExpression);
3568
+ } else if (body.catalogEntry.licenseUrl) {
3569
+ p.license = body.catalogEntry.licenseUrl;
3570
+ }
3571
+ if (body.catalogEntry.projectUrl) {
3572
+ p.repository = { url: body.catalogEntry.projectUrl };
3573
+ p.homepage = {
3574
+ url:
3575
+ "https://www.nuget.org/packages/" +
3576
+ p.group +
3577
+ (p.group !== "" ? "." : "") +
3578
+ p.name +
3579
+ "/" +
3580
+ p.version +
3581
+ "/"
3582
+ };
3583
+ }
3584
+ cdepList.push(p);
3585
+ } catch (err) {
3586
+ cdepList.push(p);
3587
+ }
3588
+ }
3589
+ return cdepList;
3590
+ };
3591
+ exports.getNugetMetadata = getNugetMetadata;
3592
+
3593
+ /**
3594
+ * Parse composer lock file
3595
+ *
3596
+ * @param {string} pkgLockFile composer.lock file
3597
+ */
3598
+ const parseComposerLock = function (pkgLockFile) {
3599
+ const pkgList = [];
3600
+ if (fs.existsSync(pkgLockFile)) {
3601
+ let lockData = {};
3602
+ try {
3603
+ lockData = JSON.parse(fs.readFileSync(pkgLockFile, "utf8"));
3604
+ } catch (e) {
3605
+ console.error("Invalid composer.lock file:", pkgLockFile);
3606
+ return [];
3607
+ }
3608
+ if (lockData) {
3609
+ let packages = {};
3610
+ if (lockData["packages"]) {
3611
+ packages["required"] = lockData["packages"];
3612
+ }
3613
+ if (lockData["packages-dev"]) {
3614
+ packages["optional"] = lockData["packages-dev"];
3615
+ }
3616
+ for (let compScope in packages) {
3617
+ for (let i in packages[compScope]) {
3618
+ const pkg = packages[compScope][i];
3619
+ let group = path.dirname(pkg.name);
3620
+ if (group === ".") {
3621
+ group = "";
3622
+ }
3623
+ let name = path.basename(pkg.name);
3624
+ pkgList.push({
3625
+ group: group,
3626
+ name: name,
3627
+ // Remove leading v from version to work around bug
3628
+ // https://github.com/OSSIndex/vulns/issues/231
3629
+ // @TODO: remove workaround when DependencyTrack v4.4 is released,
3630
+ // which has it's own workaround. Or when the 231 bug is fixed.
3631
+ version: pkg.version.replace(/^v/, ""),
3632
+ repository: pkg.source,
3633
+ license: pkg.license,
3634
+ description: pkg.description,
3635
+ scope: compScope,
3636
+ properties: [
3637
+ {
3638
+ name: "SrcFile",
3639
+ value: pkgLockFile
3640
+ }
3641
+ ]
3642
+ });
3643
+ }
3644
+ }
3645
+ }
3646
+ }
3647
+ return pkgList;
3648
+ };
3649
+ exports.parseComposerLock = parseComposerLock;
3650
+
3651
+ /**
3652
+ * Parse sbt lock file
3653
+ *
3654
+ * @param {string} pkgLockFile build.sbt.lock file
3655
+ */
3656
+ const parseSbtLock = function (pkgLockFile) {
3657
+ const pkgList = [];
3658
+ if (fs.existsSync(pkgLockFile)) {
3659
+ const lockData = JSON.parse(fs.readFileSync(pkgLockFile, "utf8"));
3660
+ if (lockData && lockData.dependencies) {
3661
+ for (let pkg of lockData.dependencies) {
3662
+ const artifacts = pkg.artifacts || undefined;
3663
+ let integrity = "";
3664
+ if (artifacts && artifacts.length) {
3665
+ integrity = artifacts[0].hash.replace("sha1:", "sha1-");
3666
+ }
3667
+ let compScope = undefined;
3668
+ if (pkg.configurations) {
3669
+ if (pkg.configurations.includes("runtime")) {
3670
+ compScope = "required";
3671
+ } else {
3672
+ compScope = "optional";
3673
+ }
3674
+ }
3675
+ pkgList.push({
3676
+ group: pkg.org,
3677
+ name: pkg.name,
3678
+ version: pkg.version,
3679
+ _integrity: integrity,
3680
+ scope: compScope,
3681
+ properties: [
3682
+ {
3683
+ name: "SrcFile",
3684
+ value: pkgLockFile
3685
+ }
3686
+ ]
3687
+ });
3688
+ }
3689
+ }
3690
+ }
3691
+ return pkgList;
3692
+ };
3693
+ exports.parseSbtLock = parseSbtLock;
3694
+
3695
+ /**
3696
+ * Convert OS query results
3697
+ *
3698
+ * @param {Object} queryObj Query Object from the queries.json configuration
3699
+ * @param {Array} results Query Results
3700
+ */
3701
+ const convertOSQueryResults = function (queryCategory, queryObj, results) {
3702
+ const pkgList = [];
3703
+ if (results && results.length) {
3704
+ for (const res of results) {
3705
+ if (res.version) {
3706
+ const version = res.version;
3707
+ let name = res.name || res.device_id;
3708
+ let group = "";
3709
+ let subpath = res.path || res.admindir || res.source;
3710
+ let publisher = res.maintainer || res.creator;
3711
+ let scope = undefined;
3712
+ let compScope = res.priority;
3713
+ if (["required", "optional", "excluded"].includes(compScope)) {
3714
+ scope = compScope;
3715
+ }
3716
+ let description =
3717
+ res.description ||
3718
+ res.arguments ||
3719
+ res.device ||
3720
+ res.codename ||
3721
+ res.section ||
3722
+ res.status ||
3723
+ res.identifier ||
3724
+ res.components;
3725
+ // Re-use the name from query obj
3726
+ if (!name && results.length === 1 && queryObj.name) {
3727
+ name = queryObj.name;
3728
+ }
3729
+ if (name && version) {
3730
+ const purl = new PackageURL(
3731
+ queryObj.purlType || "swid",
3732
+ group,
3733
+ name,
3734
+ version,
3735
+ undefined,
3736
+ subpath
3737
+ );
3738
+ pkgList.push({
3739
+ name,
3740
+ group,
3741
+ version,
3742
+ description,
3743
+ publisher,
3744
+ purl,
3745
+ scope
3746
+ });
3747
+ }
3748
+ }
3749
+ }
3750
+ }
3751
+ return pkgList;
3752
+ };
3753
+ exports.convertOSQueryResults = convertOSQueryResults;
3754
+
3755
+ /**
3756
+ * Collect maven dependencies
3757
+ *
3758
+ * @param {string} mavenCmd Maven command to use
3759
+ * @param {string} basePath Path to the maven project
3760
+ */
3761
+ const collectMvnDependencies = function (mavenCmd, basePath) {
3762
+ let tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "mvn-deps-"));
3763
+ console.log(
3764
+ `Executing 'mvn dependency:copy-dependencies -DoutputDirectory=${tempDir} -DexcludeTransitive=true -DincludeScope=runtime' in ${basePath}`
3765
+ );
3766
+ const result = spawnSync(
3767
+ mavenCmd,
3768
+ [
3769
+ "dependency:copy-dependencies",
3770
+ `-DoutputDirectory=${tempDir}`,
3771
+ "-DexcludeTransitive=true",
3772
+ "-DincludeScope=runtime",
3773
+ "-U",
3774
+ "-Dmdep.prependGroupId=" + (process.env.MAVEN_PREPEND_GROUP || "false"),
3775
+ "-Dmdep.stripVersion=" + (process.env.MAVEN_STRIP_VERSION || "false")
3776
+ ],
3777
+ { cwd: basePath, encoding: "utf-8" }
3778
+ );
3779
+ let jarNSMapping = {};
3780
+ if (result.status !== 0 || result.error) {
3781
+ console.error(result.stdout, result.stderr);
3782
+ console.log(
3783
+ "Resolve the above maven error. You can try the following remediation tips:\n"
3784
+ );
3785
+ console.log(
3786
+ "1. Check if the correct version of maven is installed and available in the PATH."
3787
+ );
3788
+ console.log(
3789
+ "2. Perform 'mvn compile package' before invoking this command. Fix any errors found during this invocation."
3790
+ );
3791
+ console.log(
3792
+ "3. Ensure the temporary directory is available and has sufficient disk space to copy all the artifacts."
3793
+ );
3794
+ } else {
3795
+ jarNSMapping = collectJarNS(tempDir);
3796
+ }
3797
+ // Clean up
3798
+ if (tempDir && tempDir.startsWith(os.tmpdir()) && fs.rmSync) {
3799
+ console.log(`Cleaning up ${tempDir}`);
3800
+ fs.rmSync(tempDir, { recursive: true, force: true });
3801
+ }
3802
+ return jarNSMapping;
3803
+ };
3804
+ exports.collectMvnDependencies = collectMvnDependencies;
3805
+
3806
+ /**
3807
+ * Method to collect class names from all jars in a directory
3808
+ *
3809
+ * @param {string} jarPath Path containing jars
3810
+ *
3811
+ * @return object containing jar name and class list
3812
+ */
3813
+ const collectJarNS = function (jarPath) {
3814
+ const jarNSMapping = {};
3815
+ console.log(
3816
+ `About to identify class names for all jars in the path ${jarPath}`
3817
+ );
3818
+ // Execute jar tvf to get class names
3819
+ const jarFiles = getAllFiles(jarPath, "**/*.jar");
3820
+ if (jarFiles && jarFiles.length) {
3821
+ for (let jf of jarFiles) {
3822
+ const jarname = path.basename(jf);
3823
+ if (DEBUG_MODE) {
3824
+ console.log(`Executing 'jar tf ${jf}'`);
3825
+ }
3826
+ const jarResult = spawnSync("jar", ["-tf", jf], { encoding: "utf-8" });
3827
+ if (jarResult.status !== 0) {
3828
+ console.error(jarResult.stdout, jarResult.stderr);
3829
+ console.log(
3830
+ "Check if JRE is installed and the jar command is available in the PATH."
3831
+ );
3832
+ break;
3833
+ } else {
3834
+ const consolelines = (jarResult.stdout || "").split("\n");
3835
+ const nsList = consolelines
3836
+ .filter((l) => {
3837
+ return l.includes(".class") && !l.includes("-INF");
3838
+ })
3839
+ .map((e) => {
3840
+ return e
3841
+ .replace(/\/$/, "")
3842
+ .replace(/\//g, ".")
3843
+ .replace(".class", "");
3844
+ });
3845
+ jarNSMapping[jarname] = nsList;
3846
+ }
3847
+ }
3848
+ if (!jarNSMapping) {
3849
+ console.log(`Unable to determine class names for the jars in ${jarPath}`);
3850
+ }
3851
+ } else {
3852
+ console.log(`${jarPath} did not contain any jars.`);
3853
+ }
3854
+ if (DEBUG_MODE) {
3855
+ console.log("JAR Namespace mapping", jarNSMapping);
3856
+ }
3857
+ return jarNSMapping;
3858
+ };
3859
+ exports.collectJarNS = collectJarNS;
3860
+
3861
+ const parsePomXml = function (pomXmlData) {
3862
+ if (!pomXmlData) {
3863
+ return undefined;
3864
+ }
3865
+ const project = convert.xml2js(pomXmlData, {
3866
+ compact: true,
3867
+ spaces: 4,
3868
+ textKey: "_",
3869
+ attributesKey: "$",
3870
+ commentKey: "value"
3871
+ }).project;
3872
+ if (project) {
3873
+ let version = project.version ? project.version._ : undefined;
3874
+ if (!version && project.parent) {
3875
+ version = project.parent.version._;
3876
+ }
3877
+ let groupId = project.groupId ? project.groupId._ : undefined;
3878
+ if (!groupId && project.parent) {
3879
+ groupId = project.parent.groupId._;
3880
+ }
3881
+ return {
3882
+ artifactId: project.artifactId ? project.artifactId._ : "",
3883
+ groupId,
3884
+ version,
3885
+ description: project.description ? project.description._ : "",
3886
+ url: project.url ? project.url._ : "",
3887
+ scm: project.scm && project.scm.url ? project.scm.url._ : ""
3888
+ };
3889
+ }
3890
+ return undefined;
3891
+ };
3892
+ exports.parsePomXml = parsePomXml;
3893
+
3894
+ const parseJarManifest = function (jarMetadata) {
3895
+ const metadata = {};
3896
+ if (!jarMetadata) {
3897
+ return metadata;
3898
+ }
3899
+ jarMetadata.split("\n").forEach((l) => {
3900
+ if (l.includes(": ")) {
3901
+ const tmpA = l.split(": ");
3902
+ if (tmpA && tmpA.length === 2) {
3903
+ metadata[tmpA[0]] = tmpA[1].replace("\r", "");
3904
+ }
3905
+ }
3906
+ });
3907
+ return metadata;
3908
+ };
3909
+ exports.parseJarManifest = parseJarManifest;
3910
+
3911
+ /**
3912
+ * Method to extract a war or ear file
3913
+ *
3914
+ * @param {string} jarFile Path to jar file
3915
+ * @param {string} tempDir Temporary directory to use for extraction
3916
+ *
3917
+ * @return pkgList Package list
3918
+ */
3919
+ const extractJarArchive = function (jarFile, tempDir) {
3920
+ let pkgList = [];
3921
+ let jarFiles = [];
3922
+ const fname = path.basename(jarFile);
3923
+ let pomname = undefined;
3924
+ // If there is a pom file in the same directory, try to use it
3925
+ if (jarFile.endsWith(".jar")) {
3926
+ pomname = jarFile.replace(".jar", ".pom");
3927
+ }
3928
+ if (pomname && fs.existsSync(pomname)) {
3929
+ tempDir = path.dirname(jarFile);
3930
+ } else {
3931
+ fs.copyFileSync(jarFile, path.join(tempDir, fname));
3932
+ }
3933
+ if (jarFile.endsWith(".war") || jarFile.endsWith(".hpi")) {
3934
+ let jarResult = spawnSync("jar", ["-xf", path.join(tempDir, fname)], {
3935
+ encoding: "utf-8",
3936
+ cwd: tempDir
3937
+ });
3938
+ if (jarResult.status !== 0) {
3939
+ console.error(jarResult.stdout, jarResult.stderr);
3940
+ console.log(
3941
+ "Check if JRE is installed and the jar command is available in the PATH."
3942
+ );
3943
+ return pkgList;
3944
+ }
3945
+ jarFiles = getAllFiles(path.join(tempDir, "WEB-INF", "lib"), "**/*.jar");
3946
+ if (jarFile.endsWith(".hpi")) {
3947
+ jarFiles.push(jarFile);
3948
+ }
3949
+ } else {
3950
+ jarFiles = [path.join(tempDir, fname)];
3951
+ }
3952
+ if (jarFiles && jarFiles.length) {
3953
+ for (let jf of jarFiles) {
3954
+ pomname = jf.replace(".jar", ".pom");
3955
+ const jarname = path.basename(jf);
3956
+ // Ignore test jars
3957
+ if (
3958
+ jarname.endsWith("-tests.jar") ||
3959
+ jarname.endsWith("-test-sources.jar")
3960
+ ) {
3961
+ continue;
3962
+ }
3963
+ let manifestDir = path.join(tempDir, "META-INF");
3964
+ const manifestFile = path.join(manifestDir, "MANIFEST.MF");
3965
+ let jarResult = {
3966
+ status: 1
3967
+ };
3968
+ if (fs.existsSync(pomname)) {
3969
+ jarResult = { status: 0 };
3970
+ } else {
3971
+ jarResult = spawnSync("jar", ["-xf", jf], {
3972
+ encoding: "utf-8",
3973
+ cwd: tempDir
3974
+ });
3975
+ }
3976
+ if (jarResult.status !== 0) {
3977
+ console.error(jarResult.stdout, jarResult.stderr);
3978
+ } else {
3979
+ if (fs.existsSync(manifestFile)) {
3980
+ const jarMetadata = parseJarManifest(
3981
+ fs.readFileSync(manifestFile, {
3982
+ encoding: "utf-8"
3983
+ })
3984
+ );
3985
+ let group =
3986
+ jarMetadata["Extension-Name"] ||
3987
+ jarMetadata["Implementation-Vendor-Id"] ||
3988
+ jarMetadata["Bundle-SymbolicName"] ||
3989
+ jarMetadata["Automatic-Module-Name"];
3990
+ let name = "";
3991
+ if (
3992
+ jarMetadata["Bundle-Name"] &&
3993
+ !jarMetadata["Bundle-Name"].includes(" ")
3994
+ ) {
3995
+ name = jarMetadata["Bundle-Name"];
3996
+ } else if (
3997
+ jarMetadata["Implementation-Title"] &&
3998
+ !jarMetadata["Implementation-Title"].includes(" ")
3999
+ ) {
4000
+ name = jarMetadata["Implementation-Title"];
4001
+ }
4002
+ let version =
4003
+ jarMetadata["Bundle-Version"] ||
4004
+ jarMetadata["Implementation-Version"] ||
4005
+ jarMetadata["Specification-Version"];
4006
+ if (version && version.includes(" ")) {
4007
+ version = version.split(" ")[0];
4008
+ }
4009
+ if (!name && group) {
4010
+ name = path.basename(group.replace(/\./g, "/"));
4011
+ if (!group.startsWith("javax")) {
4012
+ group = path.dirname(group.replace(/\./g, "/"));
4013
+ group = group.replace(/\//g, ".");
4014
+ }
4015
+ }
4016
+ // Sometimes the group might already contain the name
4017
+ // Eg: group: org.checkerframework.checker.qual name: checker-qual
4018
+ if (name && group && !group.startsWith("javax")) {
4019
+ if (group.includes("." + name.toLowerCase().replace(/-/g, "."))) {
4020
+ group = group.replace(
4021
+ new RegExp("." + name.toLowerCase().replace(/-/g, ".") + "$"),
4022
+ ""
4023
+ );
4024
+ } else if (group.includes("." + name.toLowerCase())) {
4025
+ group = group.replace(
4026
+ new RegExp("." + name.toLowerCase() + "$"),
4027
+ ""
4028
+ );
4029
+ }
4030
+ }
4031
+ // Fallback to parsing jar filename
4032
+ if (!name || !version || name === "" || version === "") {
4033
+ const tmpA = jarname.split("-");
4034
+ if (tmpA && tmpA.length > 1) {
4035
+ const lastPart = tmpA[tmpA.length - 1];
4036
+ if (!version || version === "") {
4037
+ version = lastPart.replace(".jar", "");
4038
+ }
4039
+ if (!name || name === "") {
4040
+ name = jarname.replace("-" + lastPart, "") || "";
4041
+ }
4042
+ } else {
4043
+ name = jarname.replace(".jar", "");
4044
+ }
4045
+ }
4046
+ // Patch the group string
4047
+ for (const aprefix in vendorAliases) {
4048
+ if (name && name.startsWith(aprefix)) {
4049
+ group = vendorAliases[aprefix];
4050
+ break;
4051
+ }
4052
+ }
4053
+ if (name && version) {
4054
+ pkgList.push({
4055
+ group: group === "." ? "" : group || "",
4056
+ name: name || "",
4057
+ version,
4058
+ properties: [
4059
+ {
4060
+ name: "SrcFile",
4061
+ value: jarname
4062
+ }
4063
+ ]
4064
+ });
4065
+ } else {
4066
+ if (DEBUG_MODE) {
4067
+ console.log(`Ignored jar ${jarname}`, jarMetadata, name, version);
4068
+ }
4069
+ }
4070
+ }
4071
+ try {
4072
+ if (fs.rmSync && fs.existsSync(path.join(tempDir, "META-INF"))) {
4073
+ // Clean up META-INF
4074
+ fs.rmSync(path.join(tempDir, "META-INF"), {
4075
+ recursive: true,
4076
+ force: true
4077
+ });
4078
+ }
4079
+ } catch (err) {
4080
+ // ignore cleanup errors
4081
+ }
4082
+ }
4083
+ } // for
4084
+ } // if
4085
+ return pkgList;
4086
+ };
4087
+ exports.extractJarArchive = extractJarArchive;
4088
+
4089
+ /**
4090
+ * Determine the version of SBT used in compilation of this project.
4091
+ * By default it looks into a standard SBT location i.e.
4092
+ * <path-project>/project/build.properties
4093
+ * Returns `null` if the version cannot be determined.
4094
+ *
4095
+ * @param {string} projectPath Path to the SBT project
4096
+ */
4097
+ const determineSbtVersion = function (projectPath) {
4098
+ const buildPropFile = path.join(projectPath, "project", "build.properties");
4099
+ if (fs.existsSync(buildPropFile)) {
4100
+ let properties = propertiesReader(buildPropFile);
4101
+ let property = properties.get("sbt.version");
4102
+ if (property != null && semver.valid(property)) {
4103
+ return property;
4104
+ }
4105
+ }
4106
+ return null;
4107
+ };
4108
+ exports.determineSbtVersion = determineSbtVersion;
4109
+
4110
+ /**
4111
+ * Adds a new plugin to the SBT project by amending its plugins list.
4112
+ * Only recommended for SBT < 1.2.0 or otherwise use `addPluginSbtFile`
4113
+ * parameter.
4114
+ * The change manipulates the existing plugins' file by creating a copy of it
4115
+ * and returning a path where it is moved to.
4116
+ * Once the SBT task is complete one must always call `cleanupPlugin` to remove
4117
+ * the modifications made in place.
4118
+ *
4119
+ * @param {string} projectPath Path to the SBT project
4120
+ * @param {string} plugin Name of the plugin to add
4121
+ */
4122
+ const addPlugin = function (projectPath, plugin) {
4123
+ const pluginsFile = sbtPluginsPath(projectPath);
4124
+ var originalPluginsFile = null;
4125
+ if (fs.existsSync(pluginsFile)) {
4126
+ originalPluginsFile = pluginsFile + ".cdxgen";
4127
+ fs.copyFileSync(pluginsFile, originalPluginsFile);
4128
+ }
4129
+
4130
+ fs.writeFileSync(pluginsFile, plugin, { flag: "a" });
4131
+ return originalPluginsFile;
4132
+ };
4133
+ exports.addPlugin = addPlugin;
4134
+
4135
+ /**
4136
+ * Cleans up modifications to the project's plugins' file made by the
4137
+ * `addPlugin` function.
4138
+ *
4139
+ * @param {string} projectPath Path to the SBT project
4140
+ * @param {string} originalPluginsFile Location of the original plugins file, if any
4141
+ */
4142
+ const cleanupPlugin = function (projectPath, originalPluginsFile) {
4143
+ const pluginsFile = sbtPluginsPath(projectPath);
4144
+ if (fs.existsSync(pluginsFile)) {
4145
+ if (!originalPluginsFile) {
4146
+ // just remove the file, it was never there
4147
+ fs.unlinkSync(pluginsFile);
4148
+ return !fs.existsSync(pluginsFile);
4149
+ } else {
4150
+ // Bring back the original file
4151
+ fs.copyFileSync(originalPluginsFile, pluginsFile);
4152
+ fs.unlinkSync(originalPluginsFile);
4153
+ return true;
4154
+ }
4155
+ } else {
4156
+ return false;
4157
+ }
4158
+ };
4159
+ exports.cleanupPlugin = cleanupPlugin;
4160
+
4161
+ /**
4162
+ * Returns a default location of the plugins file.
4163
+ *
4164
+ * @param {string} projectPath Path to the SBT project
4165
+ */
4166
+ const sbtPluginsPath = function (projectPath) {
4167
+ return path.join(projectPath, "project", "plugins.sbt");
4168
+ };
4169
+ exports.sbtPluginsPath = sbtPluginsPath;
4170
+
4171
+ /**
4172
+ * Method to read a single file entry from a zip file
4173
+ *
4174
+ * @param {string} zipFile Zip file to read
4175
+ * @param {string} filePattern File pattern
4176
+ *
4177
+ * @returns File contents
4178
+ */
4179
+ const readZipEntry = async function (zipFile, filePattern) {
4180
+ let retData = undefined;
4181
+ try {
4182
+ const zip = new StreamZip.async({ file: zipFile });
4183
+ const entriesCount = await zip.entriesCount;
4184
+ if (!entriesCount) {
4185
+ return undefined;
4186
+ }
4187
+ const entries = await zip.entries();
4188
+ for (const entry of Object.values(entries)) {
4189
+ if (entry.isDirectory) {
4190
+ continue;
4191
+ }
4192
+ if (entry.name.endsWith(filePattern)) {
4193
+ const fileData = await zip.entryData(entry.name);
4194
+ retData = Buffer.from(fileData).toString();
4195
+ break;
4196
+ }
4197
+ }
4198
+ zip.close();
4199
+ } catch (e) {
4200
+ console.log(e);
4201
+ }
4202
+ return retData;
4203
+ };
4204
+ exports.readZipEntry = readZipEntry;
4205
+
4206
+ /**
4207
+ * Method to return the gradle command to use.
4208
+ *
4209
+ * @param {string} srcPath Path to look for gradlew wrapper
4210
+ * @param {string} rootPath Root directory to look for gradlew wrapper
4211
+ */
4212
+ const getGradleCommand = (srcPath, rootPath) => {
4213
+ let gradleCmd = "gradle";
4214
+
4215
+ let findGradleFile = "gradlew";
4216
+ if (os.platform() == "win32") {
4217
+ findGradleFile = "gradlew.bat";
4218
+ }
4219
+
4220
+ if (fs.existsSync(path.join(srcPath, findGradleFile))) {
4221
+ // Use local gradle wrapper if available
4222
+ // Enable execute permission
4223
+ try {
4224
+ fs.chmodSync(path.join(srcPath, findGradleFile), 0o775);
4225
+ } catch (e) {
4226
+ // continue regardless of error
4227
+ }
4228
+ gradleCmd = path.resolve(path.join(srcPath, findGradleFile));
4229
+ } else if (rootPath && fs.existsSync(path.join(rootPath, findGradleFile))) {
4230
+ // Check if the root directory has a wrapper script
4231
+ try {
4232
+ fs.chmodSync(path.join(rootPath, findGradleFile), 0o775);
4233
+ } catch (e) {
4234
+ // continue regardless of error
4235
+ }
4236
+ gradleCmd = path.resolve(path.join(rootPath, findGradleFile));
4237
+ } else if (process.env.GRADLE_CMD) {
4238
+ gradleCmd = process.env.GRADLE_CMD;
4239
+ } else if (process.env.GRADLE_HOME) {
4240
+ gradleCmd = path.join(process.env.GRADLE_HOME, "bin", "gradle");
4241
+ }
4242
+ return gradleCmd;
4243
+ };
4244
+ exports.getGradleCommand = getGradleCommand;
4245
+
4246
+ /**
4247
+ * Method to return the maven command to use.
4248
+ *
4249
+ * @param {string} srcPath Path to look for maven wrapper
4250
+ * @param {string} rootPath Root directory to look for maven wrapper
4251
+ */
4252
+ const getMavenCommand = (srcPath, rootPath) => {
4253
+ let mavenCmd = "mvn";
4254
+
4255
+ let findMavenFile = "mvnw";
4256
+ if (os.platform() == "win32") {
4257
+ findMavenFile = "mvnw.bat";
4258
+ }
4259
+
4260
+ if (fs.existsSync(path.join(srcPath, findMavenFile))) {
4261
+ // Use local maven wrapper if available
4262
+ // Enable execute permission
4263
+ try {
4264
+ fs.chmodSync(path.join(srcPath, findMavenFile), 0o775);
4265
+ } catch (e) {
4266
+ // continue regardless of error
4267
+ }
4268
+ mavenCmd = path.resolve(path.join(srcPath, findMavenFile));
4269
+ } else if (rootPath && fs.existsSync(path.join(rootPath, findMavenFile))) {
4270
+ // Check if the root directory has a wrapper script
4271
+ try {
4272
+ fs.chmodSync(path.join(rootPath, findMavenFile), 0o775);
4273
+ } catch (e) {
4274
+ // continue regardless of error
4275
+ }
4276
+ mavenCmd = path.resolve(path.join(rootPath, findMavenFile));
4277
+ } else if (process.env.MVN_CMD || process.env.MAVEN_CMD) {
4278
+ mavenCmd = process.env.MVN_CMD || process.env.MAVEN_CMD;
4279
+ } else if (process.env.MAVEN_HOME) {
4280
+ mavenCmd = path.join(process.env.MAVEN_HOME, "bin", "mvn");
4281
+ }
4282
+ return mavenCmd;
4283
+ };
4284
+ exports.getMavenCommand = getMavenCommand;