@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/.eslintrc.js +15 -0
- package/LICENSE +201 -0
- package/README.md +354 -0
- package/analyzer.js +189 -0
- package/bin/cdxgen +316 -0
- package/binary.js +507 -0
- package/docker.js +769 -0
- package/docker.test.js +72 -0
- package/index.js +4292 -0
- package/jest.config.js +181 -0
- package/known-licenses.json +27 -0
- package/lic-mapping.json +294 -0
- package/package.json +94 -0
- package/queries.json +68 -0
- package/server.js +110 -0
- package/spdx-licenses.json +500 -0
- package/utils.js +4284 -0
- package/utils.test.js +1660 -0
- package/vendor-alias.json +10 -0
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;
|