scanoss 1.8.0__py3-none-any.whl → 1.10.0__py3-none-any.whl
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.
- scanoss/__init__.py +1 -1
- scanoss/cli.py +33 -24
- scanoss/data/build_date.txt +1 -1
- scanoss/scancodedeps.py +20 -0
- scanoss/scanner.py +168 -16
- scanoss/scanossgrpc.py +2 -1
- scanoss/threadeddependencies.py +24 -12
- {scanoss-1.8.0.dist-info → scanoss-1.10.0.dist-info}/METADATA +1 -1
- {scanoss-1.8.0.dist-info → scanoss-1.10.0.dist-info}/RECORD +13 -13
- {scanoss-1.8.0.dist-info → scanoss-1.10.0.dist-info}/WHEEL +1 -1
- {scanoss-1.8.0.dist-info → scanoss-1.10.0.dist-info}/LICENSE +0 -0
- {scanoss-1.8.0.dist-info → scanoss-1.10.0.dist-info}/entry_points.txt +0 -0
- {scanoss-1.8.0.dist-info → scanoss-1.10.0.dist-info}/top_level.txt +0 -0
scanoss/__init__.py
CHANGED
scanoss/cli.py
CHANGED
|
@@ -86,7 +86,6 @@ def setup_args() -> None:
|
|
|
86
86
|
'256: disable best match only, 512: hide identified files, '
|
|
87
87
|
'1024: enable download_url, 2048: enable GitHub full path, '
|
|
88
88
|
'4096: disable extended server stats)')
|
|
89
|
-
p_scan.add_argument('--skip-snippets', '-S', action='store_true', help='Skip the generation of snippets')
|
|
90
89
|
p_scan.add_argument('--post-size', '-P', type=int, default=32,
|
|
91
90
|
help='Number of kilobytes to limit the post to while scanning (optional - default 32)')
|
|
92
91
|
p_scan.add_argument('--timeout', '-M', type=int, default=180,
|
|
@@ -94,17 +93,12 @@ def setup_args() -> None:
|
|
|
94
93
|
p_scan.add_argument('--retry', '-R', type=int, default=5,
|
|
95
94
|
help='Retry limit for API communication (optional - default 5)')
|
|
96
95
|
p_scan.add_argument('--no-wfp-output', action='store_true', help='Skip WFP file generation')
|
|
97
|
-
p_scan.add_argument('--all-extensions', action='store_true', help='Scan all file extensions')
|
|
98
|
-
p_scan.add_argument('--all-folders', action='store_true', help='Scan all folders')
|
|
99
|
-
p_scan.add_argument('--all-hidden', action='store_true', help='Scan all hidden files/folders')
|
|
100
|
-
p_scan.add_argument('--obfuscate', action='store_true', help='Obfuscate file paths and names')
|
|
101
96
|
p_scan.add_argument('--dependencies', '-D', action='store_true', help='Add Dependency scanning')
|
|
102
97
|
p_scan.add_argument('--dependencies-only', action='store_true', help='Run Dependency scanning only')
|
|
103
98
|
p_scan.add_argument('--sc-command', type=str,
|
|
104
99
|
help='Scancode command and path if required (optional - default scancode).')
|
|
105
100
|
p_scan.add_argument('--sc-timeout', type=int, default=600,
|
|
106
101
|
help='Timeout (in seconds) for scancode to complete (optional - default 600)')
|
|
107
|
-
p_scan.add_argument('--hpsm', '-H', action='store_true', help='Scan using High Precision Snippet Matching')
|
|
108
102
|
|
|
109
103
|
# Sub-command: fingerprint
|
|
110
104
|
p_wfp = subparsers.add_parser('fingerprint', aliases=['fp', 'wfp'],
|
|
@@ -116,12 +110,6 @@ def setup_args() -> None:
|
|
|
116
110
|
p_wfp.add_argument('--stdin', '-s', metavar='STDIN-FILENAME', type=str,
|
|
117
111
|
help='Fingerprint the file contents supplied via STDIN (optional)')
|
|
118
112
|
p_wfp.add_argument('--output', '-o', type=str, help='Output result file name (optional - default stdout).')
|
|
119
|
-
p_wfp.add_argument('--obfuscate', action='store_true', help='Obfuscate fingerprints')
|
|
120
|
-
p_wfp.add_argument('--skip-snippets', '-S', action='store_true', help='Skip the generation of snippets')
|
|
121
|
-
p_wfp.add_argument('--all-extensions', action='store_true', help='Fingerprint all file extensions')
|
|
122
|
-
p_wfp.add_argument('--all-folders', action='store_true', help='Fingerprint all folders')
|
|
123
|
-
p_wfp.add_argument('--all-hidden', action='store_true', help='Fingerprint all hidden files/folders')
|
|
124
|
-
p_wfp.add_argument('--hpsm', '-H', action='store_true', help='Use High Precision Snippet Matching algorithm.')
|
|
125
113
|
|
|
126
114
|
# Sub-command: dependency
|
|
127
115
|
p_dep = subparsers.add_parser('dependencies', aliases=['dp', 'dep'],
|
|
@@ -260,6 +248,19 @@ def setup_args() -> None:
|
|
|
260
248
|
help='SCANOSS API URL (optional - default: https://osskb.org/api/scan/direct)')
|
|
261
249
|
p.add_argument('--ignore-cert-errors', action='store_true', help='Ignore certificate errors')
|
|
262
250
|
|
|
251
|
+
# Global Scan/Fingerprint filter options
|
|
252
|
+
for p in [p_scan, p_wfp]:
|
|
253
|
+
p.add_argument('--obfuscate', action='store_true', help='Obfuscate fingerprints')
|
|
254
|
+
p.add_argument('--all-extensions', action='store_true', help='Fingerprint all file extensions')
|
|
255
|
+
p.add_argument('--all-folders', action='store_true', help='Fingerprint all folders')
|
|
256
|
+
p.add_argument('--all-hidden', action='store_true', help='Fingerprint all hidden files/folders')
|
|
257
|
+
p.add_argument('--hpsm', '-H', action='store_true', help='Use High Precision Snippet Matching algorithm.')
|
|
258
|
+
p.add_argument('--skip-snippets', '-S', action='store_true', help='Skip the generation of snippets')
|
|
259
|
+
p.add_argument('--skip-extension', '-E', type=str, action='append', help='File Extension to skip.')
|
|
260
|
+
p.add_argument('--skip-folder', '-O', type=str, action='append', help='Folder to skip.')
|
|
261
|
+
p.add_argument('--skip-size', '-Z', type=int, default=0,
|
|
262
|
+
help='Minimum file size to consider for fingerprinting (optional - default 0 bytes [unlimited])')
|
|
263
|
+
|
|
263
264
|
# Global Scan/GRPC options
|
|
264
265
|
for p in [p_scan, c_crypto, c_vulns, c_search, c_versions, c_semgrep]:
|
|
265
266
|
p.add_argument('--key', '-k', type=str,
|
|
@@ -374,8 +375,9 @@ def wfp(parser, args):
|
|
|
374
375
|
scan_options = 0 if args.skip_snippets else ScanType.SCAN_SNIPPETS.value # Skip snippet generation or not
|
|
375
376
|
scanner = Scanner(debug=args.debug, trace=args.trace, quiet=args.quiet, obfuscate=args.obfuscate,
|
|
376
377
|
scan_options=scan_options, all_extensions=args.all_extensions,
|
|
377
|
-
all_folders=args.all_folders, hidden_files_folders=args.all_hidden, hpsm=args.hpsm
|
|
378
|
-
|
|
378
|
+
all_folders=args.all_folders, hidden_files_folders=args.all_hidden, hpsm=args.hpsm,
|
|
379
|
+
skip_size=args.skip_size, skip_extensions=args.skip_extension, skip_folders=args.skip_folder
|
|
380
|
+
)
|
|
379
381
|
if args.stdin:
|
|
380
382
|
contents = sys.stdin.buffer.read()
|
|
381
383
|
scanner.wfp_contents(args.stdin, contents, scan_output)
|
|
@@ -406,7 +408,7 @@ def get_scan_options(args):
|
|
|
406
408
|
scan_dependencies = 0
|
|
407
409
|
if args.skip_snippets:
|
|
408
410
|
scan_snippets = 0
|
|
409
|
-
if args.dependencies:
|
|
411
|
+
if args.dependencies or args.dep:
|
|
410
412
|
scan_dependencies = ScanType.SCAN_DEPENDENCIES.value
|
|
411
413
|
if args.dependencies_only:
|
|
412
414
|
scan_files = scan_snippets = 0
|
|
@@ -437,8 +439,8 @@ def scan(parser, args):
|
|
|
437
439
|
args: Namespace
|
|
438
440
|
Parsed arguments
|
|
439
441
|
"""
|
|
440
|
-
if not args.scan_dir and not args.wfp and not args.stdin:
|
|
441
|
-
print_stderr('Please specify a file/folder, fingerprint (--wfp) or STDIN (--stdin)')
|
|
442
|
+
if not args.scan_dir and not args.wfp and not args.stdin and not args.dep:
|
|
443
|
+
print_stderr('Please specify a file/folder, fingerprint (--wfp), dependency (--dep), or STDIN (--stdin)')
|
|
442
444
|
parser.parse_args([args.subparser, '-h'])
|
|
443
445
|
exit(1)
|
|
444
446
|
if args.pac and args.proxy:
|
|
@@ -530,16 +532,17 @@ def scan(parser, args):
|
|
|
530
532
|
scan_options=scan_options, sc_timeout=args.sc_timeout, sc_command=args.sc_command,
|
|
531
533
|
grpc_url=args.api2url, obfuscate=args.obfuscate,
|
|
532
534
|
ignore_cert_errors=args.ignore_cert_errors, proxy=args.proxy, grpc_proxy=args.grpc_proxy,
|
|
533
|
-
pac=pac_file, ca_cert=args.ca_cert, retry=args.retry, hpsm=args.hpsm
|
|
535
|
+
pac=pac_file, ca_cert=args.ca_cert, retry=args.retry, hpsm=args.hpsm,
|
|
536
|
+
skip_size=args.skip_size, skip_extensions=args.skip_extension, skip_folders=args.skip_folder
|
|
534
537
|
)
|
|
535
538
|
if args.wfp:
|
|
536
539
|
if not scanner.is_file_or_snippet_scan():
|
|
537
540
|
print_stderr(f'Error: Cannot specify WFP scanning if file/snippet options are disabled ({scan_options})')
|
|
538
541
|
exit(1)
|
|
539
|
-
if
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
542
|
+
if scanner.is_dependency_scan() and not args.dep:
|
|
543
|
+
print_stderr(f'Error: Cannot specify WFP & Dependency scanning without a dependency file (--dep)')
|
|
544
|
+
exit(1)
|
|
545
|
+
scanner.scan_wfp_with_options(args.wfp, args.dep)
|
|
543
546
|
elif args.stdin:
|
|
544
547
|
contents = sys.stdin.buffer.read()
|
|
545
548
|
if not scanner.scan_contents(args.stdin, contents):
|
|
@@ -549,14 +552,20 @@ def scan(parser, args):
|
|
|
549
552
|
print_stderr(f'Error: File or folder specified does not exist: {args.scan_dir}.')
|
|
550
553
|
exit(1)
|
|
551
554
|
if os.path.isdir(args.scan_dir):
|
|
552
|
-
if not scanner.scan_folder_with_options(args.scan_dir, scanner.winnowing.file_map):
|
|
555
|
+
if not scanner.scan_folder_with_options(args.scan_dir, args.dep, scanner.winnowing.file_map):
|
|
553
556
|
exit(1)
|
|
554
557
|
elif os.path.isfile(args.scan_dir):
|
|
555
|
-
if not scanner.scan_file_with_options(args.scan_dir, scanner.winnowing.file_map):
|
|
558
|
+
if not scanner.scan_file_with_options(args.scan_dir, args.dep, scanner.winnowing.file_map):
|
|
556
559
|
exit(1)
|
|
557
560
|
else:
|
|
558
561
|
print_stderr(f'Error: Path specified is neither a file or a folder: {args.scan_dir}.')
|
|
559
562
|
exit(1)
|
|
563
|
+
elif args.dep:
|
|
564
|
+
if not args.dependencies_only:
|
|
565
|
+
print_stderr(f'Error: No file or folder specified to scan. Please add --dependencies-only to decorate dependency file only.')
|
|
566
|
+
exit(1)
|
|
567
|
+
if not scanner.scan_folder_with_options(".", args.dep, scanner.winnowing.file_map):
|
|
568
|
+
exit(1)
|
|
560
569
|
else:
|
|
561
570
|
print_stderr('No action found to process')
|
|
562
571
|
exit(1)
|
scanoss/data/build_date.txt
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
date:
|
|
1
|
+
date: 20240209161953, utime: 1707495593
|
scanoss/scancodedeps.py
CHANGED
|
@@ -215,6 +215,26 @@ class ScancodeDeps(ScanossBase):
|
|
|
215
215
|
self.print_stderr(f'ERROR: Issue running scancode dependency scan on {what_to_scan}: {e}')
|
|
216
216
|
return False
|
|
217
217
|
return True
|
|
218
|
+
|
|
219
|
+
def load_from_file(self, json_file: str = None) -> json:
|
|
220
|
+
"""
|
|
221
|
+
Load the parsed JSON dependencies file and return the json object
|
|
222
|
+
:param json_file: dependency json file
|
|
223
|
+
:return: SCANOSS dependency JSON
|
|
224
|
+
"""
|
|
225
|
+
if not json_file:
|
|
226
|
+
self.print_stderr('ERROR: No parsed JSON file provided to load.')
|
|
227
|
+
return None
|
|
228
|
+
if not os.path.isfile(json_file):
|
|
229
|
+
self.print_stderr(f'ERROR: parsed JSON file does not exist or is not a file: {json_file}')
|
|
230
|
+
return None
|
|
231
|
+
with open(json_file, 'r') as f:
|
|
232
|
+
try:
|
|
233
|
+
return json.loads(f.read())
|
|
234
|
+
except Exception as e:
|
|
235
|
+
self.print_stderr(f'ERROR: Problem loading input JSON: {e}')
|
|
236
|
+
return None
|
|
237
|
+
|
|
218
238
|
#
|
|
219
239
|
# End of ScancodeDeps Class
|
|
220
240
|
#
|
scanoss/scanner.py
CHANGED
|
@@ -58,7 +58,7 @@ FILTERED_DIRS = { # Folders to skip
|
|
|
58
58
|
FILTERED_DIR_EXT = { # Folder endings to skip
|
|
59
59
|
".egg-info"
|
|
60
60
|
}
|
|
61
|
-
FILTERED_EXT =
|
|
61
|
+
FILTERED_EXT = [ # File extensions to skip
|
|
62
62
|
".1", ".2", ".3", ".4", ".5", ".6", ".7", ".8", ".9", ".ac", ".adoc", ".am",
|
|
63
63
|
".asciidoc", ".bmp", ".build", ".cfg", ".chm", ".class", ".cmake", ".cnf",
|
|
64
64
|
".conf", ".config", ".contributors", ".copying", ".crt", ".csproj", ".css",
|
|
@@ -78,7 +78,7 @@ FILTERED_EXT = { # File extensions to skip
|
|
|
78
78
|
# File endings
|
|
79
79
|
"-doc", "changelog", "config", "copying", "license", "authors", "news", "licenses", "notice",
|
|
80
80
|
"readme", "swiftdoc", "texidoc", "todo", "version", "ignore", "manifest", "sqlite", "sqlite3"
|
|
81
|
-
|
|
81
|
+
]
|
|
82
82
|
FILTERED_FILES = { # Files to skip
|
|
83
83
|
"gradlew", "gradlew.bat", "mvnw", "mvnw.cmd", "gradle-wrapper.jar", "maven-wrapper.jar",
|
|
84
84
|
"thumbs.db", "babel.config.js", "license.txt", "license.md", "copying.lib", "makefile"
|
|
@@ -100,12 +100,17 @@ class Scanner(ScanossBase):
|
|
|
100
100
|
all_extensions: bool = False, all_folders: bool = False, hidden_files_folders: bool = False,
|
|
101
101
|
scan_options: int = 7, sc_timeout: int = 600, sc_command: str = None, grpc_url: str = None,
|
|
102
102
|
obfuscate: bool = False, ignore_cert_errors: bool = False, proxy: str = None, grpc_proxy: str = None,
|
|
103
|
-
ca_cert: str = None, pac: PACFile = None, retry: int = 5, hpsm: bool = False
|
|
103
|
+
ca_cert: str = None, pac: PACFile = None, retry: int = 5, hpsm: bool = False,
|
|
104
|
+
skip_size: int = 0, skip_extensions=None, skip_folders=None
|
|
104
105
|
):
|
|
105
106
|
"""
|
|
106
107
|
Initialise scanning class, including Winnowing, ScanossApi and ThreadedScanning
|
|
107
108
|
"""
|
|
108
109
|
super().__init__(debug, trace, quiet)
|
|
110
|
+
if skip_folders is None:
|
|
111
|
+
skip_folders = []
|
|
112
|
+
if skip_extensions is None:
|
|
113
|
+
skip_extensions = []
|
|
109
114
|
self.wfp = wfp if wfp else "scanner_output.wfp"
|
|
110
115
|
self.scan_output = scan_output
|
|
111
116
|
self.output_format = output_format
|
|
@@ -117,6 +122,8 @@ class Scanner(ScanossBase):
|
|
|
117
122
|
self.scan_options = scan_options
|
|
118
123
|
self._skip_snippets = True if not scan_options & ScanType.SCAN_SNIPPETS.value else False
|
|
119
124
|
self.hpsm = hpsm
|
|
125
|
+
self.skip_folders = skip_folders
|
|
126
|
+
self.skip_size = skip_size
|
|
120
127
|
ver_details = Scanner.version_details()
|
|
121
128
|
|
|
122
129
|
self.winnowing = Winnowing(debug=debug, quiet=quiet, skip_snippets=self._skip_snippets,
|
|
@@ -143,6 +150,9 @@ class Scanner(ScanossBase):
|
|
|
143
150
|
self.post_file_count = post_size if post_size > 0 else 32 # Max number of files for any given POST (default 32)
|
|
144
151
|
if self._skip_snippets:
|
|
145
152
|
self.max_post_size = 8 * 1024 # 8k Max post size if we're skipping snippets
|
|
153
|
+
self.skip_extensions = FILTERED_EXT
|
|
154
|
+
if skip_extensions: # Append extra file extensions to skip
|
|
155
|
+
self.skip_extensions.extend(skip_extensions)
|
|
146
156
|
|
|
147
157
|
def __filter_files(self, files: list) -> list:
|
|
148
158
|
"""
|
|
@@ -160,8 +170,8 @@ class Scanner(ScanossBase):
|
|
|
160
170
|
if f_lower in FILTERED_FILES: # Check for exact files to ignore
|
|
161
171
|
ignore = True
|
|
162
172
|
if not ignore:
|
|
163
|
-
for ending in
|
|
164
|
-
if f_lower.endswith(ending):
|
|
173
|
+
for ending in self.skip_extensions: # Check for file endings to ignore (static and user supplied)
|
|
174
|
+
if ending and f_lower.endswith(ending):
|
|
165
175
|
ignore = True
|
|
166
176
|
break
|
|
167
177
|
if not ignore:
|
|
@@ -181,10 +191,12 @@ class Scanner(ScanossBase):
|
|
|
181
191
|
ignore = True
|
|
182
192
|
if not ignore and not self.all_folders: # Skip this check if we're allowing all folders
|
|
183
193
|
d_lower = d.lower()
|
|
184
|
-
if d_lower in FILTERED_DIRS: # Ignore specific folders
|
|
194
|
+
if d_lower in FILTERED_DIRS: # Ignore specific folders (case insensitive)
|
|
195
|
+
ignore = True
|
|
196
|
+
elif self.skip_folders and d in self.skip_folders: # Ignore user-supplied folders (case sensitive)
|
|
185
197
|
ignore = True
|
|
186
198
|
if not ignore:
|
|
187
|
-
for de in FILTERED_DIR_EXT: # Ignore specific folder endings
|
|
199
|
+
for de in FILTERED_DIR_EXT: # Ignore specific folder endings (case insensitive)
|
|
188
200
|
if d_lower.endswith(de):
|
|
189
201
|
ignore = True
|
|
190
202
|
break
|
|
@@ -313,10 +325,11 @@ class Scanner(ScanossBase):
|
|
|
313
325
|
return True
|
|
314
326
|
return False
|
|
315
327
|
|
|
316
|
-
def scan_folder_with_options(self, scan_dir: str, file_map: dict = None) -> bool:
|
|
328
|
+
def scan_folder_with_options(self, scan_dir: str, deps_file: str = None, file_map: dict = None) -> bool:
|
|
317
329
|
"""
|
|
318
330
|
Scan the given folder for whatever scaning options that have been configured
|
|
319
331
|
:param scan_dir: directory to scan
|
|
332
|
+
:param deps_file: pre-parsed dependency file to decorate
|
|
320
333
|
:param file_map: mapping of obfuscated files back into originals
|
|
321
334
|
:return: True if successful, False otherwise
|
|
322
335
|
"""
|
|
@@ -331,7 +344,7 @@ class Scanner(ScanossBase):
|
|
|
331
344
|
if self.scan_output:
|
|
332
345
|
self.print_msg(f'Writing results to {self.scan_output}...')
|
|
333
346
|
if self.is_dependency_scan():
|
|
334
|
-
if not self.threaded_deps.run(what_to_scan=scan_dir, wait=False): # Kick off a background dependency scan
|
|
347
|
+
if not self.threaded_deps.run(what_to_scan=scan_dir, deps_file=deps_file, wait=False): # Kick off a background dependency scan
|
|
335
348
|
success = False
|
|
336
349
|
if self.is_file_or_snippet_scan():
|
|
337
350
|
if not self.scan_folder(scan_dir):
|
|
@@ -384,7 +397,8 @@ class Scanner(ScanossBase):
|
|
|
384
397
|
except Exception as e:
|
|
385
398
|
self.print_trace(
|
|
386
399
|
f'Ignoring missing symlink file: {file} ({e})') # Can fail if there is a broken symlink
|
|
387
|
-
|
|
400
|
+
# Ignore broken links and empty files or if a user-specified size limit is supplied
|
|
401
|
+
if f_size > 0 and (self.skip_size <= 0 or f_size > self.skip_size):
|
|
388
402
|
self.print_trace(f'Fingerprinting {path}...')
|
|
389
403
|
if spinner:
|
|
390
404
|
spinner.next()
|
|
@@ -542,10 +556,11 @@ class Scanner(ScanossBase):
|
|
|
542
556
|
success = False
|
|
543
557
|
return success
|
|
544
558
|
|
|
545
|
-
def scan_file_with_options(self, file: str, file_map: dict = None) -> bool:
|
|
559
|
+
def scan_file_with_options(self, file: str, deps_file: str = None, file_map: dict = None) -> bool:
|
|
546
560
|
"""
|
|
547
561
|
Scan the given file for whatever scaning options that have been configured
|
|
548
562
|
:param file: file to scan
|
|
563
|
+
:param deps_file: pre-parsed dependency file to decorate
|
|
549
564
|
:param file_map: mapping of obfuscated files back into originals
|
|
550
565
|
:return: True if successful, False otherwise
|
|
551
566
|
"""
|
|
@@ -560,7 +575,7 @@ class Scanner(ScanossBase):
|
|
|
560
575
|
if self.scan_output:
|
|
561
576
|
self.print_msg(f'Writing results to {self.scan_output}...')
|
|
562
577
|
if self.is_dependency_scan():
|
|
563
|
-
if not self.threaded_deps.run(what_to_scan=file, wait=False): # Kick off a background dependency scan
|
|
578
|
+
if not self.threaded_deps.run(what_to_scan=file, deps_file=deps_file, wait=False): # Kick off a background dependency scan
|
|
564
579
|
success = False
|
|
565
580
|
if self.is_file_or_snippet_scan():
|
|
566
581
|
if not self.scan_file(file):
|
|
@@ -596,6 +611,117 @@ class Scanner(ScanossBase):
|
|
|
596
611
|
success = False
|
|
597
612
|
return success
|
|
598
613
|
|
|
614
|
+
def scan_files(self, files: []) -> bool:
|
|
615
|
+
"""
|
|
616
|
+
Scan the specified list of files, producing fingerprints, send to the SCANOSS API and return results
|
|
617
|
+
Please note that by providing an explicit list you bypass any exclusions that may be defined on the scanner
|
|
618
|
+
:param files: list[str]
|
|
619
|
+
List of filenames to scan
|
|
620
|
+
:return True if successful, False otherwise
|
|
621
|
+
"""
|
|
622
|
+
success = True
|
|
623
|
+
if not files:
|
|
624
|
+
raise Exception(f"ERROR: Please provide a non-empty list of filenames to scan")
|
|
625
|
+
self.print_msg(f'Scanning {len(files)} files...')
|
|
626
|
+
spinner = None
|
|
627
|
+
if not self.quiet and self.isatty:
|
|
628
|
+
spinner = Spinner('Fingerprinting ')
|
|
629
|
+
save_wfps_for_print = not self.no_wfp_file or not self.threaded_scan
|
|
630
|
+
wfp_list = []
|
|
631
|
+
scan_block = ''
|
|
632
|
+
scan_size = 0
|
|
633
|
+
queue_size = 0
|
|
634
|
+
file_count = 0 # count all files fingerprinted
|
|
635
|
+
wfp_file_count = 0 # count number of files in each queue post
|
|
636
|
+
scan_started = False
|
|
637
|
+
for file in files:
|
|
638
|
+
if self.threaded_scan and self.threaded_scan.stop_scanning():
|
|
639
|
+
self.print_stderr('Warning: Aborting fingerprinting as the scanning service is not available.')
|
|
640
|
+
break
|
|
641
|
+
f_size = 0
|
|
642
|
+
try:
|
|
643
|
+
f_size = os.stat(file).st_size
|
|
644
|
+
except Exception as e:
|
|
645
|
+
self.print_trace(
|
|
646
|
+
f'Ignoring missing symlink file: {file} ({e})') # Can fail if there is a broken symlink
|
|
647
|
+
if f_size > 0: # Ignore broken links and empty files
|
|
648
|
+
self.print_trace(f'Fingerprinting {file}...')
|
|
649
|
+
if spinner:
|
|
650
|
+
spinner.next()
|
|
651
|
+
wfp = self.winnowing.wfp_for_file(file, file)
|
|
652
|
+
if wfp is None or wfp == '':
|
|
653
|
+
self.print_stderr(f'Warning: No WFP returned for {file}')
|
|
654
|
+
continue
|
|
655
|
+
if save_wfps_for_print:
|
|
656
|
+
wfp_list.append(wfp)
|
|
657
|
+
file_count += 1
|
|
658
|
+
if self.threaded_scan:
|
|
659
|
+
wfp_size = len(wfp.encode("utf-8"))
|
|
660
|
+
# If the WFP is bigger than the max post size and we already have something stored in the scan block, add it to the queue
|
|
661
|
+
if scan_block != '' and (wfp_size + scan_size) >= self.max_post_size:
|
|
662
|
+
self.threaded_scan.queue_add(scan_block)
|
|
663
|
+
queue_size += 1
|
|
664
|
+
scan_block = ''
|
|
665
|
+
wfp_file_count = 0
|
|
666
|
+
scan_block += wfp
|
|
667
|
+
scan_size = len(scan_block.encode("utf-8"))
|
|
668
|
+
wfp_file_count += 1
|
|
669
|
+
# If the scan request block (group of WFPs) or larger than the POST size or we have reached the file limit, add it to the queue
|
|
670
|
+
if wfp_file_count > self.post_file_count or scan_size >= self.max_post_size:
|
|
671
|
+
self.threaded_scan.queue_add(scan_block)
|
|
672
|
+
queue_size += 1
|
|
673
|
+
scan_block = ''
|
|
674
|
+
wfp_file_count = 0
|
|
675
|
+
if not scan_started and queue_size > self.nb_threads: # Start scanning if we have something to do
|
|
676
|
+
scan_started = True
|
|
677
|
+
if not self.threaded_scan.run(wait=False):
|
|
678
|
+
self.print_stderr(
|
|
679
|
+
f'Warning: Some errors encounted while scanning. Results might be incomplete.')
|
|
680
|
+
success = False
|
|
681
|
+
# End for loop
|
|
682
|
+
if self.threaded_scan and scan_block != '':
|
|
683
|
+
self.threaded_scan.queue_add(scan_block) # Make sure all files have been submitted
|
|
684
|
+
if spinner:
|
|
685
|
+
spinner.finish()
|
|
686
|
+
|
|
687
|
+
if file_count > 0:
|
|
688
|
+
if save_wfps_for_print: # Write a WFP file if no threading is requested
|
|
689
|
+
self.print_debug(f'Writing fingerprints to {self.wfp}')
|
|
690
|
+
with open(self.wfp, 'w') as f:
|
|
691
|
+
f.write(''.join(wfp_list))
|
|
692
|
+
else:
|
|
693
|
+
self.print_debug(f'Skipping writing WFP file {self.wfp}')
|
|
694
|
+
if self.threaded_scan:
|
|
695
|
+
success = self.__run_scan_threaded(scan_started, file_count)
|
|
696
|
+
else:
|
|
697
|
+
Scanner.print_stderr(f'Warning: No files found to scan from: {files}')
|
|
698
|
+
return success
|
|
699
|
+
|
|
700
|
+
def scan_files_with_options(self, files: [], deps_file: str = None, file_map: dict = None) -> bool:
|
|
701
|
+
"""
|
|
702
|
+
Scan the given list of files for whatever scaning options that have been configured
|
|
703
|
+
:param files: list of files to scan
|
|
704
|
+
:param deps_file: pre-parsed dependency file to decorate
|
|
705
|
+
:param file_map: mapping of obfuscated files back into originals
|
|
706
|
+
:return: True if successful, False otherwise
|
|
707
|
+
"""
|
|
708
|
+
success = True
|
|
709
|
+
if not files:
|
|
710
|
+
raise Exception(f"ERROR: Please specify a list of files to scan")
|
|
711
|
+
if not self.is_file_or_snippet_scan():
|
|
712
|
+
raise Exception(f"ERROR: file or snippet scan options have to be set to scan files: {files}")
|
|
713
|
+
if self.is_dependency_scan() or deps_file:
|
|
714
|
+
raise Exception(f"ERROR: The dependency scan option is currently not supported when scanning a list of files")
|
|
715
|
+
if self.scan_output:
|
|
716
|
+
self.print_msg(f'Writing results to {self.scan_output}...')
|
|
717
|
+
if self.is_file_or_snippet_scan():
|
|
718
|
+
if not self.scan_files(files):
|
|
719
|
+
success = False
|
|
720
|
+
if self.threaded_scan:
|
|
721
|
+
if not self.__finish_scan_threaded(file_map):
|
|
722
|
+
success = False
|
|
723
|
+
return success
|
|
724
|
+
|
|
599
725
|
def scan_contents(self, filename: str, contents: bytes) -> bool:
|
|
600
726
|
"""
|
|
601
727
|
Scan the given contents as a file
|
|
@@ -725,11 +851,39 @@ class Scanner(ScanossBase):
|
|
|
725
851
|
|
|
726
852
|
return success
|
|
727
853
|
|
|
728
|
-
def
|
|
854
|
+
def scan_wfp_with_options(self, wfp: str, deps_file: str, file_map: dict = None) -> bool:
|
|
855
|
+
"""
|
|
856
|
+
Scan the given WFP file for whatever scaning options that have been configured
|
|
857
|
+
:param wfp: WFP file to scan
|
|
858
|
+
:param deps_file: pre-parsed dependency file to decorate
|
|
859
|
+
:param file_map: mapping of obfuscated files back into originals
|
|
860
|
+
:return: True if successful, False otherwise
|
|
861
|
+
"""
|
|
862
|
+
success = True
|
|
863
|
+
wfp_file = wfp if wfp else self.wfp # If a WFP file is specified, use it, otherwise us the default
|
|
864
|
+
if not os.path.exists(wfp_file) or not os.path.isfile(wfp_file):
|
|
865
|
+
raise Exception(f"ERROR: Specified WFP file does not exist or is not a file: {wfp_file}")
|
|
866
|
+
|
|
867
|
+
if not self.is_file_or_snippet_scan() and not self.is_dependency_scan():
|
|
868
|
+
raise Exception(f"ERROR: No scan options defined to scan WFP: {wfp}")
|
|
869
|
+
|
|
870
|
+
if self.scan_output:
|
|
871
|
+
self.print_msg(f'Writing results to {self.scan_output}...')
|
|
872
|
+
if self.is_dependency_scan():
|
|
873
|
+
if not self.threaded_deps.run(deps_file=deps_file, wait=False): # Kick off a background dependency scan
|
|
874
|
+
success = False
|
|
875
|
+
if self.is_file_or_snippet_scan():
|
|
876
|
+
if not self.scan_wfp_file_threaded(wfp_file):
|
|
877
|
+
success = False
|
|
878
|
+
if self.threaded_scan:
|
|
879
|
+
if not self.__finish_scan_threaded(file_map):
|
|
880
|
+
success = False
|
|
881
|
+
return success
|
|
882
|
+
|
|
883
|
+
def scan_wfp_file_threaded(self, file: str = None) -> bool:
|
|
729
884
|
"""
|
|
730
885
|
Scan the contents of the specified WFP file (threaded)
|
|
731
886
|
:param file: WFP file to scan (optional)
|
|
732
|
-
:param file_map: mapping of obfuscated files back into originals (optional)
|
|
733
887
|
return: True if successful, False otherwise
|
|
734
888
|
"""
|
|
735
889
|
success = True
|
|
@@ -778,8 +932,6 @@ class Scanner(ScanossBase):
|
|
|
778
932
|
|
|
779
933
|
if not self.__run_scan_threaded(scan_started, file_count):
|
|
780
934
|
success = False
|
|
781
|
-
elif not self.__finish_scan_threaded(file_map):
|
|
782
|
-
success = False
|
|
783
935
|
return success
|
|
784
936
|
|
|
785
937
|
def scan_wfp(self, wfp: str) -> bool:
|
scanoss/scanossgrpc.py
CHANGED
|
@@ -43,7 +43,8 @@ from .api.dependencies.v2.scanoss_dependencies_pb2 import DependencyRequest, Dep
|
|
|
43
43
|
from .api.common.v2.scanoss_common_pb2 import EchoRequest, EchoResponse, StatusResponse, StatusCode, PurlRequest
|
|
44
44
|
from .api.vulnerabilities.v2.scanoss_vulnerabilities_pb2 import VulnerabilityResponse
|
|
45
45
|
from .api.semgrep.v2.scanoss_semgrep_pb2 import SemgrepResponse
|
|
46
|
-
from .api.components.v2.scanoss_components_pb2 import CompSearchRequest, CompSearchResponse,
|
|
46
|
+
from .api.components.v2.scanoss_components_pb2 import (CompSearchRequest, CompSearchResponse,
|
|
47
|
+
CompVersionRequest, CompVersionResponse)
|
|
47
48
|
from .scanossbase import ScanossBase
|
|
48
49
|
from . import __version__
|
|
49
50
|
|
scanoss/threadeddependencies.py
CHANGED
|
@@ -31,6 +31,8 @@ from .scancodedeps import ScancodeDeps
|
|
|
31
31
|
from .scanossbase import ScanossBase
|
|
32
32
|
from .scanossgrpc import ScanossGrpc
|
|
33
33
|
|
|
34
|
+
DEP_FILE_PREFIX = "file=" # Default prefix to signify an existing parsed dependency file
|
|
35
|
+
|
|
34
36
|
|
|
35
37
|
@dataclass
|
|
36
38
|
class ThreadedDependencies(ScanossBase):
|
|
@@ -64,18 +66,23 @@ class ThreadedDependencies(ScanossBase):
|
|
|
64
66
|
return resp
|
|
65
67
|
return None
|
|
66
68
|
|
|
67
|
-
def run(self, what_to_scan: str = None, wait: bool = True) -> bool:
|
|
69
|
+
def run(self, what_to_scan: str = None, deps_file: str = None, wait: bool = True) -> bool:
|
|
68
70
|
"""
|
|
69
71
|
Initiate a background scan for the specified file/dir
|
|
70
72
|
:param what_to_scan: file/folder to scan
|
|
73
|
+
:param deps_file: file to decorate instead of scan (overrides what_to_scan option)
|
|
71
74
|
:param wait: wait for completion
|
|
72
75
|
:return: True if successful, False if error encountered
|
|
73
76
|
"""
|
|
74
77
|
what_to_scan = what_to_scan if what_to_scan else self.what_to_scan
|
|
75
78
|
self._errors = False
|
|
76
79
|
try:
|
|
77
|
-
|
|
78
|
-
|
|
80
|
+
if deps_file: # Decorate the given dependencies file
|
|
81
|
+
self.print_msg(f'Decorating {deps_file} dependencies...')
|
|
82
|
+
self.inputs.put(f'{DEP_FILE_PREFIX}{deps_file}') # Add to queue and have parent wait on it
|
|
83
|
+
else: # Search for dependencies to decorate
|
|
84
|
+
self.print_msg(f'Searching {what_to_scan} for dependencies...')
|
|
85
|
+
self.inputs.put(what_to_scan) # Add to queue and have parent wait on it
|
|
79
86
|
self._thread = threading.Thread(target=self.scan_dependencies, daemon=True)
|
|
80
87
|
self._thread.start()
|
|
81
88
|
except Exception as e:
|
|
@@ -87,22 +94,27 @@ class ThreadedDependencies(ScanossBase):
|
|
|
87
94
|
|
|
88
95
|
def scan_dependencies(self) -> None:
|
|
89
96
|
"""
|
|
90
|
-
Scan for dependencies from the given file/dir (from the input queue)
|
|
97
|
+
Scan for dependencies from the given file/dir or from an input file (from the input queue).
|
|
91
98
|
"""
|
|
92
99
|
current_thread = threading.get_ident()
|
|
93
100
|
self.print_trace(f'Starting dependency worker {current_thread}...')
|
|
94
101
|
try:
|
|
95
|
-
what_to_scan = self.inputs.get(timeout=5)
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
102
|
+
what_to_scan = self.inputs.get(timeout=5) # Begin processing the dependency request
|
|
103
|
+
deps = None
|
|
104
|
+
if what_to_scan.startswith(DEP_FILE_PREFIX): # We have a pre-parsed dependency file, load it
|
|
105
|
+
deps = self.sc_deps.load_from_file(what_to_scan.strip(DEP_FILE_PREFIX))
|
|
106
|
+
else: # Search the file/folder for dependency files to parse
|
|
107
|
+
if not self.sc_deps.run_scan(what_to_scan=what_to_scan):
|
|
108
|
+
self._errors = True
|
|
109
|
+
else:
|
|
110
|
+
deps = self.sc_deps.produce_from_file()
|
|
111
|
+
if not self._errors:
|
|
100
112
|
if deps is None:
|
|
101
113
|
self.print_stderr(f'Problem searching for dependencies for: {what_to_scan}')
|
|
102
114
|
self._errors = True
|
|
103
|
-
elif not deps:
|
|
104
|
-
self.
|
|
105
|
-
else:
|
|
115
|
+
elif not deps or len(deps.get("files", [])) == 0:
|
|
116
|
+
self.print_debug(f'No dependencies found to decorate for: {what_to_scan}')
|
|
117
|
+
else:
|
|
106
118
|
decorated_deps = self.grpc_api.get_dependencies(deps)
|
|
107
119
|
if decorated_deps:
|
|
108
120
|
self.output.put(decorated_deps)
|
|
@@ -4,20 +4,20 @@ protoc_gen_swagger/options/annotations_pb2.py,sha256=b25EDD6gssUWnFby9gxgcpLIROT
|
|
|
4
4
|
protoc_gen_swagger/options/annotations_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
|
|
5
5
|
protoc_gen_swagger/options/openapiv2_pb2.py,sha256=vYElGp8E1vGHszvWqX97zNG9GFJ7u2QcdK9ouq0XdyI,14939
|
|
6
6
|
protoc_gen_swagger/options/openapiv2_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
|
|
7
|
-
scanoss/__init__.py,sha256=
|
|
8
|
-
scanoss/cli.py,sha256=
|
|
7
|
+
scanoss/__init__.py,sha256=kNW4sX9iGCu_4Qlaeuymupek7DpOaKBVIfL8PoLvQVE,1163
|
|
8
|
+
scanoss/cli.py,sha256=V-T9_tuG-2GfjULxPjtw_d-mEVZgBlOs3KbAL-tVCv4,41637
|
|
9
9
|
scanoss/components.py,sha256=ZHZ1KA69shxOASZK7USD9yPTITpAc_RXL5q5zpDK23o,12590
|
|
10
10
|
scanoss/csvoutput.py,sha256=hBwr_Fc6mBdOdXgyQcdFrockYH-PJ0jblowlExJ6OPg,9925
|
|
11
11
|
scanoss/cyclonedx.py,sha256=dPhj6sdwl2P8viC-sicAOLZzyklUR82NGFHaEeGYpeA,12065
|
|
12
12
|
scanoss/filecount.py,sha256=o7xb6m387ucnsU4H1OXGzf_AdWsudhAHe49T8uX4Ieo,6660
|
|
13
|
-
scanoss/scancodedeps.py,sha256=
|
|
14
|
-
scanoss/scanner.py,sha256
|
|
13
|
+
scanoss/scancodedeps.py,sha256=dPJsv9BmEsaM1IEzceJCnwLyu6Z0JwPposxdY4q0DAg,10775
|
|
14
|
+
scanoss/scanner.py,sha256=04cVbQI1K4Twwdu4kORKqACtvx4xjHOcOPEzUW_snpU,50149
|
|
15
15
|
scanoss/scanossapi.py,sha256=JU5B_TgaFs1hQn0W7RaHm9jBmZXFpFC89kwyKNDA1PA,12562
|
|
16
16
|
scanoss/scanossbase.py,sha256=WxYlWl6WxRArho4VKGFxEla8qYnjOXtF6EnwsHTrKm4,2319
|
|
17
|
-
scanoss/scanossgrpc.py,sha256=
|
|
17
|
+
scanoss/scanossgrpc.py,sha256=lf5LJ8FFzF6OAu0zNUvCvLD6-7bIzQNR5pn3XkRiyRo,20483
|
|
18
18
|
scanoss/scantype.py,sha256=R2-ExLGOrYxaJFtIK2AEo2caD0XrN1zpF5q1qT9Zsyc,1326
|
|
19
19
|
scanoss/spdxlite.py,sha256=ZAJlkgW5U9WUT35D1ZEwIJ-eLkbVLBv3lU_XIArfoik,15441
|
|
20
|
-
scanoss/threadeddependencies.py,sha256=
|
|
20
|
+
scanoss/threadeddependencies.py,sha256=JotQC9X3nnviblKe--OPS-7rr1W-cZjuxsxSPL-tbPg,6284
|
|
21
21
|
scanoss/threadedscanning.py,sha256=T0tL8W1IEX_hLY5ksrAl_iQqtxT_KbyDhTDHo6a7xFE,9387
|
|
22
22
|
scanoss/winnowing.py,sha256=CC4hB0iPHh8CmftnM2w8bmJlS8lAroi3kmJa6hHyfgk,15222
|
|
23
23
|
scanoss/api/__init__.py,sha256=KlDD87JmyZP-10T-fuJo0_v2zt1gxWfTgs70wjky9xg,1139
|
|
@@ -47,12 +47,12 @@ scanoss/api/vulnerabilities/__init__.py,sha256=FLQtiDiv85Q1Chk-sJ9ky9WOV1mulZhEK
|
|
|
47
47
|
scanoss/api/vulnerabilities/v2/__init__.py,sha256=FLQtiDiv85Q1Chk-sJ9ky9WOV1mulZhEKjiBihlwiaM,1139
|
|
48
48
|
scanoss/api/vulnerabilities/v2/scanoss_vulnerabilities_pb2.py,sha256=CFhF80av8tenGvn9AIsGEtRJPuV2dC_syA5JLZb2lDw,5464
|
|
49
49
|
scanoss/api/vulnerabilities/v2/scanoss_vulnerabilities_pb2_grpc.py,sha256=HlS4k4Zmx6RIAqaO9I96jD-eyF5yU6Xx04pVm7pdqOg,6864
|
|
50
|
-
scanoss/data/build_date.txt,sha256=
|
|
50
|
+
scanoss/data/build_date.txt,sha256=BzICu2lLBdWOULEC4j0yzP_SrZ4mkTzDp5dZUMyD5CI,40
|
|
51
51
|
scanoss/data/spdx-exceptions.json,sha256=s7UTYxC7jqQXr11YBlIWYCNwN6lRDFTR33Y8rpN_dA4,17953
|
|
52
52
|
scanoss/data/spdx-licenses.json,sha256=A6Z0q82gaTLtnopBfzeIVZjJFxkdRW1g2TuumQc-lII,228794
|
|
53
|
-
scanoss-1.
|
|
54
|
-
scanoss-1.
|
|
55
|
-
scanoss-1.
|
|
56
|
-
scanoss-1.
|
|
57
|
-
scanoss-1.
|
|
58
|
-
scanoss-1.
|
|
53
|
+
scanoss-1.10.0.dist-info/LICENSE,sha256=LLUaXoiyOroIbr5ubAyrxBOwSRLTm35ETO2FmLpy8QQ,1074
|
|
54
|
+
scanoss-1.10.0.dist-info/METADATA,sha256=W1j9E0Pg4ISCu4EUYsQFEwnkvtvMC5lqDOai-zhIGi8,5906
|
|
55
|
+
scanoss-1.10.0.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
|
56
|
+
scanoss-1.10.0.dist-info/entry_points.txt,sha256=Uy28xnaDL5KQ7V77sZD5VLDXPNxYYzSr5tsqtiXVzAs,48
|
|
57
|
+
scanoss-1.10.0.dist-info/top_level.txt,sha256=V11PrQ6Pnrc-nDF9xnisnJ8e6-i7HqSIKVNqduRWcL8,27
|
|
58
|
+
scanoss-1.10.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|