Skip to content

Commit d6b757c

Browse files
Adds HPSM and parsers for dependencies
Adds HPSM Adds parser for go.sum, yarn.lock and package-lock.json Adds tests for winnowing and new dependency parsers
1 parent da0ea8a commit d6b757c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+33374
-5450
lines changed

Diff for: package.json

+6-17
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "scanoss",
3-
"version": "0.2.28",
3+
"version": "0.3.0",
44
"description": "The SCANOSS JS package provides a simple, easy to consume module for interacting with SCANOSS APIs/Engine.",
55
"main": "build/main/index.js",
66
"typings": "build/main/index.d.ts",
@@ -18,16 +18,14 @@
1818
"fix": "run-s fix:*",
1919
"fix:prettier": "prettier \"src/**/*.ts\" --write",
2020
"fix:lint": "eslint src --ext .ts --fix",
21-
"test": "run-s build test:*",
21+
"test": "mocha -r ts-node/register 'tests/**/*.ts'",
2222
"test:lint": "eslint src --ext .ts",
2323
"test:prettier": "prettier \"src/**/*.ts\" --list-different",
2424
"test:spelling": "cspell \"{README.md,.github/*.md,src/**/*.ts}\"",
25-
"test:unit": "nyc --silent ava",
2625
"check-cli": "run-s test diff-integration-tests check-integration-tests",
2726
"check-integration-tests": "run-s check-integration-test:*",
2827
"diff-integration-tests": "mkdir -p diff && rm -rf diff/test && cp -r test diff/test && rm -rf diff/test/test-*/.git && cd diff && git init --quiet && git add -A && git commit --quiet --no-verify --allow-empty -m 'WIP' && echo '\\n\\nCommitted most recent integration test output in the \"diff\" directory. Review the changes with \"cd diff && git diff HEAD\" or your preferred git diff viewer.'",
2928
"watch:build": "tsc -p tsconfig.json -w",
30-
"watch:test": "nyc --silent ava --watch",
3129
"cov": "run-s build test:unit cov:html cov:lcov && open-cli coverage/index.html",
3230
"cov:html": "nyc report --reporter=html",
3331
"cov:lcov": "nyc report --reporter=lcov",
@@ -61,10 +59,12 @@
6159
"devDependencies": {
6260
"@ava/typescript": "^1.1.1",
6361
"@istanbuljs/nyc-config-typescript": "^1.0.1",
62+
"@types/chai": "^4.3.1",
63+
"@types/mocha": "^9.1.1",
6464
"@types/node": "^17.0.2",
6565
"@typescript-eslint/eslint-plugin": "^4.0.1",
6666
"@typescript-eslint/parser": "^4.0.1",
67-
"ava": "^3.12.1",
67+
"chai": "^4.3.6",
6868
"codecov": "^3.5.0",
6969
"cspell": "^4.1.0",
7070
"cz-conventional-changelog": "^3.3.0",
@@ -74,6 +74,7 @@
7474
"eslint-plugin-functional": "^3.0.2",
7575
"eslint-plugin-import": "^2.22.0",
7676
"gh-pages": "^3.1.0",
77+
"mocha": "^10.0.0",
7778
"npm-run-all": "^4.1.5",
7879
"nyc": "^15.1.0",
7980
"open-cli": "^6.0.1",
@@ -92,18 +93,6 @@
9293
"LICENSE",
9394
"README.md"
9495
],
95-
"ava": {
96-
"failFast": true,
97-
"timeout": "60s",
98-
"typescript": {
99-
"rewritePaths": {
100-
"src/": "build/main/"
101-
}
102-
},
103-
"files": [
104-
"!build/module/**"
105-
]
106-
},
10796
"config": {
10897
"commitizen": {
10998
"path": "cz-conventional-changelog"

Diff for: src/bin/cli-bin.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,14 @@ function CLIErrorHandler(e: Error) {
1616

1717
async function main() {
1818
program
19-
.version("0.2.28")
19+
.version("0.3.0")
2020
.description('The SCANOSS JS package provides a simple, easy to consume module for interacting with SCANOSS APIs/Engine.')
2121

2222
program
2323
.command('scan <source>')
2424
.description('Scan a folder/file')
2525
.option('-w, --wfp', 'Scan a .wfp file instead of a folder')
26+
.option('-H, --hpsm', 'Scan using winnowing high precision matching')
2627
.option('-c, --concurrency <number>', 'Number of concurrent connections to use while scanning (optional -default 10)')
2728
.option('-n, --ignore <ignore>', 'Ignore components specified in the SBOM file')
2829
.option('-f, --filter <path>', 'Loads an user defined filter (optional)')
@@ -51,6 +52,7 @@ async function main() {
5152
program
5253
.command('fingerprint <source>')
5354
.description('Generates fingerprints for a folder/file')
55+
.option('-H, --hpsm', 'Scan using winnowing high precision matching')
5456
.option('-o, --output <filename>', 'Output result file name (optional - default stdout)')
5557
.option('-p, --block-size <size>', 'Maximum size in Kb for each fingerprint block (optional - default 64Kb)')
5658
.action((source, options) => {fingerprintHandler(source, options).catch((e) => {CLIErrorHandler(e)})})

Diff for: src/commands/fingerprint.ts

+23-14
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
1-
import { isFolder } from "./helpers";
2-
import { ScannerEvents, WfpCalculator } from "..";
3-
import { Tree } from "../lib/tree/Tree";
4-
import { FilterList } from "../lib/filters/filtering";
5-
import { FingerprintPackage } from "../lib/scanner/WfpProvider/FingerprintPackage";
1+
import { isFolder } from './helpers';
2+
import { ScannerEvents, WfpCalculator, WinnowingMode } from '..';
3+
import { Tree } from '../lib/tree/Tree';
4+
import { FilterList } from '../lib/filters/filtering';
5+
import {
6+
FingerprintPackage
7+
} from '../lib/scanner/WfpProvider/FingerprintPackage';
68
import fs from 'fs';
7-
import { defaultFilter } from "../lib/filters/defaultFilter";
9+
import { defaultFilter } from '../lib/filters/defaultFilter';
810
import cliProgress from 'cli-progress';
11+
import { IWfpProviderInput } from '../lib/scanner/WfpProvider/WfpProvider';
912

1013

1114
export async function fingerprintHandler(rootPath: string, options: any): Promise<void> {
@@ -15,14 +18,19 @@ export async function fingerprintHandler(rootPath: string, options: any): Promis
1518
const pathIsFolder = await isFolder(rootPath);
1619
const wfpCalculator = new WfpCalculator();
1720

18-
const tree = new Tree(rootPath);
19-
const filter = new FilterList('');
20-
filter.load(defaultFilter as FilterList);
21+
let filesToFingerprint: string[] = [];
22+
if (pathIsFolder) {
23+
const tree = new Tree(rootPath);
24+
const filter = new FilterList('');
25+
filter.load(defaultFilter as FilterList);
2126

22-
tree.loadFilter(filter);
23-
tree.buildTree();
27+
tree.loadFilter(filter);
28+
tree.buildTree();
29+
filesToFingerprint = tree.getFileList();
30+
} else {
31+
filesToFingerprint.push(rootPath)
32+
}
2433

25-
const filesToFingerprint = tree.getFileList();
2634

2735
const optBar1 = { format: 'Fingerprinting Progress: [{bar}] {percentage}% | Fingerprinted {value} files of {total}' };
2836
const bar1 = new cliProgress.SingleBar(optBar1, cliProgress.Presets.shades_classic);
@@ -48,8 +56,9 @@ export async function fingerprintHandler(rootPath: string, options: any): Promis
4856
}
4957
});
5058

51-
52-
wfpCalculator.start({fileList: filesToFingerprint, folderRoot: rootPath});
59+
const wfpInput: IWfpProviderInput = {fileList: filesToFingerprint, folderRoot: rootPath}
60+
if(options.hpsm) wfpInput.winnowingMode = WinnowingMode.FULL_WINNOWING_HPSM;
61+
wfpCalculator.start(wfpInput);
5362

5463

5564
}

Diff for: src/commands/scan.ts

+10-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
11
import { Scanner } from '../lib/scanner/Scanner';
2-
import { SbomMode, ScannerEvents, ScannerInput } from '../lib/scanner/ScannerTypes';
2+
import {
3+
SbomMode,
4+
ScannerEvents,
5+
ScannerInput,
6+
WinnowingMode
7+
} from '../lib/scanner/ScannerTypes';
38
import { ScannerCfg } from '../lib/scanner/ScannerCfg';
49
import { Tree } from '../lib/tree/Tree';
510

611
import cliProgress from 'cli-progress';
7-
import { DispatcherResponse } from '../lib/scanner/Dispatcher/DispatcherResponse';
12+
import {
13+
DispatcherResponse
14+
} from '../lib/scanner/Dispatcher/DispatcherResponse';
815
import { defaultFilter } from '../lib/filters/defaultFilter';
916
import { FilterList } from '../lib/filters/filtering';
1017

@@ -80,6 +87,7 @@ export async function scanHandler(rootPath: string, options: any): Promise<void>
8087
});
8188

8289
if (options.wfp) scannerInput.wfpPath = rootPath;
90+
if (options.hpsm) scannerInput.winnowingMode = WinnowingMode.FULL_WINNOWING_HPSM
8391

8492
if (options.ignore) {
8593
scannerInput.sbom = fs.readFileSync(options.ignore, 'utf-8');

Diff for: src/lib/dependencies/DependencyScanner.ts

+20-12
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,10 @@ export class DependencyScanner {
2626
const grpcResponse = await this.grpcDependencyService.get(request);
2727
const response = grpcResponse.toObject();
2828

29-
3029
// Extract scope from localDependencies and add it to response
31-
this.mergeScopeField(localDependencies, response);
30+
// Also adds the requirements field from localDependency to the response if the server didn't
31+
// replay back a version
32+
this.repairOutput(localDependencies, response);
3233
return response;
3334
}
3435

@@ -71,27 +72,34 @@ export class DependencyScanner {
7172
}
7273
}
7374

74-
private mergeScopeField(localdependency: ILocalDependencies, serverResponse: DependencyResponse.AsObject
75-
): IDependencyResponse {
76-
77-
const scopeHashMap = {};
75+
private repairOutput(localdependency: ILocalDependencies, serverResponse: DependencyResponse.AsObject) {
7876

77+
// Create a map with key = [filename + purl] and the value is an object containing:
78+
// * The scope of the local dependency
79+
// * The requirement of the local dependency
80+
// Later this map is used to add information in the server response
81+
const localDependencyInfo = {};
7982
for (const file of localdependency.files) {
8083
const filename = file.file
81-
for (const dependency of file.purls) {
82-
if (dependency?.scope) scopeHashMap[filename + dependency.purl] = dependency.scope;
84+
for (const localDependency of file.purls) {
85+
const localInfo = {}
86+
if (localDependency?.scope) localInfo['scope'] = localDependency.scope
87+
if(localDependency?.requirement) localInfo['requirement'] = localDependency.requirement
88+
localDependencyInfo[filename + localDependency.purl] = localInfo;
8389
}
8490
}
8591

8692
for (const file of serverResponse.filesList) {
8793
const filename = file.file
8894
for (const dependency of file.dependenciesList) {
89-
const scope = scopeHashMap[filename + dependency.purl];
90-
if (scope) dependency['scope'] = scope;
95+
const localDependencyData = localDependencyInfo[filename + dependency.purl];
96+
if (localDependencyData?.scope) dependency['scope'] = localDependencyData.scope;
97+
if (localDependencyData?.requirement && dependency.version == "") {
98+
dependency.version = localDependencyData.requirement;
99+
}
91100
}
92101
}
93-
94-
return serverResponse;
95102
}
96103

104+
97105
}

Diff for: src/lib/dependencies/LocalDependency/LocalDependency.ts

+8-2
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,13 @@ import fs from 'fs';
33
import { ParserFuncType, ILocalDependencies } from "./DependencyTypes";
44
import { requirementsParser } from "./parsers/pyParser";
55
import { pomParser } from "./parsers/mavenParser";
6-
import { packagelockParser, packageParser } from "./parsers/npmParser";
6+
import {
7+
packagelockParser,
8+
packageParser,
9+
yarnLockParser
10+
} from './parsers/npmParser';
711
import { gemfilelockParser, gemfileParser } from "./parsers/rubyParser";
8-
import { goModParser } from './parsers/golangParser';
12+
import { goModParser, goSumParser } from './parsers/golangParser';
913

1014
export class LocalDependencies {
1115

@@ -24,6 +28,8 @@ export class LocalDependencies {
2428
'Gemfile': gemfileParser,
2529
'Gemfile.lock': gemfilelockParser,
2630
'go.mod': goModParser,
31+
'go.sum': goSumParser,
32+
'yarn.lock': yarnLockParser
2733
};
2834
}
2935

Diff for: src/lib/dependencies/LocalDependency/parsers/golangParser.ts

+67-15
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,6 @@ import { ILocalDependency } from "../DependencyTypes";
33
import { PackageURL } from "packageurl-js";
44
import path from "path";
55

6-
function parseModule (str: string) {
7-
const res = /(?<type>[^\s]+)(?:\s)+(?<ns_name>[^\s]+)\s?(?<version>(.*))/.exec(str);
8-
return {
9-
type: res.groups.type,
10-
ns_name: res.groups.ns_name,
11-
version: res.groups.version
12-
};
13-
}
14-
156
function parseDepLink (str: string) {
167
const res = /.*?(?<ns_name>[^\s]+)\s+(?<version>(.*))/.exec(str);
178
return {
@@ -20,6 +11,16 @@ function parseDepLink (str: string) {
2011
};
2112
}
2213

14+
function getDepDataGoModFromLine(line: string) {
15+
const {ns_name, version} = parseDepLink(line);
16+
17+
const index = ns_name.lastIndexOf('/');
18+
const namespace = ns_name.substring(0, index);
19+
const name = ns_name.substring(index + 1);
20+
21+
return {namespace, name, version}
22+
}
23+
2324
// Removes comments and spaces
2425
function preprocessLine(line: string) {
2526
if (line.includes("//"))
@@ -45,7 +46,6 @@ export function goModParser(fileContent: string, filePath: string): ILocalDepend
4546
const lines = fileContent.split('\n');
4647

4748
const require = [];
48-
const exclude = [];
4949

5050
for (let num = 0 ; num < lines.length ; num+=1) {
5151

@@ -57,11 +57,7 @@ export function goModParser(fileContent: string, filePath: string): ILocalDepend
5757
line = preprocessLine(lines[num]);
5858
while (num < lines.length && line!==')') {
5959

60-
const {ns_name, version} = parseDepLink(line);
61-
62-
const index = ns_name.lastIndexOf('/');
63-
const namespace = ns_name.substring(0, index);
64-
const name = ns_name.substring(index + 1);
60+
const {namespace, name, version} = getDepDataGoModFromLine(line)
6561

6662
const purlString = new PackageURL(PURL_TYPE, namespace, name, version, undefined, undefined).toString();
6763
results.purls.push({purl: purlString});
@@ -76,3 +72,59 @@ export function goModParser(fileContent: string, filePath: string): ILocalDepend
7672

7773
return results;
7874
}
75+
76+
77+
78+
79+
80+
function parseGoSumDepLink (str: string) {
81+
const res = /.*?(?<ns_name>[^\s]+)\s+(?<version>(.*))\s+h1:(?<checksum>(.*))/.exec(str);
82+
return {
83+
ns_name: res?.groups?.ns_name,
84+
version: res?.groups?.version,
85+
checksum: res?.groups?.checksum
86+
};
87+
}
88+
89+
function getDepDataGoSumFromLine(line: string) {
90+
const {ns_name, version} = parseGoSumDepLink(line);
91+
92+
if (!ns_name) return {};
93+
94+
const index = ns_name.lastIndexOf('/');
95+
const namespace = ns_name.substring(0, index);
96+
const name = ns_name.substring(index + 1);
97+
98+
return {namespace, name, version}
99+
}
100+
101+
// See reference on: https://go.dev/ref/mod#go-mod-file
102+
export function goSumParser(fileContent: string, filePath: string): ILocalDependency {
103+
104+
// If the file is not a go.mod manifest file, return an empty results
105+
const results: ILocalDependency = { file: filePath, purls: [] };
106+
if (path.basename(filePath) != 'go.sum')
107+
return results;
108+
109+
110+
const lines = fileContent.split('\n');
111+
for (let num = 0; num < lines.length; num += 1) {
112+
113+
let line = preprocessLine(lines[num]); //Deletes coments
114+
if(!line) continue
115+
116+
line = line.replace('/go.mod', '')
117+
const {namespace, name, version} = getDepDataGoSumFromLine(line)
118+
119+
if (!name) continue
120+
121+
//const purlString = new PackageURL(PURL_TYPE, namespace, name, undefined, undefined, undefined).toString();
122+
const purlString = `pkg:${PURL_TYPE}/${namespace}/${name}`
123+
results.purls.push({purl: purlString, requirement: version})
124+
}
125+
126+
return results;
127+
128+
129+
}
130+

0 commit comments

Comments
 (0)