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 CHANGED
@@ -22,4 +22,4 @@
22
22
  THE SOFTWARE.
23
23
  """
24
24
 
25
- __version__ = '1.8.0'
25
+ __version__ = '1.10.0'
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 args.threads > 1:
540
- scanner.scan_wfp_file_threaded(args.wfp)
541
- else:
542
- scanner.scan_wfp_file(args.wfp)
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)
@@ -1 +1 @@
1
- date: 20231114111907, utime: 1699960747
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 = { # File extensions to skip
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 FILTERED_EXT: # Check for file endings to ignore
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
- if f_size > 0: # Ignore broken links and empty files
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 scan_wfp_file_threaded(self, file: str = None, file_map: dict = None) -> bool:
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, CompVersionRequest, CompVersionResponse
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
 
@@ -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
- self.print_msg(f'Searching {what_to_scan} for dependencies...')
78
- self.inputs.put(what_to_scan) # Set up an input queue to enable the parent to wait for completion
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) # Begin processing the dependency request
96
- if not self.sc_deps.run_scan(what_to_scan=what_to_scan):
97
- self._errors = True
98
- else:
99
- deps = self.sc_deps.produce_from_file()
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.print_trace(f'No dependencies found to decorate for: {what_to_scan}')
105
- else: # TODO add API call to get dep data
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)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: scanoss
3
- Version: 1.8.0
3
+ Version: 1.10.0
4
4
  Summary: Simple Python library to leverage the SCANOSS APIs
5
5
  Home-page: https://scanoss.com
6
6
  Author: SCANOSS
@@ -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=N3xlYVsAadBxzi2AhmMaGQgaO7FkR3AT8296M7P8svI,1162
8
- scanoss/cli.py,sha256=M2MODqfg8MMCbpwqBbLEx0W5xI8lnJZyoYRxjY7qLWA,41016
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=e2FTCxBEkABrOmHlz7TNajn4EMPRXxBjHI_9GAjAodU,9996
14
- scanoss/scanner.py,sha256=-amQOrsf-ZnUQRaEe8i6uHEu_BbC4gSjhC4LaFk7OAc,42141
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=rN4P-iDk9QKTukdqLjXGJ0PnEkSkiZmGB0fcWhZ_zkk,20426
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=jGSU7QTwmclcltbXuyqPS1fpmcYApnqL0d65wtejc9M,5264
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=_uu7KfZJ5zvx0G35HZ-4Te-QZXVpJsvoTRQtXI9QvYA,40
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.8.0.dist-info/LICENSE,sha256=LLUaXoiyOroIbr5ubAyrxBOwSRLTm35ETO2FmLpy8QQ,1074
54
- scanoss-1.8.0.dist-info/METADATA,sha256=56es3JKCrJNkX0A6u57We6ppuVk2cXQSfbQdJzc9DHI,5905
55
- scanoss-1.8.0.dist-info/WHEEL,sha256=Xo9-1PvkuimrydujYJAjF7pCkriuXBpUPEjma1nZyJ0,92
56
- scanoss-1.8.0.dist-info/entry_points.txt,sha256=Uy28xnaDL5KQ7V77sZD5VLDXPNxYYzSr5tsqtiXVzAs,48
57
- scanoss-1.8.0.dist-info/top_level.txt,sha256=V11PrQ6Pnrc-nDF9xnisnJ8e6-i7HqSIKVNqduRWcL8,27
58
- scanoss-1.8.0.dist-info/RECORD,,
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,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.41.3)
2
+ Generator: bdist_wheel (0.42.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5