scanoss 1.12.2__py3-none-any.whl → 1.43.1__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.
- protoc_gen_swagger/__init__.py +13 -13
- protoc_gen_swagger/options/__init__.py +13 -13
- protoc_gen_swagger/options/annotations_pb2.py +18 -12
- protoc_gen_swagger/options/annotations_pb2.pyi +48 -0
- protoc_gen_swagger/options/annotations_pb2_grpc.py +20 -0
- protoc_gen_swagger/options/openapiv2_pb2.py +110 -99
- protoc_gen_swagger/options/openapiv2_pb2.pyi +1317 -0
- protoc_gen_swagger/options/openapiv2_pb2_grpc.py +20 -0
- scanoss/__init__.py +18 -18
- scanoss/api/__init__.py +17 -17
- scanoss/api/common/__init__.py +17 -17
- scanoss/api/common/v2/__init__.py +17 -17
- scanoss/api/common/v2/scanoss_common_pb2.py +49 -20
- scanoss/api/common/v2/scanoss_common_pb2_grpc.py +25 -0
- scanoss/api/components/__init__.py +17 -17
- scanoss/api/components/v2/__init__.py +17 -17
- scanoss/api/components/v2/scanoss_components_pb2.py +68 -43
- scanoss/api/components/v2/scanoss_components_pb2_grpc.py +83 -22
- scanoss/api/cryptography/v2/scanoss_cryptography_pb2.py +136 -21
- scanoss/api/cryptography/v2/scanoss_cryptography_pb2_grpc.py +766 -13
- scanoss/api/dependencies/__init__.py +17 -17
- scanoss/api/dependencies/v2/__init__.py +17 -17
- scanoss/api/dependencies/v2/scanoss_dependencies_pb2.py +56 -29
- scanoss/api/dependencies/v2/scanoss_dependencies_pb2_grpc.py +94 -8
- scanoss/api/geoprovenance/__init__.py +23 -0
- scanoss/api/geoprovenance/v2/__init__.py +23 -0
- scanoss/api/geoprovenance/v2/scanoss_geoprovenance_pb2.py +92 -0
- scanoss/api/geoprovenance/v2/scanoss_geoprovenance_pb2_grpc.py +381 -0
- scanoss/api/licenses/__init__.py +23 -0
- scanoss/api/licenses/v2/__init__.py +23 -0
- scanoss/api/licenses/v2/scanoss_licenses_pb2.py +84 -0
- scanoss/api/licenses/v2/scanoss_licenses_pb2_grpc.py +302 -0
- scanoss/api/scanning/__init__.py +17 -17
- scanoss/api/scanning/v2/__init__.py +17 -17
- scanoss/api/scanning/v2/scanoss_scanning_pb2.py +42 -13
- scanoss/api/scanning/v2/scanoss_scanning_pb2_grpc.py +86 -7
- scanoss/api/semgrep/__init__.py +17 -17
- scanoss/api/semgrep/v2/__init__.py +17 -17
- scanoss/api/semgrep/v2/scanoss_semgrep_pb2.py +50 -23
- scanoss/api/semgrep/v2/scanoss_semgrep_pb2_grpc.py +151 -16
- scanoss/api/vulnerabilities/__init__.py +17 -17
- scanoss/api/vulnerabilities/v2/__init__.py +17 -17
- scanoss/api/vulnerabilities/v2/scanoss_vulnerabilities_pb2.py +78 -31
- scanoss/api/vulnerabilities/v2/scanoss_vulnerabilities_pb2_grpc.py +282 -18
- scanoss/cli.py +2359 -370
- scanoss/components.py +187 -94
- scanoss/constants.py +22 -0
- scanoss/cryptography.py +308 -0
- scanoss/csvoutput.py +91 -58
- scanoss/cyclonedx.py +221 -63
- scanoss/data/build_date.txt +1 -1
- scanoss/data/osadl-copyleft.json +133 -0
- scanoss/data/scanoss-settings-schema.json +254 -0
- scanoss/delta.py +197 -0
- scanoss/export/__init__.py +23 -0
- scanoss/export/dependency_track.py +227 -0
- scanoss/file_filters.py +582 -0
- scanoss/filecount.py +75 -69
- scanoss/gitlabqualityreport.py +214 -0
- scanoss/header_filter.py +563 -0
- scanoss/inspection/__init__.py +23 -0
- scanoss/inspection/policy_check/__init__.py +0 -0
- scanoss/inspection/policy_check/dependency_track/__init__.py +0 -0
- scanoss/inspection/policy_check/dependency_track/project_violation.py +479 -0
- scanoss/inspection/policy_check/policy_check.py +222 -0
- scanoss/inspection/policy_check/scanoss/__init__.py +0 -0
- scanoss/inspection/policy_check/scanoss/copyleft.py +243 -0
- scanoss/inspection/policy_check/scanoss/undeclared_component.py +309 -0
- scanoss/inspection/summary/__init__.py +0 -0
- scanoss/inspection/summary/component_summary.py +170 -0
- scanoss/inspection/summary/license_summary.py +191 -0
- scanoss/inspection/summary/match_summary.py +341 -0
- scanoss/inspection/utils/file_utils.py +44 -0
- scanoss/inspection/utils/license_utils.py +123 -0
- scanoss/inspection/utils/markdown_utils.py +63 -0
- scanoss/inspection/utils/scan_result_processor.py +417 -0
- scanoss/osadl.py +125 -0
- scanoss/results.py +275 -0
- scanoss/scancodedeps.py +87 -38
- scanoss/scanner.py +431 -539
- scanoss/scanners/__init__.py +23 -0
- scanoss/scanners/container_scanner.py +476 -0
- scanoss/scanners/folder_hasher.py +358 -0
- scanoss/scanners/scanner_config.py +73 -0
- scanoss/scanners/scanner_hfh.py +252 -0
- scanoss/scanoss_settings.py +337 -0
- scanoss/scanossapi.py +140 -101
- scanoss/scanossbase.py +59 -22
- scanoss/scanossgrpc.py +799 -251
- scanoss/scanpostprocessor.py +294 -0
- scanoss/scantype.py +22 -21
- scanoss/services/dependency_track_service.py +132 -0
- scanoss/spdxlite.py +532 -174
- scanoss/threadeddependencies.py +148 -47
- scanoss/threadedscanning.py +53 -37
- scanoss/utils/__init__.py +23 -0
- scanoss/utils/abstract_presenter.py +103 -0
- scanoss/utils/crc64.py +96 -0
- scanoss/utils/file.py +84 -0
- scanoss/utils/scanoss_scan_results_utils.py +41 -0
- scanoss/utils/simhash.py +198 -0
- scanoss/winnowing.py +241 -63
- {scanoss-1.12.2.dist-info → scanoss-1.43.1.dist-info}/METADATA +18 -9
- scanoss-1.43.1.dist-info/RECORD +110 -0
- {scanoss-1.12.2.dist-info → scanoss-1.43.1.dist-info}/WHEEL +1 -1
- scanoss-1.12.2.dist-info/RECORD +0 -58
- {scanoss-1.12.2.dist-info → scanoss-1.43.1.dist-info}/entry_points.txt +0 -0
- {scanoss-1.12.2.dist-info → scanoss-1.43.1.dist-info/licenses}/LICENSE +0 -0
- {scanoss-1.12.2.dist-info → scanoss-1.43.1.dist-info}/top_level.txt +0 -0
scanoss/threadeddependencies.py
CHANGED
|
@@ -1,52 +1,78 @@
|
|
|
1
1
|
"""
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
2
|
+
SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
Copyright (c) 2021, SCANOSS
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in
|
|
14
|
+
all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
22
|
+
THE SOFTWARE.
|
|
23
23
|
"""
|
|
24
24
|
|
|
25
|
-
import
|
|
25
|
+
import json
|
|
26
26
|
import queue
|
|
27
|
-
|
|
27
|
+
import threading
|
|
28
28
|
from dataclasses import dataclass
|
|
29
|
+
from enum import Enum
|
|
30
|
+
from typing import Dict
|
|
29
31
|
|
|
30
32
|
from .scancodedeps import ScancodeDeps
|
|
31
33
|
from .scanossbase import ScanossBase
|
|
32
34
|
from .scanossgrpc import ScanossGrpc
|
|
33
35
|
|
|
34
|
-
DEP_FILE_PREFIX =
|
|
36
|
+
DEP_FILE_PREFIX = 'file=' # Default prefix to signify an existing parsed dependency file
|
|
37
|
+
|
|
38
|
+
DEV_DEPENDENCIES = {
|
|
39
|
+
'dev',
|
|
40
|
+
'test',
|
|
41
|
+
'development',
|
|
42
|
+
'provided',
|
|
43
|
+
'runtime',
|
|
44
|
+
'devDependencies',
|
|
45
|
+
'dev-dependencies',
|
|
46
|
+
'testImplementation',
|
|
47
|
+
'testCompile',
|
|
48
|
+
'Test',
|
|
49
|
+
'require-dev',
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
# Define an enum class
|
|
54
|
+
class SCOPE(Enum):
|
|
55
|
+
PRODUCTION = 'prod'
|
|
56
|
+
DEVELOPMENT = 'dev'
|
|
35
57
|
|
|
36
58
|
|
|
37
59
|
@dataclass
|
|
38
60
|
class ThreadedDependencies(ScanossBase):
|
|
39
|
-
"""
|
|
61
|
+
""" """
|
|
40
62
|
|
|
41
|
-
"""
|
|
42
63
|
inputs: queue.Queue = queue.Queue()
|
|
43
64
|
output: queue.Queue = queue.Queue()
|
|
44
65
|
|
|
45
|
-
def __init__(
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
66
|
+
def __init__( # noqa: PLR0913
|
|
67
|
+
self,
|
|
68
|
+
sc_deps: ScancodeDeps,
|
|
69
|
+
grpc_api: ScanossGrpc,
|
|
70
|
+
what_to_scan: str = None,
|
|
71
|
+
debug: bool = False,
|
|
72
|
+
trace: bool = False,
|
|
73
|
+
quiet: bool = False,
|
|
74
|
+
) -> None:
|
|
75
|
+
""" """
|
|
50
76
|
super().__init__(debug, trace, quiet)
|
|
51
77
|
self.sc_deps = sc_deps
|
|
52
78
|
self.grpc_api = grpc_api
|
|
@@ -66,9 +92,20 @@ class ThreadedDependencies(ScanossBase):
|
|
|
66
92
|
return resp
|
|
67
93
|
return None
|
|
68
94
|
|
|
69
|
-
def run(
|
|
95
|
+
def run(
|
|
96
|
+
self,
|
|
97
|
+
what_to_scan: str = None,
|
|
98
|
+
deps_file: str = None,
|
|
99
|
+
wait: bool = True,
|
|
100
|
+
dep_scope: SCOPE = None,
|
|
101
|
+
dep_scope_include: str = None,
|
|
102
|
+
dep_scope_exclude: str = None,
|
|
103
|
+
) -> bool:
|
|
70
104
|
"""
|
|
71
105
|
Initiate a background scan for the specified file/dir
|
|
106
|
+
:param dep_scope_exclude: comma separated list of dependency scopes to exclude
|
|
107
|
+
:param dep_scope_include: comma separated list of dependency scopes to include
|
|
108
|
+
:param dep_scope: Enum dependency scope to use
|
|
72
109
|
:param what_to_scan: file/folder to scan
|
|
73
110
|
:param deps_file: file to decorate instead of scan (overrides what_to_scan option)
|
|
74
111
|
:param wait: wait for completion
|
|
@@ -77,42 +114,105 @@ class ThreadedDependencies(ScanossBase):
|
|
|
77
114
|
what_to_scan = what_to_scan if what_to_scan else self.what_to_scan
|
|
78
115
|
self._errors = False
|
|
79
116
|
try:
|
|
80
|
-
if deps_file:
|
|
117
|
+
if deps_file: # Decorate the given dependencies file
|
|
81
118
|
self.print_msg(f'Decorating {deps_file} dependencies...')
|
|
82
|
-
self.inputs.put(f'{DEP_FILE_PREFIX}{deps_file}')
|
|
83
|
-
else:
|
|
119
|
+
self.inputs.put(f'{DEP_FILE_PREFIX}{deps_file}') # Add to queue and have parent wait on it
|
|
120
|
+
else: # Search for dependencies to decorate
|
|
84
121
|
self.print_msg(f'Searching {what_to_scan} for dependencies...')
|
|
85
|
-
self.inputs.put(what_to_scan)
|
|
86
|
-
|
|
122
|
+
self.inputs.put(what_to_scan)
|
|
123
|
+
# Add to queue and have parent wait on it
|
|
124
|
+
self._thread = threading.Thread(
|
|
125
|
+
target=self.scan_dependencies(dep_scope, dep_scope_include, dep_scope_exclude), daemon=True
|
|
126
|
+
)
|
|
87
127
|
self._thread.start()
|
|
88
128
|
except Exception as e:
|
|
89
129
|
self.print_stderr(f'ERROR: Problem running threaded dependencies: {e}')
|
|
90
130
|
self._errors = True
|
|
91
|
-
if wait and not self._errors:
|
|
131
|
+
if wait and not self._errors: # Wait for all inputs to complete
|
|
92
132
|
self.complete()
|
|
93
133
|
return False if self._errors else True
|
|
94
134
|
|
|
95
|
-
def
|
|
135
|
+
def filter_dependencies(self, deps, filter_dep) -> json:
|
|
136
|
+
files = deps.get('files', [])
|
|
137
|
+
# Iterate over files and their purls
|
|
138
|
+
for file in files:
|
|
139
|
+
if 'purls' in file:
|
|
140
|
+
# Filter purls with scope 'dependencies' and remove the scope field
|
|
141
|
+
file['purls'] = [
|
|
142
|
+
{key: value for key, value in purl.items() if key != 'scope'}
|
|
143
|
+
for purl in file['purls']
|
|
144
|
+
if filter_dep(purl.get('scope'))
|
|
145
|
+
]
|
|
146
|
+
# End of for loop
|
|
147
|
+
|
|
148
|
+
return {'files': [file for file in deps.get('files', []) if file.get('purls')]}
|
|
149
|
+
|
|
150
|
+
def filter_dependencies_by_scopes(
|
|
151
|
+
self, deps: json, dep_scope: SCOPE = None, dep_scope_include: str = None, dep_scope_exclude: str = None
|
|
152
|
+
) -> json:
|
|
153
|
+
# Predefined set of scopes to filter
|
|
154
|
+
|
|
155
|
+
# Include all scopes
|
|
156
|
+
include_all = (dep_scope is None or dep_scope == '') and dep_scope_include is None and dep_scope_exclude is None
|
|
157
|
+
## All dependencies, remove scope key
|
|
158
|
+
if include_all:
|
|
159
|
+
return self.filter_dependencies(deps, lambda purl: True)
|
|
160
|
+
|
|
161
|
+
# Use default list of scopes if a custom list is not set
|
|
162
|
+
if (dep_scope is not None and dep_scope != '') and dep_scope_include is None and dep_scope_exclude is None:
|
|
163
|
+
return self.filter_dependencies(
|
|
164
|
+
deps,
|
|
165
|
+
lambda purl: (dep_scope == SCOPE.PRODUCTION and purl not in DEV_DEPENDENCIES)
|
|
166
|
+
or dep_scope == SCOPE.DEVELOPMENT
|
|
167
|
+
and purl in DEV_DEPENDENCIES,
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
if (
|
|
171
|
+
(dep_scope_include is not None and dep_scope_include != '')
|
|
172
|
+
or dep_scope_exclude is not None
|
|
173
|
+
and dep_scope_exclude != ''
|
|
174
|
+
):
|
|
175
|
+
# Create sets from comma-separated strings, if provided
|
|
176
|
+
exclude = set(dep_scope_exclude.split(',')) if dep_scope_exclude else set()
|
|
177
|
+
include = set(dep_scope_include.split(',')) if dep_scope_include else set()
|
|
178
|
+
|
|
179
|
+
# Define a lambda function that checks the inclusion/exclusion logic
|
|
180
|
+
return self.filter_dependencies(
|
|
181
|
+
deps, lambda purl: (exclude and purl not in exclude) or (not exclude and purl in include)
|
|
182
|
+
)
|
|
183
|
+
return None
|
|
184
|
+
|
|
185
|
+
def scan_dependencies( # noqa: PLR0912
|
|
186
|
+
self, dep_scope: SCOPE = None, dep_scope_include: str = None, dep_scope_exclude: str = None
|
|
187
|
+
) -> None:
|
|
96
188
|
"""
|
|
97
189
|
Scan for dependencies from the given file/dir or from an input file (from the input queue).
|
|
98
190
|
"""
|
|
191
|
+
# TODO refactor to simplify branches based on PLR0912
|
|
99
192
|
current_thread = threading.get_ident()
|
|
100
193
|
self.print_trace(f'Starting dependency worker {current_thread}...')
|
|
101
194
|
try:
|
|
102
|
-
what_to_scan = self.inputs.get(timeout=5)
|
|
195
|
+
what_to_scan = self.inputs.get(timeout=5) # Begin processing the dependency request
|
|
103
196
|
deps = None
|
|
104
|
-
if what_to_scan.startswith(DEP_FILE_PREFIX):
|
|
197
|
+
if what_to_scan.startswith(DEP_FILE_PREFIX): # We have a pre-parsed dependency file, load it
|
|
105
198
|
deps = self.sc_deps.load_from_file(what_to_scan.strip(DEP_FILE_PREFIX))
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
199
|
+
elif not self.sc_deps.run_scan(what_to_scan=what_to_scan):
|
|
200
|
+
self._errors = True
|
|
201
|
+
else:
|
|
202
|
+
deps = self.sc_deps.produce_from_file()
|
|
203
|
+
if dep_scope is not None:
|
|
204
|
+
self.print_debug(f'Filtering {dep_scope.name} dependencies')
|
|
205
|
+
if dep_scope_include is not None:
|
|
206
|
+
self.print_debug(f"Including dependencies with '{dep_scope_include.split(',')}' scopes")
|
|
207
|
+
if dep_scope_exclude is not None:
|
|
208
|
+
self.print_debug(f"Excluding dependencies with '{dep_scope_exclude.split(',')}' scopes")
|
|
209
|
+
deps = self.filter_dependencies_by_scopes(deps, dep_scope, dep_scope_include, dep_scope_exclude)
|
|
210
|
+
|
|
111
211
|
if not self._errors:
|
|
112
212
|
if deps is None:
|
|
113
213
|
self.print_stderr(f'Problem searching for dependencies for: {what_to_scan}')
|
|
114
214
|
self._errors = True
|
|
115
|
-
elif not deps or len(deps.get(
|
|
215
|
+
elif not deps or len(deps.get('files', [])) == 0:
|
|
116
216
|
self.print_debug(f'No dependencies found to decorate for: {what_to_scan}')
|
|
117
217
|
else:
|
|
118
218
|
decorated_deps = self.grpc_api.get_dependencies(deps)
|
|
@@ -140,6 +240,7 @@ class ThreadedDependencies(ScanossBase):
|
|
|
140
240
|
self._errors = True
|
|
141
241
|
return True if not self._errors else False
|
|
142
242
|
|
|
243
|
+
|
|
143
244
|
#
|
|
144
245
|
# End of ThreadedDependencies Class
|
|
145
246
|
#
|
scanoss/threadedscanning.py
CHANGED
|
@@ -1,41 +1,45 @@
|
|
|
1
1
|
"""
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
2
|
+
SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
Copyright (c) 2021, SCANOSS
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in
|
|
14
|
+
all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
22
|
+
THE SOFTWARE.
|
|
23
23
|
"""
|
|
24
|
+
|
|
25
|
+
import atexit
|
|
24
26
|
import os
|
|
27
|
+
import queue
|
|
25
28
|
import sys
|
|
26
29
|
import threading
|
|
27
|
-
import queue
|
|
28
30
|
import time
|
|
29
|
-
|
|
30
|
-
from typing import Dict, List
|
|
31
31
|
from dataclasses import dataclass
|
|
32
|
+
from typing import Dict, List
|
|
33
|
+
|
|
32
34
|
from progress.bar import Bar
|
|
33
35
|
|
|
34
36
|
from .scanossapi import ScanossApi
|
|
35
37
|
from .scanossbase import ScanossBase
|
|
36
38
|
|
|
37
|
-
WFP_FILE_START =
|
|
38
|
-
MAX_ALLOWED_THREADS =
|
|
39
|
+
WFP_FILE_START = 'file='
|
|
40
|
+
MAX_ALLOWED_THREADS = (
|
|
41
|
+
int(os.environ.get('SCANOSS_MAX_ALLOWED_THREADS')) if os.environ.get('SCANOSS_MAX_ALLOWED_THREADS') else 30
|
|
42
|
+
)
|
|
39
43
|
|
|
40
44
|
|
|
41
45
|
@dataclass
|
|
@@ -45,13 +49,12 @@ class ThreadedScanning(ScanossBase):
|
|
|
45
49
|
WFP scan requests are loaded into the input queue.
|
|
46
50
|
Multiple threads pull messages off this queue, process the request and put the results into an output queue
|
|
47
51
|
"""
|
|
48
|
-
|
|
49
|
-
output: queue.Queue = queue.Queue()
|
|
52
|
+
|
|
50
53
|
bar: Bar = None
|
|
51
54
|
|
|
52
|
-
def __init__(
|
|
53
|
-
|
|
54
|
-
|
|
55
|
+
def __init__(
|
|
56
|
+
self, scanapi: ScanossApi, debug: bool = False, trace: bool = False, quiet: bool = False, nb_threads: int = 5
|
|
57
|
+
) -> None:
|
|
55
58
|
"""
|
|
56
59
|
Initialise the ThreadedScanning class
|
|
57
60
|
:param scanapi: SCANOSS API to send scan requests to
|
|
@@ -61,6 +64,8 @@ class ThreadedScanning(ScanossBase):
|
|
|
61
64
|
:param nb_threads: Number of thread to run (default 5)
|
|
62
65
|
"""
|
|
63
66
|
super().__init__(debug, trace, quiet)
|
|
67
|
+
self.inputs = queue.Queue()
|
|
68
|
+
self.output = queue.Queue()
|
|
64
69
|
self.scanapi = scanapi
|
|
65
70
|
self.nb_threads = nb_threads
|
|
66
71
|
self._isatty = sys.stderr.isatty()
|
|
@@ -73,6 +78,8 @@ class ThreadedScanning(ScanossBase):
|
|
|
73
78
|
if nb_threads > MAX_ALLOWED_THREADS:
|
|
74
79
|
self.print_msg(f'Warning: Requested threads too large: {nb_threads}. Reducing to {MAX_ALLOWED_THREADS}')
|
|
75
80
|
self.nb_threads = MAX_ALLOWED_THREADS
|
|
81
|
+
# Register cleanup to ensure progress bar is finished on exit
|
|
82
|
+
atexit.register(self.complete_bar)
|
|
76
83
|
|
|
77
84
|
@staticmethod
|
|
78
85
|
def __count_files_in_wfp(wfp: str):
|
|
@@ -97,6 +104,13 @@ class ThreadedScanning(ScanossBase):
|
|
|
97
104
|
if self.bar:
|
|
98
105
|
self.bar.finish()
|
|
99
106
|
|
|
107
|
+
def __del__(self):
|
|
108
|
+
"""Ensure progress bar is cleaned up when object is destroyed"""
|
|
109
|
+
try:
|
|
110
|
+
self.complete_bar()
|
|
111
|
+
except Exception:
|
|
112
|
+
pass # Ignore errors during cleanup
|
|
113
|
+
|
|
100
114
|
def set_bar(self, bar: Bar) -> None:
|
|
101
115
|
"""
|
|
102
116
|
Set the Progress Bar to display progress while scanning
|
|
@@ -130,7 +144,7 @@ class ThreadedScanning(ScanossBase):
|
|
|
130
144
|
:param wfp: WFP to add to queue
|
|
131
145
|
"""
|
|
132
146
|
if wfp is None or wfp == '':
|
|
133
|
-
self.print_stderr(
|
|
147
|
+
self.print_stderr('Warning: empty WFP. Skipping from scan...')
|
|
134
148
|
else:
|
|
135
149
|
self.inputs.put(wfp)
|
|
136
150
|
|
|
@@ -158,8 +172,9 @@ class ThreadedScanning(ScanossBase):
|
|
|
158
172
|
"""
|
|
159
173
|
qsize = self.inputs.qsize()
|
|
160
174
|
if qsize < self.nb_threads:
|
|
161
|
-
self.print_debug(
|
|
162
|
-
|
|
175
|
+
self.print_debug(
|
|
176
|
+
f'Input queue ({qsize}) smaller than requested threads: {self.nb_threads}. Reducing to queue size.'
|
|
177
|
+
)
|
|
163
178
|
self.nb_threads = qsize
|
|
164
179
|
else:
|
|
165
180
|
self.print_debug(f'Starting {self.nb_threads} threads to process {qsize} requests...')
|
|
@@ -171,7 +186,7 @@ class ThreadedScanning(ScanossBase):
|
|
|
171
186
|
except Exception as e:
|
|
172
187
|
self.print_stderr(f'ERROR: Problem running threaded scanning: {e}')
|
|
173
188
|
self._errors = True
|
|
174
|
-
if wait:
|
|
189
|
+
if wait: # Wait for all inputs to complete
|
|
175
190
|
self.complete()
|
|
176
191
|
return False if self._errors else True
|
|
177
192
|
|
|
@@ -180,7 +195,7 @@ class ThreadedScanning(ScanossBase):
|
|
|
180
195
|
Wait for input queue to complete processing and complete the worker threads
|
|
181
196
|
"""
|
|
182
197
|
self.inputs.join()
|
|
183
|
-
self._stop_event.set()
|
|
198
|
+
self._stop_event.set() # Tell the worker threads to stop
|
|
184
199
|
try:
|
|
185
200
|
for t in self._threads: # Complete the threads
|
|
186
201
|
t.join(timeout=5)
|
|
@@ -199,7 +214,7 @@ class ThreadedScanning(ScanossBase):
|
|
|
199
214
|
api_error = False
|
|
200
215
|
while not self._stop_event.is_set():
|
|
201
216
|
wfp = None
|
|
202
|
-
if not self.inputs.empty():
|
|
217
|
+
if not self.inputs.empty(): # Only try to get a message if there is one on the queue
|
|
203
218
|
try:
|
|
204
219
|
wfp = self.inputs.get(timeout=5)
|
|
205
220
|
if api_error: # API error encountered, so stop processing anymore requests
|
|
@@ -228,6 +243,7 @@ class ThreadedScanning(ScanossBase):
|
|
|
228
243
|
time.sleep(1) # Sleep while waiting for the queue depth to build up
|
|
229
244
|
self.print_trace(f'Thread complete ({current_thread}).')
|
|
230
245
|
|
|
246
|
+
|
|
231
247
|
#
|
|
232
248
|
# End of ThreadedScanning Class
|
|
233
249
|
#
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
Copyright (c) 2024, SCANOSS
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in
|
|
14
|
+
all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
22
|
+
THE SOFTWARE.
|
|
23
|
+
"""
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
|
|
3
|
+
from scanoss.scanossbase import ScanossBase
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class AbstractPresenter(ABC):
|
|
7
|
+
"""
|
|
8
|
+
Abstract presenter class for presenting output in a given format.
|
|
9
|
+
Subclasses must implement the _format_json_output and _format_plain_output methods.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
def __init__(
|
|
13
|
+
self,
|
|
14
|
+
debug: bool = False,
|
|
15
|
+
trace: bool = False,
|
|
16
|
+
quiet: bool = False,
|
|
17
|
+
output_file: str = None,
|
|
18
|
+
output_format: str = None,
|
|
19
|
+
):
|
|
20
|
+
"""
|
|
21
|
+
Initialize the presenter with the given output file and format.
|
|
22
|
+
"""
|
|
23
|
+
self.AVAILABLE_OUTPUT_FORMATS = ['json', 'plain', 'cyclonedx', 'spdxlite', 'csv', 'raw']
|
|
24
|
+
self.base = ScanossBase(debug=debug, trace=trace, quiet=quiet)
|
|
25
|
+
self.output_file = output_file
|
|
26
|
+
self.output_format = output_format
|
|
27
|
+
|
|
28
|
+
def present(self, output_format: str = None, output_file: str = None):
|
|
29
|
+
"""
|
|
30
|
+
Present the formatted output to a file if provided; otherwise, print to stdout.
|
|
31
|
+
"""
|
|
32
|
+
file_path = output_file or self.output_file
|
|
33
|
+
fmt = output_format or self.output_format
|
|
34
|
+
|
|
35
|
+
if fmt and fmt not in self.AVAILABLE_OUTPUT_FORMATS:
|
|
36
|
+
raise ValueError(
|
|
37
|
+
f"ERROR: Invalid output format '{fmt}'. Valid values are: {', '.join(self.AVAILABLE_OUTPUT_FORMATS)}"
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
if fmt == 'json':
|
|
41
|
+
content = self._format_json_output()
|
|
42
|
+
elif fmt == 'plain':
|
|
43
|
+
content = self._format_plain_output()
|
|
44
|
+
elif fmt == 'cyclonedx':
|
|
45
|
+
content = self._format_cyclonedx_output()
|
|
46
|
+
elif fmt == 'spdxlite':
|
|
47
|
+
content = self._format_spdxlite_output()
|
|
48
|
+
elif fmt == 'csv':
|
|
49
|
+
content = self._format_csv_output()
|
|
50
|
+
elif fmt == 'raw':
|
|
51
|
+
content = self._format_raw_output()
|
|
52
|
+
else:
|
|
53
|
+
content = self._format_plain_output()
|
|
54
|
+
|
|
55
|
+
self._present_output(content, file_path)
|
|
56
|
+
|
|
57
|
+
def _present_output(self, content: str, file_path: str = None):
|
|
58
|
+
"""
|
|
59
|
+
If a file path is provided, write to that file; otherwise, print the content to stdout.
|
|
60
|
+
"""
|
|
61
|
+
self.base.print_to_file_or_stdout(content, file_path)
|
|
62
|
+
|
|
63
|
+
@abstractmethod
|
|
64
|
+
def _format_cyclonedx_output(self) -> str:
|
|
65
|
+
"""
|
|
66
|
+
Return a CycloneDX string representation of the data.
|
|
67
|
+
"""
|
|
68
|
+
pass
|
|
69
|
+
|
|
70
|
+
@abstractmethod
|
|
71
|
+
def _format_spdxlite_output(self) -> str:
|
|
72
|
+
"""
|
|
73
|
+
Return a SPDX-Lite string representation of the data.
|
|
74
|
+
"""
|
|
75
|
+
pass
|
|
76
|
+
|
|
77
|
+
@abstractmethod
|
|
78
|
+
def _format_csv_output(self) -> str:
|
|
79
|
+
"""
|
|
80
|
+
Return a CSV string representation of the data.
|
|
81
|
+
"""
|
|
82
|
+
pass
|
|
83
|
+
|
|
84
|
+
@abstractmethod
|
|
85
|
+
def _format_json_output(self) -> str:
|
|
86
|
+
"""
|
|
87
|
+
Return a JSON string representation of the data.
|
|
88
|
+
"""
|
|
89
|
+
pass
|
|
90
|
+
|
|
91
|
+
@abstractmethod
|
|
92
|
+
def _format_plain_output(self) -> str:
|
|
93
|
+
"""
|
|
94
|
+
Return a plain text string representation of the data.
|
|
95
|
+
"""
|
|
96
|
+
pass
|
|
97
|
+
|
|
98
|
+
@abstractmethod
|
|
99
|
+
def _format_raw_output(self) -> str:
|
|
100
|
+
"""
|
|
101
|
+
Return a raw string representation of the data.
|
|
102
|
+
"""
|
|
103
|
+
pass
|
scanoss/utils/crc64.py
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
Copyright (c) 2025, SCANOSS
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in
|
|
14
|
+
all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
22
|
+
THE SOFTWARE.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
import struct
|
|
26
|
+
from typing import List
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class CRC64:
|
|
30
|
+
"""
|
|
31
|
+
CRC64 ECMA implementation matching Go's hash/crc64 package.
|
|
32
|
+
Uses polynomial: 0xC96C5795D7870F42
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
POLY = 0xC96C5795D7870F42
|
|
36
|
+
_TABLE = None
|
|
37
|
+
|
|
38
|
+
def __init__(self):
|
|
39
|
+
if CRC64._TABLE is None:
|
|
40
|
+
CRC64._TABLE = self._make_table()
|
|
41
|
+
self.crc = 0xFFFFFFFFFFFFFFFF # Initial value
|
|
42
|
+
|
|
43
|
+
def _make_table(self) -> list:
|
|
44
|
+
"""Generate the CRC64 lookup table."""
|
|
45
|
+
table = []
|
|
46
|
+
for i in range(256):
|
|
47
|
+
crc = i
|
|
48
|
+
for _ in range(8):
|
|
49
|
+
if crc & 1:
|
|
50
|
+
crc = (crc >> 1) ^ self.POLY
|
|
51
|
+
else:
|
|
52
|
+
crc >>= 1
|
|
53
|
+
table.append(crc)
|
|
54
|
+
return table
|
|
55
|
+
|
|
56
|
+
def update(self, data: bytes) -> None:
|
|
57
|
+
"""Update the CRC with new data."""
|
|
58
|
+
if isinstance(data, str):
|
|
59
|
+
data = data.encode('utf-8')
|
|
60
|
+
|
|
61
|
+
crc = self.crc
|
|
62
|
+
for b in data:
|
|
63
|
+
crc = (crc >> 8) ^ CRC64._TABLE[(crc ^ b) & 0xFF] # Use class-level table
|
|
64
|
+
self.crc = crc
|
|
65
|
+
|
|
66
|
+
def digest(self) -> int:
|
|
67
|
+
"""Get the current CRC value."""
|
|
68
|
+
return self.crc ^ 0xFFFFFFFFFFFFFFFF # Final XOR value
|
|
69
|
+
|
|
70
|
+
def hexdigest(self):
|
|
71
|
+
"""Get the current CRC value as a hexadecimal string."""
|
|
72
|
+
return format(self.digest(), '016x')
|
|
73
|
+
|
|
74
|
+
@classmethod
|
|
75
|
+
def checksum(cls, data: bytes) -> int:
|
|
76
|
+
"""Calculate CRC64 checksum for the given data."""
|
|
77
|
+
crc = cls()
|
|
78
|
+
crc.update(data)
|
|
79
|
+
return crc.digest()
|
|
80
|
+
|
|
81
|
+
@classmethod
|
|
82
|
+
def get_hash_buff(cls, buff: bytes) -> List[bytes]:
|
|
83
|
+
"""
|
|
84
|
+
Get the hash value of the given buffer, and converts it to 8 bytes in big-endian order.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
buff (bytes): The buffer to get the hash value of.
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
bytes: The hash value of the given buffer, and converts it to 8 bytes in big-endian order.
|
|
91
|
+
"""
|
|
92
|
+
crc = cls()
|
|
93
|
+
crc.update(buff)
|
|
94
|
+
hash_val = crc.digest()
|
|
95
|
+
|
|
96
|
+
return list(struct.pack('>Q', hash_val))
|