scanoss 1.20.0__py3-none-any.whl → 1.20.2__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 +12 -9
- protoc_gen_swagger/options/annotations_pb2_grpc.py +1 -1
- protoc_gen_swagger/options/openapiv2_pb2.py +98 -96
- protoc_gen_swagger/options/openapiv2_pb2_grpc.py +1 -1
- 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 +18 -18
- scanoss/api/common/v2/scanoss_common_pb2_grpc.py +1 -1
- scanoss/api/components/__init__.py +17 -17
- scanoss/api/components/v2/__init__.py +17 -17
- scanoss/api/components/v2/scanoss_components_pb2.py +48 -38
- scanoss/api/components/v2/scanoss_components_pb2_grpc.py +142 -96
- scanoss/api/cryptography/v2/scanoss_cryptography_pb2.py +22 -16
- scanoss/api/cryptography/v2/scanoss_cryptography_pb2_grpc.py +75 -49
- scanoss/api/dependencies/__init__.py +17 -17
- scanoss/api/dependencies/v2/__init__.py +17 -17
- scanoss/api/dependencies/v2/scanoss_dependencies_pb2.py +30 -24
- scanoss/api/dependencies/v2/scanoss_dependencies_pb2_grpc.py +75 -49
- scanoss/api/scanning/__init__.py +17 -17
- scanoss/api/scanning/v2/__init__.py +17 -17
- scanoss/api/scanning/v2/scanoss_scanning_pb2.py +10 -8
- scanoss/api/scanning/v2/scanoss_scanning_pb2_grpc.py +40 -32
- scanoss/api/semgrep/__init__.py +17 -17
- scanoss/api/semgrep/v2/__init__.py +17 -17
- scanoss/api/semgrep/v2/scanoss_semgrep_pb2.py +22 -18
- scanoss/api/semgrep/v2/scanoss_semgrep_pb2_grpc.py +71 -49
- scanoss/api/vulnerabilities/__init__.py +17 -17
- scanoss/api/vulnerabilities/v2/__init__.py +17 -17
- scanoss/api/vulnerabilities/v2/scanoss_vulnerabilities_pb2.py +37 -27
- scanoss/api/vulnerabilities/v2/scanoss_vulnerabilities_pb2_grpc.py +109 -72
- scanoss/cli.py +664 -356
- scanoss/components.py +67 -45
- scanoss/csvoutput.py +83 -56
- scanoss/cyclonedx.py +48 -46
- scanoss/data/build_date.txt +1 -1
- scanoss/file_filters.py +13 -15
- scanoss/filecount.py +43 -36
- scanoss/inspection/__init__.py +17 -17
- scanoss/inspection/copyleft.py +71 -58
- scanoss/inspection/policy_check.py +76 -53
- scanoss/inspection/undeclared_component.py +98 -75
- scanoss/inspection/utils/license_utils.py +66 -44
- scanoss/results.py +51 -60
- scanoss/scancodedeps.py +61 -38
- scanoss/scanner.py +203 -135
- scanoss/scanoss_settings.py +5 -3
- scanoss/scanossapi.py +98 -69
- scanoss/scanossbase.py +19 -19
- scanoss/scanossgrpc.py +73 -51
- scanoss/scanpostprocessor.py +9 -6
- scanoss/scantype.py +22 -21
- scanoss/spdxlite.py +265 -171
- scanoss/threadeddependencies.py +91 -61
- scanoss/threadedscanning.py +37 -31
- scanoss/utils/file.py +4 -4
- scanoss/winnowing.py +111 -47
- {scanoss-1.20.0.dist-info → scanoss-1.20.2.dist-info}/METADATA +1 -1
- scanoss-1.20.2.dist-info/RECORD +74 -0
- {scanoss-1.20.0.dist-info → scanoss-1.20.2.dist-info}/WHEEL +1 -1
- scanoss-1.20.0.dist-info/RECORD +0 -74
- {scanoss-1.20.0.dist-info → scanoss-1.20.2.dist-info}/LICENSE +0 -0
- {scanoss-1.20.0.dist-info → scanoss-1.20.2.dist-info}/entry_points.txt +0 -0
- {scanoss-1.20.0.dist-info → scanoss-1.20.2.dist-info}/top_level.txt +0 -0
scanoss/scantype.py
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
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
25
|
from enum import Enum
|
|
@@ -29,6 +29,7 @@ class ScanType(Enum):
|
|
|
29
29
|
"""
|
|
30
30
|
Octal Enum class describing all the scanning options
|
|
31
31
|
"""
|
|
32
|
+
|
|
32
33
|
SCAN_FILES = 1
|
|
33
34
|
SCAN_SNIPPETS = 2
|
|
34
35
|
SCAN_DEPENDENCIES = 4
|
scanoss/spdxlite.py
CHANGED
|
@@ -1,33 +1,35 @@
|
|
|
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 os.path
|
|
26
|
-
import sys
|
|
27
|
-
import hashlib
|
|
24
|
+
|
|
28
25
|
import datetime
|
|
29
26
|
import getpass
|
|
27
|
+
import hashlib
|
|
28
|
+
import json
|
|
29
|
+
import os.path
|
|
30
30
|
import re
|
|
31
|
+
import sys
|
|
32
|
+
|
|
31
33
|
import importlib_resources
|
|
32
34
|
|
|
33
35
|
from . import __version__
|
|
@@ -71,75 +73,103 @@ class SpdxLite:
|
|
|
71
73
|
if not data:
|
|
72
74
|
self.print_stderr('ERROR: No JSON data provided to parse.')
|
|
73
75
|
return None
|
|
74
|
-
|
|
76
|
+
|
|
77
|
+
self.print_debug('Processing raw results into summary format...')
|
|
78
|
+
return self._process_files(data)
|
|
79
|
+
|
|
80
|
+
def _process_files(self, data: json) -> dict:
|
|
81
|
+
"""Process each file in the data and build summary."""
|
|
82
|
+
summary = {}
|
|
83
|
+
for file_path in data:
|
|
84
|
+
file_details = data.get(file_path)
|
|
85
|
+
self._process_file_entries(file_path, file_details, summary)
|
|
86
|
+
return summary
|
|
87
|
+
|
|
88
|
+
def _process_file_entries(self, file_path: str, file_details: list, summary: dict):
|
|
89
|
+
"""Process entries for a single file."""
|
|
90
|
+
for entry in file_details:
|
|
91
|
+
id_details = entry.get('id')
|
|
92
|
+
if not id_details or id_details == 'none':
|
|
93
|
+
continue
|
|
94
|
+
|
|
95
|
+
if id_details == 'dependency':
|
|
96
|
+
self._process_dependency_entry(file_path, entry, summary)
|
|
97
|
+
else:
|
|
98
|
+
self._process_normal_entry(file_path, entry, summary)
|
|
99
|
+
|
|
100
|
+
def _process_dependency_entry(self, file_path: str, entry: dict, summary: dict):
|
|
101
|
+
"""Process a dependency type entry."""
|
|
102
|
+
dependencies = entry.get('dependencies')
|
|
103
|
+
if not dependencies:
|
|
104
|
+
self.print_stderr(f'Warning: No Dependencies found for {file_path}')
|
|
105
|
+
return
|
|
106
|
+
|
|
107
|
+
for dep in dependencies:
|
|
108
|
+
purl = dep.get('purl')
|
|
109
|
+
if not self._is_valid_purl(file_path, dep, purl, summary):
|
|
110
|
+
continue
|
|
111
|
+
|
|
112
|
+
summary[purl] = self._create_dependency_summary(dep)
|
|
113
|
+
|
|
114
|
+
def _process_normal_entry(self, file_path: str, entry: dict, summary: dict):
|
|
115
|
+
"""Process a normal file type entry."""
|
|
116
|
+
purls = entry.get('purl')
|
|
117
|
+
if not purls:
|
|
118
|
+
self.print_stderr(f'Purl block missing for {file_path}')
|
|
119
|
+
return
|
|
120
|
+
|
|
121
|
+
purl = purls[0] if purls else None
|
|
122
|
+
if not self._is_valid_purl(file_path, entry, purl, summary):
|
|
123
|
+
return
|
|
124
|
+
|
|
125
|
+
summary[purl] = self._create_normal_summary(entry)
|
|
126
|
+
|
|
127
|
+
def _is_valid_purl(self, file_path: str, entry: dict, purl: str, summary: dict) -> bool:
|
|
128
|
+
"""Check if PURL is valid and not already processed."""
|
|
129
|
+
if not purl:
|
|
130
|
+
self.print_stderr(f'Warning: No PURL found for {file_path}: {entry}')
|
|
131
|
+
return False
|
|
132
|
+
|
|
133
|
+
if summary.get(purl):
|
|
134
|
+
self.print_debug(f'Component {purl} already stored: {summary.get(purl)}')
|
|
135
|
+
return False
|
|
136
|
+
|
|
137
|
+
return True
|
|
138
|
+
|
|
139
|
+
def _create_dependency_summary(self, dep: dict) -> dict:
|
|
140
|
+
"""Create summary for dependency entry."""
|
|
75
141
|
summary = {}
|
|
76
|
-
for
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
for d in file_details:
|
|
80
|
-
id_details = d.get("id")
|
|
81
|
-
if not id_details or id_details == 'none': # Ignore files with no ids
|
|
82
|
-
continue
|
|
83
|
-
purl = None
|
|
84
|
-
if id_details == 'dependency': # Process dependency data
|
|
85
|
-
dependencies = d.get("dependencies")
|
|
86
|
-
if not dependencies:
|
|
87
|
-
self.print_stderr(f'Warning: No Dependencies found for {f}: {file_details}')
|
|
88
|
-
continue
|
|
89
|
-
for deps in dependencies:
|
|
90
|
-
# print(f'File: {f} Deps: {deps}')
|
|
91
|
-
purl = deps.get("purl")
|
|
92
|
-
if not purl:
|
|
93
|
-
self.print_stderr(f'Warning: No PURL found for {f}: {deps}')
|
|
94
|
-
continue
|
|
95
|
-
if summary.get(purl):
|
|
96
|
-
self.print_debug(f'Component {purl} already stored: {summary.get(purl)}')
|
|
97
|
-
continue
|
|
98
|
-
fd = {}
|
|
99
|
-
for field in ['component', 'version', 'url']:
|
|
100
|
-
fd[field] = deps.get(field, '')
|
|
101
|
-
licenses = deps.get('licenses')
|
|
102
|
-
fdl = []
|
|
103
|
-
if licenses:
|
|
104
|
-
dc = []
|
|
105
|
-
for lic in licenses:
|
|
106
|
-
name = lic.get("name")
|
|
107
|
-
if name not in dc: # Only save the license name once
|
|
108
|
-
fdl.append({'id': name})
|
|
109
|
-
dc.append(name)
|
|
110
|
-
fd['licenses'] = fdl
|
|
111
|
-
summary[purl] = fd
|
|
112
|
-
else: # Normal file id type
|
|
113
|
-
purls = d.get('purl')
|
|
114
|
-
if not purls:
|
|
115
|
-
self.print_stderr(f'Purl block missing for {f}: {file_details}')
|
|
116
|
-
continue
|
|
117
|
-
for p in purls:
|
|
118
|
-
self.print_debug(f'Purl: {p}')
|
|
119
|
-
purl = p
|
|
120
|
-
break
|
|
121
|
-
if not purl:
|
|
122
|
-
self.print_stderr(f'Warning: No PURL found for {f}: {file_details}')
|
|
123
|
-
continue
|
|
124
|
-
if summary.get(purl):
|
|
125
|
-
self.print_debug(f'Component {purl} already stored: {summary.get(purl)}')
|
|
126
|
-
continue
|
|
127
|
-
fd = {}
|
|
128
|
-
for field in ['id', 'vendor', 'component', 'version', 'latest', 'url']:
|
|
129
|
-
fd[field] = d.get(field)
|
|
130
|
-
licenses = d.get('licenses')
|
|
131
|
-
fdl = []
|
|
132
|
-
if licenses:
|
|
133
|
-
dc = []
|
|
134
|
-
for lic in licenses:
|
|
135
|
-
name = lic.get("name")
|
|
136
|
-
if name not in dc: # Only save the license name once
|
|
137
|
-
fdl.append({'id': name})
|
|
138
|
-
dc.append(name)
|
|
139
|
-
fd['licenses'] = fdl
|
|
140
|
-
summary[purl] = fd
|
|
142
|
+
for field in ['component', 'version', 'url']:
|
|
143
|
+
summary[field] = dep.get(field, '')
|
|
144
|
+
summary['licenses'] = self._process_licenses(dep.get('licenses'))
|
|
141
145
|
return summary
|
|
142
146
|
|
|
147
|
+
def _create_normal_summary(self, entry: dict) -> dict:
|
|
148
|
+
"""Create summary for normal file entry."""
|
|
149
|
+
summary = {}
|
|
150
|
+
fields = ['id', 'vendor', 'component', 'version', 'latest',
|
|
151
|
+
'url', 'url_hash', 'download_url']
|
|
152
|
+
for field in fields:
|
|
153
|
+
summary[field] = entry.get(field)
|
|
154
|
+
summary['licenses'] = self._process_licenses(entry.get('licenses'))
|
|
155
|
+
return summary
|
|
156
|
+
|
|
157
|
+
def _process_licenses(self, licenses: list) -> list:
|
|
158
|
+
"""Process license information and remove duplicates."""
|
|
159
|
+
if not licenses:
|
|
160
|
+
return []
|
|
161
|
+
|
|
162
|
+
processed_licenses = []
|
|
163
|
+
seen_names = set()
|
|
164
|
+
|
|
165
|
+
for license_info in licenses:
|
|
166
|
+
name = license_info.get('name')
|
|
167
|
+
if name and name not in seen_names:
|
|
168
|
+
processed_licenses.append({'id': name})
|
|
169
|
+
seen_names.add(name)
|
|
170
|
+
|
|
171
|
+
return processed_licenses
|
|
172
|
+
|
|
143
173
|
def produce_from_file(self, json_file: str, output_file: str = None) -> bool:
|
|
144
174
|
"""
|
|
145
175
|
Parse plain/raw input JSON file and produce SPDX Lite output
|
|
@@ -168,101 +198,163 @@ class SpdxLite:
|
|
|
168
198
|
if not raw_data:
|
|
169
199
|
self.print_stderr('ERROR: No SPDX data returned for the JSON string provided.')
|
|
170
200
|
return False
|
|
201
|
+
|
|
171
202
|
self.load_license_data()
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
now = datetime.datetime.utcnow()
|
|
203
|
+
spdx_document = self._create_base_document(raw_data)
|
|
204
|
+
self._process_packages(raw_data, spdx_document)
|
|
205
|
+
return self._write_output(spdx_document, output_file)
|
|
206
|
+
|
|
207
|
+
def _create_base_document(self, raw_data: dict) -> dict:
|
|
208
|
+
"""Create the base SPDX document structure."""
|
|
209
|
+
now = datetime.datetime.utcnow()
|
|
179
210
|
md5hex = hashlib.md5(f'{raw_data}-{now}'.encode('utf-8')).hexdigest()
|
|
180
|
-
|
|
211
|
+
|
|
212
|
+
return {
|
|
181
213
|
'spdxVersion': 'SPDX-2.2',
|
|
182
214
|
'dataLicense': 'CC0-1.0',
|
|
183
|
-
'SPDXID':
|
|
215
|
+
'SPDXID': 'SPDXRef-DOCUMENT',
|
|
184
216
|
'name': 'SCANOSS-SBOM',
|
|
185
|
-
'creationInfo':
|
|
186
|
-
'created': now.strftime('%Y-%m-%dT%H:%M:%SZ'),
|
|
187
|
-
'creators': [f'Tool: SCANOSS-PY: {__version__}', f'Person: {getpass.getuser()}']
|
|
188
|
-
},
|
|
217
|
+
'creationInfo': self._create_creation_info(now),
|
|
189
218
|
'documentNamespace': f'https://spdx.org/spdxdocs/scanoss-py-{__version__}-{md5hex}',
|
|
190
219
|
'documentDescribes': [],
|
|
191
220
|
'hasExtractedLicensingInfos': [],
|
|
192
|
-
'packages': []
|
|
221
|
+
'packages': [],
|
|
193
222
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
223
|
+
|
|
224
|
+
def _create_creation_info(self, timestamp: datetime.datetime) -> dict:
|
|
225
|
+
"""Create the creation info section."""
|
|
226
|
+
return {
|
|
227
|
+
'created': timestamp.strftime('%Y-%m-%dT%H:%M:%SZ'),
|
|
228
|
+
'creators': [
|
|
229
|
+
f'Tool: SCANOSS-PY: {__version__}',
|
|
230
|
+
f'Person: {getpass.getuser()}',
|
|
231
|
+
'Organization: SCANOSS'
|
|
232
|
+
],
|
|
233
|
+
'comment': 'SBOM Build information - SBOM Type: Build',
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
def _process_packages(self, raw_data: dict, spdx_document: dict):
|
|
237
|
+
"""Process packages and add them to the SPDX document."""
|
|
238
|
+
lic_refs = set()
|
|
239
|
+
|
|
240
|
+
for purl, comp in raw_data.items():
|
|
241
|
+
package_info = self._create_package_info(purl, comp, lic_refs)
|
|
242
|
+
spdx_document['packages'].append(package_info)
|
|
243
|
+
spdx_document['documentDescribes'].append(package_info['SPDXID'])
|
|
244
|
+
|
|
245
|
+
self._process_license_refs(lic_refs, spdx_document)
|
|
246
|
+
|
|
247
|
+
def _create_package_info(self, purl: str, comp: dict, lic_refs: set) -> dict:
|
|
248
|
+
"""Create package information for SPDX document."""
|
|
249
|
+
lic_text = self._process_package_licenses(comp.get('licenses', []), lic_refs)
|
|
250
|
+
comp_ver = comp.get('version')
|
|
251
|
+
purl_ver = f'{purl}@{comp_ver}'
|
|
252
|
+
purl_hash = hashlib.md5(purl_ver.encode('utf-8')).hexdigest()
|
|
253
|
+
|
|
254
|
+
return {
|
|
255
|
+
'name': comp.get('component'),
|
|
256
|
+
'SPDXID': f'SPDXRef-{purl_hash}',
|
|
257
|
+
'versionInfo': comp_ver,
|
|
258
|
+
'downloadLocation': comp.get('download_url') or comp.get('url'),
|
|
259
|
+
'homepage': comp.get('url', ''),
|
|
260
|
+
'licenseDeclared': lic_text,
|
|
261
|
+
'licenseConcluded': 'NOASSERTION',
|
|
262
|
+
'filesAnalyzed': False,
|
|
263
|
+
'copyrightText': 'NOASSERTION',
|
|
264
|
+
'supplier': f'Organization: {comp.get("vendor", "NOASSERTION")}',
|
|
265
|
+
'externalRefs': [
|
|
266
|
+
{
|
|
234
267
|
'referenceCategory': 'PACKAGE-MANAGER',
|
|
235
268
|
'referenceLocator': purl_ver,
|
|
236
269
|
'referenceType': 'purl'
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
270
|
+
}
|
|
271
|
+
],
|
|
272
|
+
'checksums': [
|
|
273
|
+
{
|
|
274
|
+
'algorithm': 'MD5',
|
|
275
|
+
'checksumValue': comp.get('url_hash') or '0' * 32
|
|
276
|
+
}
|
|
277
|
+
],
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
def _process_package_licenses(self, licenses: list, lic_refs: set) -> str:
|
|
281
|
+
"""Process licenses and return license text."""
|
|
282
|
+
if not licenses:
|
|
283
|
+
return 'NOASSERTION'
|
|
284
|
+
|
|
285
|
+
lic_set = set()
|
|
286
|
+
for lic in licenses:
|
|
287
|
+
lc_id = lic.get('id')
|
|
288
|
+
if lc_id:
|
|
289
|
+
self._process_license_id(lc_id, lic_refs, lic_set)
|
|
290
|
+
|
|
291
|
+
return self._format_license_text(lic_set)
|
|
292
|
+
|
|
293
|
+
def _process_license_id(self, lc_id: str, lic_refs: set, lic_set: set):
|
|
294
|
+
"""Process individual license ID."""
|
|
295
|
+
spdx_id = self.get_spdx_license_id(lc_id)
|
|
296
|
+
if not spdx_id:
|
|
297
|
+
if not lc_id.startswith('LicenseRef'):
|
|
298
|
+
lc_id = f'LicenseRef-{lc_id}'
|
|
299
|
+
lic_refs.add(lc_id)
|
|
300
|
+
lic_set.add(spdx_id if spdx_id else lc_id)
|
|
301
|
+
|
|
302
|
+
def _format_license_text(self, lic_set: set) -> str:
|
|
303
|
+
"""Format the license text with proper syntax."""
|
|
304
|
+
if not lic_set:
|
|
305
|
+
return 'NOASSERTION'
|
|
306
|
+
|
|
307
|
+
lic_text = ' AND '.join(lic_set)
|
|
308
|
+
if len(lic_set) > 1:
|
|
309
|
+
lic_text = f'({lic_text})'
|
|
310
|
+
return lic_text
|
|
311
|
+
|
|
312
|
+
def _process_license_refs(self, lic_refs: set, spdx_document: dict):
|
|
313
|
+
"""Process and add license references to the document."""
|
|
314
|
+
for lic_ref in lic_refs:
|
|
315
|
+
license_info = self._parse_license_ref(lic_ref)
|
|
316
|
+
spdx_document['hasExtractedLicensingInfos'].append(license_info)
|
|
317
|
+
|
|
318
|
+
def _parse_license_ref(self, lic_ref: str) -> dict:
|
|
319
|
+
"""Parse license reference and create info dictionary."""
|
|
320
|
+
source, name = self._extract_license_info(lic_ref)
|
|
321
|
+
source_text = f' by {source}.' if source else '.'
|
|
322
|
+
|
|
323
|
+
return {
|
|
324
|
+
'licenseId': lic_ref,
|
|
325
|
+
'name': name.replace('-', ' '),
|
|
326
|
+
'extractedText': 'Detected license, please review component source code.',
|
|
327
|
+
'comment': f'Detected license{source_text}',
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
def _extract_license_info(self, lic_ref: str):
|
|
331
|
+
"""Extract source and name from license reference."""
|
|
332
|
+
match = re.search(r'^LicenseRef-(scancode-|scanoss-|)(\S+)$', lic_ref, re.IGNORECASE)
|
|
333
|
+
if match:
|
|
334
|
+
source = match.group(1).replace('-', '')
|
|
335
|
+
name = match.group(2)
|
|
336
|
+
else:
|
|
241
337
|
source = ''
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
338
|
+
name = lic_ref
|
|
339
|
+
return source, name
|
|
340
|
+
|
|
341
|
+
def _write_output(self, data: dict, output_file: str = None) -> bool:
|
|
342
|
+
"""Write the SPDX document to output."""
|
|
343
|
+
try:
|
|
344
|
+
file = self._get_output_file(output_file)
|
|
345
|
+
print(json.dumps(data, indent=2), file=file)
|
|
346
|
+
if output_file:
|
|
347
|
+
file.close()
|
|
348
|
+
return True
|
|
349
|
+
except Exception as e:
|
|
350
|
+
self.print_stderr(f'Error writing output: {str(e)}')
|
|
351
|
+
return False
|
|
352
|
+
|
|
353
|
+
def _get_output_file(self, output_file: str = None):
|
|
354
|
+
"""Get the appropriate output file handle."""
|
|
258
355
|
if not output_file and self.output_file:
|
|
259
356
|
output_file = self.output_file
|
|
260
|
-
if output_file
|
|
261
|
-
file = open(output_file, 'w')
|
|
262
|
-
print(json.dumps(data, indent=2), file=file)
|
|
263
|
-
if output_file:
|
|
264
|
-
file.close()
|
|
265
|
-
return True
|
|
357
|
+
return open(output_file, 'w') if output_file else sys.stdout
|
|
266
358
|
|
|
267
359
|
def produce_from_str(self, json_str: str, output_file: str = None) -> bool:
|
|
268
360
|
"""
|
|
@@ -352,6 +444,8 @@ class SpdxLite:
|
|
|
352
444
|
return lic_id
|
|
353
445
|
self.print_debug(f'Warning: Failed to find valid SPDX license identifier for: {lic_name}')
|
|
354
446
|
return None
|
|
447
|
+
|
|
448
|
+
|
|
355
449
|
#
|
|
356
450
|
# End of SpdxLite Class
|
|
357
451
|
#
|