scanoss 1.44.0__py3-none-any.whl → 1.45.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 +49 -10
- scanoss/data/build_date.txt +1 -1
- scanoss/data/scanoss-settings-schema.json +94 -0
- scanoss/scan_settings_builder.py +311 -0
- scanoss/scanner.py +64 -13
- scanoss/scanoss_settings.py +96 -0
- scanoss/scanossapi.py +58 -0
- scanoss/scanpostprocessor.py +12 -12
- {scanoss-1.44.0.dist-info → scanoss-1.45.0.dist-info}/METADATA +1 -1
- {scanoss-1.44.0.dist-info → scanoss-1.45.0.dist-info}/RECORD +15 -14
- {scanoss-1.44.0.dist-info → scanoss-1.45.0.dist-info}/WHEEL +1 -1
- {scanoss-1.44.0.dist-info → scanoss-1.45.0.dist-info}/entry_points.txt +0 -0
- {scanoss-1.44.0.dist-info → scanoss-1.45.0.dist-info}/licenses/LICENSE +0 -0
- {scanoss-1.44.0.dist-info → scanoss-1.45.0.dist-info}/top_level.txt +0 -0
scanoss/__init__.py
CHANGED
scanoss/cli.py
CHANGED
|
@@ -195,6 +195,40 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
|
|
|
195
195
|
help='Save fingerprints to specified file during scan'
|
|
196
196
|
)
|
|
197
197
|
|
|
198
|
+
# Snippet tuning options
|
|
199
|
+
p_scan.add_argument(
|
|
200
|
+
'--min-snippet-hits',
|
|
201
|
+
type=int,
|
|
202
|
+
default=None,
|
|
203
|
+
help='Minimum snippet hits required. A value of 0 defers to server configuration (optional)',
|
|
204
|
+
)
|
|
205
|
+
p_scan.add_argument(
|
|
206
|
+
'--min-snippet-lines',
|
|
207
|
+
type=int,
|
|
208
|
+
default=None,
|
|
209
|
+
help='Minimum snippet lines required. A value of 0 defers to server configuration (optional)',
|
|
210
|
+
)
|
|
211
|
+
p_scan.add_argument(
|
|
212
|
+
'--ranking',
|
|
213
|
+
type=str,
|
|
214
|
+
choices=['unset' ,'true', 'false'],
|
|
215
|
+
default='unset',
|
|
216
|
+
help='Enable or disable ranking (optional - default: server configuration)',
|
|
217
|
+
)
|
|
218
|
+
p_scan.add_argument(
|
|
219
|
+
'--ranking-threshold',
|
|
220
|
+
type=int,
|
|
221
|
+
default=-1,
|
|
222
|
+
help='Ranking threshold value. Valid range: -1 to 10. A value of -1 defers to server configuration (optional)',
|
|
223
|
+
)
|
|
224
|
+
p_scan.add_argument(
|
|
225
|
+
'--honour-file-exts',
|
|
226
|
+
type=str,
|
|
227
|
+
choices=['unset','true', 'false'],
|
|
228
|
+
default='unset',
|
|
229
|
+
help='Honour file extensions during scanning. When not set, defers to server configuration (optional)',
|
|
230
|
+
)
|
|
231
|
+
|
|
198
232
|
# Sub-command: fingerprint
|
|
199
233
|
p_wfp = subparsers.add_parser(
|
|
200
234
|
'fingerprint',
|
|
@@ -1381,11 +1415,11 @@ def wfp(parser, args):
|
|
|
1381
1415
|
initialise_empty_file(args.output)
|
|
1382
1416
|
|
|
1383
1417
|
# Load scan settings
|
|
1384
|
-
|
|
1418
|
+
scanoss_settings = None
|
|
1385
1419
|
if not args.skip_settings_file:
|
|
1386
|
-
|
|
1420
|
+
scanoss_settings = ScanossSettings(debug=args.debug, trace=args.trace, quiet=args.quiet)
|
|
1387
1421
|
try:
|
|
1388
|
-
|
|
1422
|
+
scanoss_settings.load_json_file(args.settings, args.scan_dir)
|
|
1389
1423
|
except ScanossSettingsError as e:
|
|
1390
1424
|
print_stderr(f'Error: {e}')
|
|
1391
1425
|
sys.exit(1)
|
|
@@ -1407,7 +1441,7 @@ def wfp(parser, args):
|
|
|
1407
1441
|
skip_md5_ids=args.skip_md5,
|
|
1408
1442
|
strip_hpsm_ids=args.strip_hpsm,
|
|
1409
1443
|
strip_snippet_ids=args.strip_snippet,
|
|
1410
|
-
|
|
1444
|
+
scanoss_settings=scanoss_settings,
|
|
1411
1445
|
skip_headers=args.skip_headers,
|
|
1412
1446
|
skip_headers_limit=args.skip_headers_limit,
|
|
1413
1447
|
)
|
|
@@ -1491,20 +1525,20 @@ def scan(parser, args): # noqa: PLR0912, PLR0915
|
|
|
1491
1525
|
print_stderr('ERROR: Cannot specify both --settings and --skip-file-settings options.')
|
|
1492
1526
|
sys.exit(1)
|
|
1493
1527
|
# Figure out which settings (if any) to load before processing
|
|
1494
|
-
|
|
1528
|
+
scanoss_settings = None
|
|
1495
1529
|
if not args.skip_settings_file:
|
|
1496
|
-
|
|
1530
|
+
scanoss_settings = ScanossSettings(debug=args.debug, trace=args.trace, quiet=args.quiet)
|
|
1497
1531
|
try:
|
|
1498
1532
|
if args.identify:
|
|
1499
|
-
|
|
1533
|
+
scanoss_settings.load_json_file(args.identify, args.scan_dir).set_file_type('legacy').set_scan_type(
|
|
1500
1534
|
'identify'
|
|
1501
1535
|
)
|
|
1502
1536
|
elif args.ignore:
|
|
1503
|
-
|
|
1537
|
+
scanoss_settings.load_json_file(args.ignore, args.scan_dir).set_file_type('legacy').set_scan_type(
|
|
1504
1538
|
'blacklist'
|
|
1505
1539
|
)
|
|
1506
1540
|
else:
|
|
1507
|
-
|
|
1541
|
+
scanoss_settings.load_json_file(args.settings, args.scan_dir).set_file_type('new')
|
|
1508
1542
|
|
|
1509
1543
|
except ScanossSettingsError as e:
|
|
1510
1544
|
print_stderr(f'Error: {e}')
|
|
@@ -1600,9 +1634,14 @@ def scan(parser, args): # noqa: PLR0912, PLR0915
|
|
|
1600
1634
|
skip_md5_ids=args.skip_md5,
|
|
1601
1635
|
strip_hpsm_ids=args.strip_hpsm,
|
|
1602
1636
|
strip_snippet_ids=args.strip_snippet,
|
|
1603
|
-
|
|
1637
|
+
scanoss_settings=scanoss_settings,
|
|
1604
1638
|
req_headers=process_req_headers(args.header),
|
|
1605
1639
|
use_grpc=args.grpc,
|
|
1640
|
+
min_snippet_hits=args.min_snippet_hits,
|
|
1641
|
+
min_snippet_lines=args.min_snippet_lines,
|
|
1642
|
+
ranking=args.ranking,
|
|
1643
|
+
ranking_threshold=args.ranking_threshold,
|
|
1644
|
+
honour_file_exts=args.honour_file_exts,
|
|
1606
1645
|
skip_headers=args.skip_headers,
|
|
1607
1646
|
skip_headers_limit=args.skip_headers_limit,
|
|
1608
1647
|
wfp_output=args.wfp_output,
|
scanoss/data/build_date.txt
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
date:
|
|
1
|
+
date: 20260202142827, utime: 1770042507
|
|
@@ -139,6 +139,100 @@
|
|
|
139
139
|
}
|
|
140
140
|
}
|
|
141
141
|
}
|
|
142
|
+
},
|
|
143
|
+
"file_snippet": {
|
|
144
|
+
"type": "object",
|
|
145
|
+
"description": "File snippet scanning configuration",
|
|
146
|
+
"properties": {
|
|
147
|
+
"proxy": {
|
|
148
|
+
"type": "object",
|
|
149
|
+
"description": "Proxy configuration for file snippet requests",
|
|
150
|
+
"properties": {
|
|
151
|
+
"host": {
|
|
152
|
+
"type": "string",
|
|
153
|
+
"description": "Proxy host URL"
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
"http_config": {
|
|
158
|
+
"type": "object",
|
|
159
|
+
"description": "HTTP configuration for file snippet requests",
|
|
160
|
+
"properties": {
|
|
161
|
+
"base_uri": {
|
|
162
|
+
"type": "string",
|
|
163
|
+
"description": "Base URI for file snippet API requests"
|
|
164
|
+
},
|
|
165
|
+
"ignore_cert_errors": {
|
|
166
|
+
"type": "boolean",
|
|
167
|
+
"description": "Whether to ignore certificate errors"
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
"ranking_enabled": {
|
|
172
|
+
"type": ["boolean", "null"],
|
|
173
|
+
"description": "Enable/disable ranking",
|
|
174
|
+
"default": null
|
|
175
|
+
},
|
|
176
|
+
"ranking_threshold": {
|
|
177
|
+
"type": ["integer", "null"],
|
|
178
|
+
"description": "Ranking threshold value. A value of -1 defers to server configuration",
|
|
179
|
+
"minimum": -1,
|
|
180
|
+
"maximum": 99,
|
|
181
|
+
"default": 0
|
|
182
|
+
},
|
|
183
|
+
"min_snippet_hits": {
|
|
184
|
+
"type": "integer",
|
|
185
|
+
"description": "Minimum snippet hits required",
|
|
186
|
+
"minimum": 0,
|
|
187
|
+
"default": 0
|
|
188
|
+
},
|
|
189
|
+
"min_snippet_lines": {
|
|
190
|
+
"type": "integer",
|
|
191
|
+
"description": "Minimum snippet lines required",
|
|
192
|
+
"minimum": 0,
|
|
193
|
+
"default": 0
|
|
194
|
+
},
|
|
195
|
+
"honour_file_exts": {
|
|
196
|
+
"type": ["boolean", "null"],
|
|
197
|
+
"description": "Ignores file extensions. When not set, defers to server configuration.",
|
|
198
|
+
"default": true
|
|
199
|
+
},
|
|
200
|
+
"dependency_analysis": {
|
|
201
|
+
"type": "boolean",
|
|
202
|
+
"description": "Enable dependency analysis"
|
|
203
|
+
},
|
|
204
|
+
"skip_headers": {
|
|
205
|
+
"type": "boolean",
|
|
206
|
+
"description": "Skip license headers, comments and imports at the beginning of files",
|
|
207
|
+
"default": false
|
|
208
|
+
},
|
|
209
|
+
"skip_headers_limit": {
|
|
210
|
+
"type": "integer",
|
|
211
|
+
"description": "Maximum number of lines to skip when filtering headers",
|
|
212
|
+
"default": 0
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
"hpfm": {
|
|
217
|
+
"type": "object",
|
|
218
|
+
"description": "HPFM (High Precision Folder Matching) configuration",
|
|
219
|
+
"properties": {
|
|
220
|
+
"ranking_enabled": {
|
|
221
|
+
"type": "boolean",
|
|
222
|
+
"description": "Enable ranking for HPFM"
|
|
223
|
+
},
|
|
224
|
+
"ranking_threshold": {
|
|
225
|
+
"type": ["integer", "null"],
|
|
226
|
+
"description": "Ranking threshold value. A value of -1 defers to server configuration",
|
|
227
|
+
"minimum": -1,
|
|
228
|
+
"maximum": 99,
|
|
229
|
+
"default": 0
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
},
|
|
233
|
+
"container": {
|
|
234
|
+
"type": "object",
|
|
235
|
+
"description": "Container scanning configuration"
|
|
142
236
|
}
|
|
143
237
|
}
|
|
144
238
|
},
|
|
@@ -0,0 +1,311 @@
|
|
|
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
|
+
from typing import TYPE_CHECKING, Optional
|
|
26
|
+
|
|
27
|
+
from .scanossbase import ScanossBase
|
|
28
|
+
|
|
29
|
+
if TYPE_CHECKING:
|
|
30
|
+
from .scanoss_settings import ScanossSettings
|
|
31
|
+
|
|
32
|
+
MAX_RANKING_THRESHOLD = 10
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class ScanSettingsBuilder(ScanossBase):
|
|
36
|
+
"""Builder class for merging CLI arguments with scanoss.json settings file values.
|
|
37
|
+
|
|
38
|
+
This class implements an API for merging scan configuration
|
|
39
|
+
from multiple sources with the following priority order:
|
|
40
|
+
1. settings.file_snippet section in scanoss.json (highest priority)
|
|
41
|
+
2. settings section in scanoss.json (middle priority)
|
|
42
|
+
3. CLI arguments (lowest priority - used as fallback)
|
|
43
|
+
|
|
44
|
+
Attributes:
|
|
45
|
+
proxy: Merged proxy host URL
|
|
46
|
+
url: Merged API base URL
|
|
47
|
+
ignore_cert_errors: Whether to ignore SSL certificate errors
|
|
48
|
+
min_snippet_hits: Minimum snippet hits required for matching
|
|
49
|
+
min_snippet_lines: Minimum snippet lines required for matching
|
|
50
|
+
honour_file_exts: Whether to honour file extensions during scanning
|
|
51
|
+
ranking: Whether ranking is enabled
|
|
52
|
+
ranking_threshold: Ranking threshold value
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
def __init__(
|
|
56
|
+
self,
|
|
57
|
+
scanoss_settings: 'ScanossSettings | None',
|
|
58
|
+
debug: bool = False,
|
|
59
|
+
trace: bool = False,
|
|
60
|
+
quiet: bool = False,
|
|
61
|
+
):
|
|
62
|
+
"""Initialize the builder with optional scanoss settings.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
scanoss_settings: ScanossSettings instance loaded from scanoss.json,
|
|
66
|
+
or None if no settings file is available.
|
|
67
|
+
debug: Enable debug output
|
|
68
|
+
trace: Enable trace output
|
|
69
|
+
quiet: Enable quiet mode
|
|
70
|
+
"""
|
|
71
|
+
super().__init__(debug=debug, trace=trace, quiet=quiet)
|
|
72
|
+
self.scanoss_settings = scanoss_settings
|
|
73
|
+
# Merged values
|
|
74
|
+
self.proxy: Optional[str] = None
|
|
75
|
+
self.url: Optional[str] = None
|
|
76
|
+
self.ignore_cert_errors: bool = False
|
|
77
|
+
self.min_snippet_hits: Optional[int] = None
|
|
78
|
+
self.min_snippet_lines: Optional[int] = None
|
|
79
|
+
self.honour_file_exts: Optional[any] = None
|
|
80
|
+
self.ranking: Optional[any] = None
|
|
81
|
+
self.ranking_threshold: Optional[int] = None
|
|
82
|
+
|
|
83
|
+
def with_proxy(self, cli_value: str = None) -> 'ScanSettingsBuilder':
|
|
84
|
+
"""Set proxy host with priority: file_snippet.proxy.host > settings.proxy.host > CLI.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
cli_value: Proxy host from CLI argument (e.g., 'http://proxy:8080')
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
Self for method chaining
|
|
91
|
+
"""
|
|
92
|
+
self.proxy = self._merge_with_priority(
|
|
93
|
+
cli_value,
|
|
94
|
+
self._get_proxy_host(self._get_file_snippet_proxy()),
|
|
95
|
+
self._get_proxy_host(self._get_root_proxy())
|
|
96
|
+
)
|
|
97
|
+
return self
|
|
98
|
+
|
|
99
|
+
def with_url(self, cli_value: str = None) -> 'ScanSettingsBuilder':
|
|
100
|
+
"""Set API base URL with priority: file_snippet.http_config.base_uri > settings.http_config.base_uri > CLI.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
cli_value: API base URL from CLI argument (e.g., 'https://api.scanoss.com')
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
Self for method chaining
|
|
107
|
+
"""
|
|
108
|
+
self.url = self._merge_with_priority(
|
|
109
|
+
cli_value,
|
|
110
|
+
self._get_file_snippet_http_config_value('base_uri'),
|
|
111
|
+
self._get_http_config_value('base_uri')
|
|
112
|
+
)
|
|
113
|
+
return self
|
|
114
|
+
|
|
115
|
+
def with_ignore_cert_errors(self, cli_value: bool = False) -> 'ScanSettingsBuilder':
|
|
116
|
+
"""Set ignore_cert_errors with priority: CLI True > file_snippet > settings > False.
|
|
117
|
+
|
|
118
|
+
Note: CLI value only takes effect if True (flag present). False means
|
|
119
|
+
the flag was not provided, so settings file values are checked.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
cli_value: Whether to ignore SSL certificate errors from CLI flag
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
Self for method chaining
|
|
126
|
+
"""
|
|
127
|
+
result = self._merge_with_priority(
|
|
128
|
+
cli_value if cli_value else None,
|
|
129
|
+
self._get_file_snippet_http_config_value('ignore_cert_errors'),
|
|
130
|
+
self._get_http_config_value('ignore_cert_errors')
|
|
131
|
+
)
|
|
132
|
+
self.ignore_cert_errors = result if result is not None else False
|
|
133
|
+
return self
|
|
134
|
+
|
|
135
|
+
def with_min_snippet_hits(self, cli_value: int = None) -> 'ScanSettingsBuilder':
|
|
136
|
+
"""Set minimum snippet hits with priority: settings.file_snippet.min_snippet_hits > CLI.
|
|
137
|
+
|
|
138
|
+
Minimum allowed value is 0. Values below 0 will be clamped and logged.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
cli_value: Minimum snippet hits from CLI argument
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
Self for method chaining
|
|
145
|
+
"""
|
|
146
|
+
self.min_snippet_hits = self._merge_cli_with_settings(
|
|
147
|
+
cli_value,
|
|
148
|
+
self._get_file_snippet_setting('min_snippet_hits')
|
|
149
|
+
)
|
|
150
|
+
if self.min_snippet_hits is not None and self.min_snippet_hits < 0:
|
|
151
|
+
self.print_msg(
|
|
152
|
+
f'WARNING: min-snippet-hits value {self.min_snippet_hits} is below minimum allowed (0). '
|
|
153
|
+
f'Setting to 0.'
|
|
154
|
+
)
|
|
155
|
+
self.min_snippet_hits = 0
|
|
156
|
+
return self
|
|
157
|
+
|
|
158
|
+
def with_min_snippet_lines(self, cli_value: int = None) -> 'ScanSettingsBuilder':
|
|
159
|
+
"""Set minimum snippet lines with priority: settings.file_snippet.min_snippet_lines > CLI.
|
|
160
|
+
|
|
161
|
+
Minimum allowed value is 0. Values below 0 will be clamped and logged.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
cli_value: Minimum snippet lines from CLI argument
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
Self for method chaining
|
|
168
|
+
"""
|
|
169
|
+
self.min_snippet_lines = self._merge_cli_with_settings(
|
|
170
|
+
cli_value,
|
|
171
|
+
self._get_file_snippet_setting('min_snippet_lines')
|
|
172
|
+
)
|
|
173
|
+
if self.min_snippet_lines is not None and self.min_snippet_lines < 0:
|
|
174
|
+
self.print_msg(
|
|
175
|
+
f'WARNING: min-snippet-lines value {self.min_snippet_lines} is below minimum allowed (0). '
|
|
176
|
+
f'Setting to 0.'
|
|
177
|
+
)
|
|
178
|
+
self.min_snippet_lines = 0
|
|
179
|
+
return self
|
|
180
|
+
|
|
181
|
+
def with_honour_file_exts(self, cli_value: str = None) -> 'ScanSettingsBuilder':
|
|
182
|
+
"""Set honour_file_exts with priority: settings.file_snippet.honour_file_exts > CLI.
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
cli_value: String 'true', 'false', or 'unset' from CLI argument
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
Self for method chaining
|
|
189
|
+
"""
|
|
190
|
+
self.honour_file_exts = self._merge_cli_with_settings(
|
|
191
|
+
cli_value,
|
|
192
|
+
self._get_file_snippet_setting('honour_file_exts')
|
|
193
|
+
)
|
|
194
|
+
## Convert to boolean
|
|
195
|
+
if self.honour_file_exts is not None and self.honour_file_exts!= 'unset':
|
|
196
|
+
self.honour_file_exts = self._str_to_bool(self.honour_file_exts)
|
|
197
|
+
return self
|
|
198
|
+
|
|
199
|
+
def with_ranking(self, cli_value: str = None) -> 'ScanSettingsBuilder':
|
|
200
|
+
"""Set ranking enabled with priority: settings.file_snippet.ranking_enabled > CLI.
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
cli_value: String 'true', 'false', or 'unset' from CLI argument
|
|
204
|
+
|
|
205
|
+
Returns:
|
|
206
|
+
Self for method chaining
|
|
207
|
+
"""
|
|
208
|
+
self.ranking = self._merge_cli_with_settings(
|
|
209
|
+
cli_value,
|
|
210
|
+
self._get_file_snippet_setting('ranking_enabled')
|
|
211
|
+
)
|
|
212
|
+
if self.ranking is not None and self.ranking != 'unset':
|
|
213
|
+
self.ranking = self._str_to_bool(self.ranking)
|
|
214
|
+
return self
|
|
215
|
+
|
|
216
|
+
def with_ranking_threshold(self, cli_value: int = None) -> 'ScanSettingsBuilder':
|
|
217
|
+
"""Set ranking threshold with priority: settings.file_snippet.ranking_threshold > CLI.
|
|
218
|
+
|
|
219
|
+
Valid range is -1 to 10. Values outside this range will be clamped and logged.
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
cli_value: Ranking threshold from CLI argument
|
|
223
|
+
|
|
224
|
+
Returns:
|
|
225
|
+
Self for method chaining
|
|
226
|
+
"""
|
|
227
|
+
self.ranking_threshold = self._merge_cli_with_settings(
|
|
228
|
+
cli_value,
|
|
229
|
+
self._get_file_snippet_setting('ranking_threshold')
|
|
230
|
+
)
|
|
231
|
+
if self.ranking_threshold is not None:
|
|
232
|
+
if self.ranking_threshold > MAX_RANKING_THRESHOLD:
|
|
233
|
+
self.print_msg(
|
|
234
|
+
f'WARNING: ranking-threshold value {self.ranking_threshold} exceeds maximum allowed '
|
|
235
|
+
f'({MAX_RANKING_THRESHOLD}). Setting to {MAX_RANKING_THRESHOLD}.'
|
|
236
|
+
)
|
|
237
|
+
self.ranking_threshold = MAX_RANKING_THRESHOLD
|
|
238
|
+
elif self.ranking_threshold < -1:
|
|
239
|
+
self.print_msg(
|
|
240
|
+
f'WARNING: ranking-threshold value {self.ranking_threshold} is below minimum allowed (-1). '
|
|
241
|
+
f'Setting to -1.'
|
|
242
|
+
)
|
|
243
|
+
self.ranking_threshold = -1
|
|
244
|
+
return self
|
|
245
|
+
|
|
246
|
+
# Private helper methods
|
|
247
|
+
@staticmethod
|
|
248
|
+
def _merge_with_priority(cli_value, file_snippet_value, root_value):
|
|
249
|
+
"""Merge with priority: file_snippet > root settings > CLI"""
|
|
250
|
+
if file_snippet_value is not None:
|
|
251
|
+
return file_snippet_value
|
|
252
|
+
if root_value is not None:
|
|
253
|
+
return root_value
|
|
254
|
+
return cli_value
|
|
255
|
+
|
|
256
|
+
@staticmethod
|
|
257
|
+
def _merge_cli_with_settings(cli_value, settings_value):
|
|
258
|
+
"""Merge CLI value with settings, with settings taking priority over CLI.
|
|
259
|
+
|
|
260
|
+
Returns settings_value if not None, otherwise falls back to cli_value.
|
|
261
|
+
"""
|
|
262
|
+
if settings_value is not None:
|
|
263
|
+
return settings_value
|
|
264
|
+
return cli_value
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
@staticmethod
|
|
268
|
+
def _str_to_bool(value: str) -> Optional[bool]:
|
|
269
|
+
"""Convert string 'true'/'false' to boolean."""
|
|
270
|
+
if value is None:
|
|
271
|
+
return None
|
|
272
|
+
if isinstance(value, bool):
|
|
273
|
+
return value
|
|
274
|
+
return value.lower() == 'true'
|
|
275
|
+
|
|
276
|
+
# Methods to extract values from scanoss_settings
|
|
277
|
+
def _get_file_snippet_setting(self, key: str):
|
|
278
|
+
"""Get a setting from the file_snippet section."""
|
|
279
|
+
if not self.scanoss_settings:
|
|
280
|
+
return None
|
|
281
|
+
return self.scanoss_settings.get_file_snippet_settings().get(key)
|
|
282
|
+
|
|
283
|
+
def _get_file_snippet_proxy(self):
|
|
284
|
+
"""Get proxy config from file_snippet section."""
|
|
285
|
+
return self.scanoss_settings.get_file_snippet_proxy() if self.scanoss_settings else None
|
|
286
|
+
|
|
287
|
+
def _get_root_proxy(self):
|
|
288
|
+
"""Get proxy config from root settings section."""
|
|
289
|
+
return self.scanoss_settings.get_proxy() if self.scanoss_settings else None
|
|
290
|
+
|
|
291
|
+
@staticmethod
|
|
292
|
+
def _get_proxy_host(proxy_config) -> Optional[str]:
|
|
293
|
+
"""Extract host from proxy configuration dict."""
|
|
294
|
+
if proxy_config is None:
|
|
295
|
+
return None
|
|
296
|
+
host = proxy_config.get('host')
|
|
297
|
+
return host if host else None
|
|
298
|
+
|
|
299
|
+
def _get_http_config_value(self, key: str):
|
|
300
|
+
"""Extract a value from http_config dict."""
|
|
301
|
+
if not self.scanoss_settings:
|
|
302
|
+
return None
|
|
303
|
+
config = self.scanoss_settings.get_http_config()
|
|
304
|
+
return config.get(key) if config else None
|
|
305
|
+
|
|
306
|
+
def _get_file_snippet_http_config_value(self, key: str):
|
|
307
|
+
"""Extract a value from file_snippet http_config dict."""
|
|
308
|
+
if not self.scanoss_settings:
|
|
309
|
+
return None
|
|
310
|
+
config = self.scanoss_settings.get_file_snippet_http_config()
|
|
311
|
+
return config.get(key) if config else None
|
scanoss/scanner.py
CHANGED
|
@@ -39,6 +39,7 @@ from scanoss.file_filters import FileFilters
|
|
|
39
39
|
from . import __version__
|
|
40
40
|
from .csvoutput import CsvOutput
|
|
41
41
|
from .cyclonedx import CycloneDx
|
|
42
|
+
from .scan_settings_builder import ScanSettingsBuilder
|
|
42
43
|
from .scancodedeps import ScancodeDeps
|
|
43
44
|
from .scanoss_settings import ScanossSettings
|
|
44
45
|
from .scanossapi import ScanossApi
|
|
@@ -103,9 +104,14 @@ class Scanner(ScanossBase):
|
|
|
103
104
|
strip_hpsm_ids=None,
|
|
104
105
|
strip_snippet_ids=None,
|
|
105
106
|
skip_md5_ids=None,
|
|
106
|
-
|
|
107
|
+
scanoss_settings: 'ScanossSettings | None' = None,
|
|
107
108
|
req_headers: dict = None,
|
|
108
109
|
use_grpc: bool = False,
|
|
110
|
+
min_snippet_hits: int = None,
|
|
111
|
+
min_snippet_lines: int = None,
|
|
112
|
+
ranking: str = None,
|
|
113
|
+
ranking_threshold: int = None,
|
|
114
|
+
honour_file_exts: str = None,
|
|
109
115
|
skip_headers: bool = False,
|
|
110
116
|
skip_headers_limit: int = 0,
|
|
111
117
|
wfp_output: str = None,
|
|
@@ -132,8 +138,20 @@ class Scanner(ScanossBase):
|
|
|
132
138
|
self.skip_size = skip_size
|
|
133
139
|
self.skip_extensions = skip_extensions
|
|
134
140
|
self.req_headers = req_headers
|
|
141
|
+
self.scanoss_settings = scanoss_settings
|
|
135
142
|
ver_details = Scanner.version_details()
|
|
136
143
|
|
|
144
|
+
# Get settings values for skip_headers options
|
|
145
|
+
file_snippet_settings = scanoss_settings.get_file_snippet_settings() if scanoss_settings else {}
|
|
146
|
+
settings_skip_headers = file_snippet_settings.get('skip_headers')
|
|
147
|
+
settings_skip_headers_limit = file_snippet_settings.get('skip_headers_limit')
|
|
148
|
+
|
|
149
|
+
# Merge CLI values with settings (scanoss.json takes priority over CLI)
|
|
150
|
+
skip_headers = Scanner._merge_cli_with_settings(skip_headers, settings_skip_headers)
|
|
151
|
+
skip_headers_limit = Scanner._merge_cli_with_settings(
|
|
152
|
+
skip_headers_limit, settings_skip_headers_limit)
|
|
153
|
+
self.print_debug(f'Skip headers {skip_headers} with limit: {skip_headers_limit}')
|
|
154
|
+
|
|
137
155
|
self.winnowing = Winnowing(
|
|
138
156
|
debug=debug,
|
|
139
157
|
trace=trace,
|
|
@@ -148,21 +166,40 @@ class Scanner(ScanossBase):
|
|
|
148
166
|
skip_headers=skip_headers,
|
|
149
167
|
skip_headers_limit=skip_headers_limit,
|
|
150
168
|
)
|
|
169
|
+
|
|
170
|
+
# Build merged settings using builder pattern
|
|
171
|
+
scan_settings = (ScanSettingsBuilder(scanoss_settings, debug=debug, trace=trace, quiet=quiet)
|
|
172
|
+
.with_proxy(proxy)
|
|
173
|
+
.with_url(url)
|
|
174
|
+
.with_ignore_cert_errors(ignore_cert_errors)
|
|
175
|
+
.with_min_snippet_hits(min_snippet_hits)
|
|
176
|
+
.with_min_snippet_lines(min_snippet_lines)
|
|
177
|
+
.with_honour_file_exts(honour_file_exts)
|
|
178
|
+
.with_ranking(ranking)
|
|
179
|
+
.with_ranking_threshold(ranking_threshold))
|
|
180
|
+
|
|
181
|
+
self.print_debug(f'Scan settings: {scan_settings}')
|
|
182
|
+
|
|
151
183
|
self.scanoss_api = ScanossApi(
|
|
152
184
|
debug=debug,
|
|
153
185
|
trace=trace,
|
|
154
186
|
quiet=quiet,
|
|
155
187
|
api_key=api_key,
|
|
156
|
-
url=url,
|
|
188
|
+
url=scan_settings.url,
|
|
157
189
|
flags=flags,
|
|
158
190
|
timeout=timeout,
|
|
159
191
|
ver_details=ver_details,
|
|
160
|
-
ignore_cert_errors=ignore_cert_errors,
|
|
161
|
-
proxy=proxy,
|
|
192
|
+
ignore_cert_errors=scan_settings.ignore_cert_errors,
|
|
193
|
+
proxy=scan_settings.proxy,
|
|
162
194
|
ca_cert=ca_cert,
|
|
163
195
|
pac=pac,
|
|
164
196
|
retry=retry,
|
|
165
|
-
req_headers=
|
|
197
|
+
req_headers=self.req_headers,
|
|
198
|
+
min_snippet_hits=scan_settings.min_snippet_hits,
|
|
199
|
+
min_snippet_lines=scan_settings.min_snippet_lines,
|
|
200
|
+
honour_file_exts=scan_settings.honour_file_exts,
|
|
201
|
+
ranking=scan_settings.ranking,
|
|
202
|
+
ranking_threshold=scan_settings.ranking_threshold,
|
|
166
203
|
)
|
|
167
204
|
sc_deps = ScancodeDeps(debug=debug, quiet=quiet, trace=trace, timeout=sc_timeout, sc_command=sc_command)
|
|
168
205
|
grpc_api = ScanossGrpc(
|
|
@@ -193,19 +230,32 @@ class Scanner(ScanossBase):
|
|
|
193
230
|
if self._skip_snippets:
|
|
194
231
|
self.max_post_size = 8 * 1024 # 8k Max post size if we're skipping snippets
|
|
195
232
|
|
|
196
|
-
self.scan_settings = scan_settings
|
|
197
233
|
self.post_processor = (
|
|
198
|
-
ScanPostProcessor(
|
|
234
|
+
ScanPostProcessor(scanoss_settings, debug=debug, trace=trace, quiet=quiet) if scan_settings else None
|
|
199
235
|
)
|
|
200
236
|
self._maybe_set_api_sbom()
|
|
201
237
|
|
|
202
238
|
def _maybe_set_api_sbom(self):
|
|
203
|
-
if not self.
|
|
239
|
+
if not self.scanoss_settings:
|
|
204
240
|
return
|
|
205
|
-
sbom = self.
|
|
241
|
+
sbom = self.scanoss_settings.get_sbom()
|
|
206
242
|
if sbom:
|
|
207
243
|
self.scanoss_api.set_sbom(sbom)
|
|
208
244
|
|
|
245
|
+
@staticmethod
|
|
246
|
+
def _merge_cli_with_settings(cli_value, settings_value):
|
|
247
|
+
"""Merge CLI value with settings value (two-level priority: settings > cli).
|
|
248
|
+
|
|
249
|
+
Args:
|
|
250
|
+
cli_value: Value from CLI argument
|
|
251
|
+
settings_value: Value from scanoss.json file_snippet settings
|
|
252
|
+
Returns:
|
|
253
|
+
Merged value with CLI taking priority over settings
|
|
254
|
+
"""
|
|
255
|
+
if settings_value is not None:
|
|
256
|
+
return settings_value
|
|
257
|
+
return cli_value
|
|
258
|
+
|
|
209
259
|
@staticmethod
|
|
210
260
|
def __count_files_in_wfp_file(wfp_file: str):
|
|
211
261
|
"""
|
|
@@ -288,7 +338,8 @@ class Scanner(ScanossBase):
|
|
|
288
338
|
"""
|
|
289
339
|
if self.scan_options & ScanType.SCAN_DEPENDENCIES.value:
|
|
290
340
|
return True
|
|
291
|
-
|
|
341
|
+
file_snippet_settings = self.scanoss_settings.get_file_snippet_settings() if self.scanoss_settings else {}
|
|
342
|
+
return file_snippet_settings.get('dependency_analysis', False)
|
|
292
343
|
|
|
293
344
|
def scan_folder_with_options( # noqa: PLR0913
|
|
294
345
|
self,
|
|
@@ -356,7 +407,7 @@ class Scanner(ScanossBase):
|
|
|
356
407
|
debug=self.debug,
|
|
357
408
|
trace=self.trace,
|
|
358
409
|
quiet=self.quiet,
|
|
359
|
-
scanoss_settings=self.
|
|
410
|
+
scanoss_settings=self.scanoss_settings,
|
|
360
411
|
all_extensions=self.all_extensions,
|
|
361
412
|
all_folders=self.all_folders,
|
|
362
413
|
hidden_files_folders=self.hidden_files_folders,
|
|
@@ -624,7 +675,7 @@ class Scanner(ScanossBase):
|
|
|
624
675
|
debug=self.debug,
|
|
625
676
|
trace=self.trace,
|
|
626
677
|
quiet=self.quiet,
|
|
627
|
-
scanoss_settings=self.
|
|
678
|
+
scanoss_settings=self.scanoss_settings,
|
|
628
679
|
all_extensions=self.all_extensions,
|
|
629
680
|
all_folders=self.all_folders,
|
|
630
681
|
hidden_files_folders=self.hidden_files_folders,
|
|
@@ -939,7 +990,7 @@ class Scanner(ScanossBase):
|
|
|
939
990
|
debug=self.debug,
|
|
940
991
|
trace=self.trace,
|
|
941
992
|
quiet=self.quiet,
|
|
942
|
-
scanoss_settings=self.
|
|
993
|
+
scanoss_settings=self.scanoss_settings,
|
|
943
994
|
all_extensions=self.all_extensions,
|
|
944
995
|
all_folders=self.all_folders,
|
|
945
996
|
hidden_files_folders=self.hidden_files_folders,
|
scanoss/scanoss_settings.py
CHANGED
|
@@ -335,3 +335,99 @@ class ScanossSettings(ScanossBase):
|
|
|
335
335
|
List: Min and max sizes to skip
|
|
336
336
|
"""
|
|
337
337
|
return self.data.get('settings', {}).get('skip', {}).get('sizes', {}).get(operation_type, [])
|
|
338
|
+
|
|
339
|
+
def get_file_snippet_settings(self) -> dict:
|
|
340
|
+
"""
|
|
341
|
+
Get the file_snippet settings section
|
|
342
|
+
Returns:
|
|
343
|
+
dict: File snippet settings
|
|
344
|
+
"""
|
|
345
|
+
return self.data.get('settings', {}).get('file_snippet', {})
|
|
346
|
+
|
|
347
|
+
def get_min_snippet_hits(self) -> Optional[int]:
|
|
348
|
+
"""
|
|
349
|
+
Get the minimum snippet hits required
|
|
350
|
+
Returns:
|
|
351
|
+
int or None: Minimum snippet hits, or None if not set
|
|
352
|
+
"""
|
|
353
|
+
return self.get_file_snippet_settings().get('min_snippet_hits')
|
|
354
|
+
|
|
355
|
+
def get_min_snippet_lines(self) -> Optional[int]:
|
|
356
|
+
"""
|
|
357
|
+
Get the minimum snippet lines required
|
|
358
|
+
Returns:
|
|
359
|
+
int or None: Minimum snippet lines, or None if not set
|
|
360
|
+
"""
|
|
361
|
+
return self.get_file_snippet_settings().get('min_snippet_lines')
|
|
362
|
+
|
|
363
|
+
def get_ranking_enabled(self) -> Optional[bool]:
|
|
364
|
+
"""
|
|
365
|
+
Get whether ranking is enabled
|
|
366
|
+
Returns:
|
|
367
|
+
bool or None: True if enabled, False if disabled, None if not set
|
|
368
|
+
"""
|
|
369
|
+
return self.get_file_snippet_settings().get('ranking_enabled')
|
|
370
|
+
|
|
371
|
+
def get_ranking_threshold(self) -> Optional[int]:
|
|
372
|
+
"""
|
|
373
|
+
Get the ranking threshold value
|
|
374
|
+
Returns:
|
|
375
|
+
int or None: Ranking threshold, or None if not set
|
|
376
|
+
"""
|
|
377
|
+
return self.get_file_snippet_settings().get('ranking_threshold')
|
|
378
|
+
|
|
379
|
+
def get_honour_file_exts(self) -> Optional[bool]:
|
|
380
|
+
"""
|
|
381
|
+
Get whether to honour file extensions
|
|
382
|
+
Returns:
|
|
383
|
+
bool or None: True to honour, False to ignore, None if not set
|
|
384
|
+
"""
|
|
385
|
+
return self.get_file_snippet_settings().get('honour_file_exts')
|
|
386
|
+
|
|
387
|
+
def get_skip_headers_limit(self) -> int:
|
|
388
|
+
"""
|
|
389
|
+
Get the skip headers limit value
|
|
390
|
+
Returns:
|
|
391
|
+
int: Skip headers limit, or 0 if not set
|
|
392
|
+
"""
|
|
393
|
+
return self.get_file_snippet_settings().get('skip_headers_limit', 0)
|
|
394
|
+
|
|
395
|
+
def get_skip_headers(self) -> bool:
|
|
396
|
+
"""
|
|
397
|
+
Get whether to skip headers
|
|
398
|
+
Returns:
|
|
399
|
+
bool: True to skip headers, False otherwise (default)
|
|
400
|
+
"""
|
|
401
|
+
return self.get_file_snippet_settings().get('skip_headers', False)
|
|
402
|
+
|
|
403
|
+
def get_proxy(self) -> Optional[dict]:
|
|
404
|
+
"""
|
|
405
|
+
Get the root-level proxy configuration
|
|
406
|
+
Returns:
|
|
407
|
+
dict or None: Proxy configuration with 'host' key, or None if not set
|
|
408
|
+
"""
|
|
409
|
+
return self.data.get('settings', {}).get('proxy')
|
|
410
|
+
|
|
411
|
+
def get_http_config(self) -> Optional[dict]:
|
|
412
|
+
"""
|
|
413
|
+
Get the root-level http_config configuration
|
|
414
|
+
Returns:
|
|
415
|
+
dict or None: HTTP config with 'base_uri' and 'ignore_cert_errors' keys, or None if not set
|
|
416
|
+
"""
|
|
417
|
+
return self.data.get('settings', {}).get('http_config')
|
|
418
|
+
|
|
419
|
+
def get_file_snippet_proxy(self) -> Optional[dict]:
|
|
420
|
+
"""
|
|
421
|
+
Get the file_snippet-level proxy configuration (takes priority over root)
|
|
422
|
+
Returns:
|
|
423
|
+
dict or None: Proxy configuration with 'host' key, or None if not set
|
|
424
|
+
"""
|
|
425
|
+
return self.get_file_snippet_settings().get('proxy')
|
|
426
|
+
|
|
427
|
+
def get_file_snippet_http_config(self) -> Optional[dict]:
|
|
428
|
+
"""
|
|
429
|
+
Get the file_snippet-level http_config configuration (takes priority over root)
|
|
430
|
+
Returns:
|
|
431
|
+
dict or None: HTTP config with 'base_uri' and 'ignore_cert_errors' keys, or None if not set
|
|
432
|
+
"""
|
|
433
|
+
return self.get_file_snippet_settings().get('http_config')
|
scanoss/scanossapi.py
CHANGED
|
@@ -22,13 +22,16 @@ SPDX-License-Identifier: MIT
|
|
|
22
22
|
THE SOFTWARE.
|
|
23
23
|
"""
|
|
24
24
|
|
|
25
|
+
import base64
|
|
25
26
|
import http.client as http_client
|
|
27
|
+
import json
|
|
26
28
|
import logging
|
|
27
29
|
import os
|
|
28
30
|
import sys
|
|
29
31
|
import time
|
|
30
32
|
import uuid
|
|
31
33
|
from json.decoder import JSONDecodeError
|
|
34
|
+
from typing import Optional, Union
|
|
32
35
|
from urllib.parse import urlparse, urlunparse
|
|
33
36
|
|
|
34
37
|
import requests
|
|
@@ -98,6 +101,11 @@ class ScanossApi(ScanossBase):
|
|
|
98
101
|
pac: PACFile = None,
|
|
99
102
|
retry: int = 5,
|
|
100
103
|
req_headers: dict = None,
|
|
104
|
+
min_snippet_hits: int = None,
|
|
105
|
+
min_snippet_lines: int = None,
|
|
106
|
+
honour_file_exts: Union[bool, str, None] = 'unset',
|
|
107
|
+
ranking: Union[bool, str, None] = 'unset',
|
|
108
|
+
ranking_threshold: int = None,
|
|
101
109
|
):
|
|
102
110
|
"""
|
|
103
111
|
Initialise the SCANOSS API
|
|
@@ -108,6 +116,11 @@ class ScanossApi(ScanossBase):
|
|
|
108
116
|
:param debug: Enable debug (default False)
|
|
109
117
|
:param trace: Enable trace (default False)
|
|
110
118
|
:param quiet: Enable quiet mode (default False)
|
|
119
|
+
:param min_snippet_hits: Minimum snippet hits required (default None)
|
|
120
|
+
:param min_snippet_lines: Minimum snippet lines required (default None)
|
|
121
|
+
:param honour_file_exts: Whether to honour file extensions (default 'unset')
|
|
122
|
+
:param ranking: Enable/disable ranking (default 'unset')
|
|
123
|
+
:param ranking_threshold: Ranking threshold value (default None)
|
|
111
124
|
|
|
112
125
|
To set a custom certificate use:
|
|
113
126
|
REQUESTS_CA_BUNDLE=/path/to/cert.pem
|
|
@@ -117,6 +130,12 @@ class ScanossApi(ScanossBase):
|
|
|
117
130
|
"""
|
|
118
131
|
super().__init__(debug, trace, quiet)
|
|
119
132
|
self.sbom = None
|
|
133
|
+
# Scan tuning parameters
|
|
134
|
+
self.min_snippet_hits = min_snippet_hits
|
|
135
|
+
self.min_snippet_lines = min_snippet_lines
|
|
136
|
+
self.honour_file_exts = honour_file_exts
|
|
137
|
+
self.ranking = ranking
|
|
138
|
+
self.ranking_threshold = ranking_threshold
|
|
120
139
|
self.scan_format = scan_format if scan_format else 'plain'
|
|
121
140
|
self.flags = flags
|
|
122
141
|
self.timeout = timeout if timeout > MIN_TIMEOUT else DEFAULT_TIMEOUT
|
|
@@ -183,6 +202,10 @@ class ScanossApi(ScanossBase):
|
|
|
183
202
|
scan_files = {'file': ('%s.wfp' % request_id, wfp)}
|
|
184
203
|
headers = self.headers
|
|
185
204
|
headers['x-request-id'] = request_id # send a unique request id for each post
|
|
205
|
+
# Add scan settings header if any settings are configured
|
|
206
|
+
scan_settings_header = self._build_scan_settings_header()
|
|
207
|
+
if scan_settings_header:
|
|
208
|
+
headers['scanoss-settings'] = scan_settings_header
|
|
186
209
|
r = None
|
|
187
210
|
retry = 0 # Add some retry logic to cater for timeouts, etc.
|
|
188
211
|
while retry <= self.retry_limit:
|
|
@@ -296,6 +319,41 @@ class ScanossApi(ScanossBase):
|
|
|
296
319
|
self.sbom = sbom
|
|
297
320
|
return self
|
|
298
321
|
|
|
322
|
+
def _build_scan_settings_header(self) -> Optional[str]:
|
|
323
|
+
"""
|
|
324
|
+
Build base64-encoded JSON for x-scanoss-scan-settings header.
|
|
325
|
+
Only includes parameters that have meaningful (non-"unset") values.
|
|
326
|
+
Returns:
|
|
327
|
+
Base64-encoded JSON string, or None if no settings to send
|
|
328
|
+
"""
|
|
329
|
+
settings = {}
|
|
330
|
+
|
|
331
|
+
# min_snippet_hits: 0 = unset, don't send
|
|
332
|
+
if self.min_snippet_hits is not None and self.min_snippet_hits != 0:
|
|
333
|
+
settings['min_snippet_hits'] = self.min_snippet_hits
|
|
334
|
+
|
|
335
|
+
# min_snippet_lines: 0 = unset, don't send
|
|
336
|
+
if self.min_snippet_lines is not None and self.min_snippet_lines != 0:
|
|
337
|
+
settings['min_snippet_lines'] = self.min_snippet_lines
|
|
338
|
+
|
|
339
|
+
# honour_file_exts: None = unset, don't send
|
|
340
|
+
if self.honour_file_exts is not None and self.honour_file_exts != 'unset':
|
|
341
|
+
settings['honour_file_exts'] = self.honour_file_exts
|
|
342
|
+
|
|
343
|
+
# ranking: None = unset, don't send
|
|
344
|
+
if self.ranking is not None and self.ranking != 'unset':
|
|
345
|
+
settings['ranking_enabled'] = self.ranking
|
|
346
|
+
|
|
347
|
+
# ranking_threshold: -1 = unset, don't send
|
|
348
|
+
if self.ranking_threshold is not None and self.ranking_threshold != -1:
|
|
349
|
+
settings['ranking_threshold'] = self.ranking_threshold
|
|
350
|
+
|
|
351
|
+
if settings:
|
|
352
|
+
json_str = json.dumps(settings)
|
|
353
|
+
self.print_debug(f'Scan settings: {json_str}')
|
|
354
|
+
return base64.b64encode(json_str.encode()).decode()
|
|
355
|
+
return None
|
|
356
|
+
|
|
299
357
|
def load_generic_headers(self, url):
|
|
300
358
|
"""
|
|
301
359
|
Adds custom headers from req_headers to the headers collection.
|
scanoss/scanpostprocessor.py
CHANGED
|
@@ -80,7 +80,7 @@ class ScanPostProcessor(ScanossBase):
|
|
|
80
80
|
|
|
81
81
|
def __init__(
|
|
82
82
|
self,
|
|
83
|
-
|
|
83
|
+
scanoss_settings: ScanossSettings,
|
|
84
84
|
debug: bool = False,
|
|
85
85
|
trace: bool = False,
|
|
86
86
|
quiet: bool = False,
|
|
@@ -88,14 +88,14 @@ class ScanPostProcessor(ScanossBase):
|
|
|
88
88
|
):
|
|
89
89
|
"""
|
|
90
90
|
Args:
|
|
91
|
-
|
|
91
|
+
scanoss_settings (ScanossSettings): Scanoss settings object
|
|
92
92
|
debug (bool, optional): Debug mode. Defaults to False.
|
|
93
93
|
trace (bool, optional): Traces. Defaults to False.
|
|
94
94
|
quiet (bool, optional): Quiet mode. Defaults to False.
|
|
95
95
|
results (dict | str, optional): Results to be processed. Defaults to None.
|
|
96
96
|
"""
|
|
97
97
|
super().__init__(debug, trace, quiet)
|
|
98
|
-
self.
|
|
98
|
+
self.scanoss_settings = scanoss_settings
|
|
99
99
|
self.results: dict = results
|
|
100
100
|
self.component_info_map: dict = {}
|
|
101
101
|
|
|
@@ -114,10 +114,10 @@ class ScanPostProcessor(ScanossBase):
|
|
|
114
114
|
if not self.results:
|
|
115
115
|
return
|
|
116
116
|
for _, result in self.results.items():
|
|
117
|
-
|
|
118
|
-
purls =
|
|
117
|
+
entry = result[0] if isinstance(result, list) else result
|
|
118
|
+
purls = entry.get('purl', [])
|
|
119
119
|
for purl in purls:
|
|
120
|
-
self.component_info_map[purl] =
|
|
120
|
+
self.component_info_map[purl] = entry
|
|
121
121
|
|
|
122
122
|
def post_process(self):
|
|
123
123
|
"""
|
|
@@ -126,7 +126,7 @@ class ScanPostProcessor(ScanossBase):
|
|
|
126
126
|
Returns:
|
|
127
127
|
dict: Processed results
|
|
128
128
|
"""
|
|
129
|
-
if self.
|
|
129
|
+
if self.scanoss_settings.is_legacy():
|
|
130
130
|
self.print_stderr(
|
|
131
131
|
'Legacy settings file detected. Post-processing is not supported for legacy settings file.'
|
|
132
132
|
)
|
|
@@ -139,7 +139,7 @@ class ScanPostProcessor(ScanossBase):
|
|
|
139
139
|
"""
|
|
140
140
|
Remove entries from the results based on files and/or purls specified in the SCANOSS settings file
|
|
141
141
|
"""
|
|
142
|
-
to_remove_entries = self.
|
|
142
|
+
to_remove_entries = self.scanoss_settings.get_bom_remove()
|
|
143
143
|
if not to_remove_entries:
|
|
144
144
|
return
|
|
145
145
|
self.results = {
|
|
@@ -152,15 +152,15 @@ class ScanPostProcessor(ScanossBase):
|
|
|
152
152
|
"""
|
|
153
153
|
Replace purls in the results based on the SCANOSS settings file
|
|
154
154
|
"""
|
|
155
|
-
to_replace_entries = self.
|
|
155
|
+
to_replace_entries = self.scanoss_settings.get_bom_replace()
|
|
156
156
|
if not to_replace_entries:
|
|
157
157
|
return
|
|
158
158
|
|
|
159
159
|
for result_path, result in self.results.items():
|
|
160
|
-
|
|
161
|
-
should_replace, to_replace_with_purl = self._should_replace_result(result_path,
|
|
160
|
+
entry = result[0] if isinstance(result, list) else result
|
|
161
|
+
should_replace, to_replace_with_purl = self._should_replace_result(result_path, entry, to_replace_entries)
|
|
162
162
|
if should_replace:
|
|
163
|
-
self.results[result_path] = [self._update_replaced_result(
|
|
163
|
+
self.results[result_path] = [self._update_replaced_result(entry, to_replace_with_purl)]
|
|
164
164
|
|
|
165
165
|
def _update_replaced_result(self, result: dict, to_replace_with_purl: str) -> dict:
|
|
166
166
|
"""
|
|
@@ -6,8 +6,8 @@ protoc_gen_swagger/options/annotations_pb2_grpc.py,sha256=KZOW9Ciio-f9iL42FuLFnS
|
|
|
6
6
|
protoc_gen_swagger/options/openapiv2_pb2.py,sha256=w0xDs63uyrWGgzRaQZXfJpfI7Jpyvh-i9ay_uzOR-aM,16475
|
|
7
7
|
protoc_gen_swagger/options/openapiv2_pb2.pyi,sha256=hYOV6uQ2yqhP89042_V3GuAsvoBBiXf5CGuYmnFnfv4,54665
|
|
8
8
|
protoc_gen_swagger/options/openapiv2_pb2_grpc.py,sha256=sje9Nh3yE7CHCUWZwtjTgwsKB4GvyGz5vOrGTnRXJfc,917
|
|
9
|
-
scanoss/__init__.py,sha256=
|
|
10
|
-
scanoss/cli.py,sha256=
|
|
9
|
+
scanoss/__init__.py,sha256=iPkgzK0wSsFjSAwlOzqgAXcFJW65XbmsUg-xi_nfZUo,1146
|
|
10
|
+
scanoss/cli.py,sha256=oSw9-YTxZE90wUP4WECXVQh5Zzl_SGVA8NkcwdU3-4M,106155
|
|
11
11
|
scanoss/components.py,sha256=NFyt_w3aoMotr_ZaFU-ng00_89sruc0kgY7ERnJXkmM,15891
|
|
12
12
|
scanoss/constants.py,sha256=vurzLNIfP_dnRMwOdZsUWvr5XAVuGoj98XZ0yjXNOjQ,632
|
|
13
13
|
scanoss/cryptography.py,sha256=lOoD_dW16ARQxYiYyb5R8S7gx0FqWIsnGkKfsB0nGaU,10627
|
|
@@ -20,13 +20,14 @@ scanoss/gitlabqualityreport.py,sha256=_VG0Xoh8wYF3lsXGJvjoj-Ty58OS_-H1Domiq9OpQE
|
|
|
20
20
|
scanoss/header_filter.py,sha256=-Dqore9coROLMWWw9yP3nz8dpCB7jYAVm842hoRTmeE,21879
|
|
21
21
|
scanoss/osadl.py,sha256=VWalcHpshWxtRDGje2cK32SfFeSBAO62knfSW9pyYqc,4558
|
|
22
22
|
scanoss/results.py,sha256=47ZXXuU2sDjYa5vhtbWTmikit9jHhA0rsYKwkvZFI5w,9252
|
|
23
|
+
scanoss/scan_settings_builder.py,sha256=3AyQXJgBVZLWPyRhptACSB8LoMHdX3BSzRqeaYQIXR8,12143
|
|
23
24
|
scanoss/scancodedeps.py,sha256=JbpoGW1POtPMmowzfwa4oh8sSBeeQCqaW9onvc4UFYM,11517
|
|
24
|
-
scanoss/scanner.py,sha256=
|
|
25
|
-
scanoss/scanoss_settings.py,sha256=
|
|
26
|
-
scanoss/scanossapi.py,sha256=
|
|
25
|
+
scanoss/scanner.py,sha256=p2d4mbap5gaY5WVJPMsicZF0pPu9vbokUJcd9GGC77k,43193
|
|
26
|
+
scanoss/scanoss_settings.py,sha256=O0AD0XhXYiQaD2Ym3pmoj0kJrUuEvY9gfsSliDaQ40c,15600
|
|
27
|
+
scanoss/scanossapi.py,sha256=3wN5POEt-Hi3qz_8VFC0JqDw2QHliJ-dJIW8VNZEzc8,16906
|
|
27
28
|
scanoss/scanossbase.py,sha256=tKlHPAi50ZarGaPXsNi1XrowQBynsSqSSst-NuG2ScI,3163
|
|
28
29
|
scanoss/scanossgrpc.py,sha256=9UuVPUjBLUhqim_tSntyoRZW-OAtiz5iP_VjjNr5RPY,41715
|
|
29
|
-
scanoss/scanpostprocessor.py,sha256
|
|
30
|
+
scanoss/scanpostprocessor.py,sha256=u6ZfwkSukn8TjeqRj1_IuWNjlUEug1_B36vZNIfUPZk,11081
|
|
30
31
|
scanoss/scantype.py,sha256=gFmyVmKQpHWogN2iCmMj032e_sZo4T92xS3_EH5B3Tc,1310
|
|
31
32
|
scanoss/spdxlite.py,sha256=4JMxmyNmvcL6fjScihk8toWfSuQ-Pj1gzaT3SIn1fXA,29425
|
|
32
33
|
scanoss/threadeddependencies.py,sha256=aN8E43iKS1pWJLJP3xCle5ewlfR5DE2-ljUzI_29Xwk,9851
|
|
@@ -67,9 +68,9 @@ scanoss/api/vulnerabilities/__init__.py,sha256=IFrDk_DTJgKSZmmU-nuLXuq_s8sQZlrSC
|
|
|
67
68
|
scanoss/api/vulnerabilities/v2/__init__.py,sha256=IFrDk_DTJgKSZmmU-nuLXuq_s8sQZlrSCHhIDMJT4r0,1122
|
|
68
69
|
scanoss/api/vulnerabilities/v2/scanoss_vulnerabilities_pb2.py,sha256=pmm0MSiXkdf8e4rCIIDRcsNRixR2vGvD1Xak4l-wdwI,16550
|
|
69
70
|
scanoss/api/vulnerabilities/v2/scanoss_vulnerabilities_pb2_grpc.py,sha256=BNxT5kUKQ-mgtOt5QYBM1Qrg5LNDqSpWKpfEZquIlsM,19127
|
|
70
|
-
scanoss/data/build_date.txt,sha256
|
|
71
|
+
scanoss/data/build_date.txt,sha256=gyjPVgVGpUWx0EUA2CYrW4yKZV_gBf-3hDut1Va79ME,40
|
|
71
72
|
scanoss/data/osadl-copyleft.json,sha256=O9b2XAfpjQY0TL0fYzO6kwMcp5IwQbF6f_YWbB10MhQ,4761
|
|
72
|
-
scanoss/data/scanoss-settings-schema.json,sha256=
|
|
73
|
+
scanoss/data/scanoss-settings-schema.json,sha256=baOwoHm5_JumSOxWYp6COHqnmTR7eO9RxVwf3FAOuJY,12157
|
|
73
74
|
scanoss/data/spdx-exceptions.json,sha256=s7UTYxC7jqQXr11YBlIWYCNwN6lRDFTR33Y8rpN_dA4,17953
|
|
74
75
|
scanoss/data/spdx-licenses.json,sha256=A6Z0q82gaTLtnopBfzeIVZjJFxkdRW1g2TuumQc-lII,228794
|
|
75
76
|
scanoss/export/__init__.py,sha256=D4C0lWLuNp8k_BjQZEc07WZcUgAvriVwQWOk063b0ZU,1122
|
|
@@ -102,9 +103,9 @@ scanoss/utils/crc64.py,sha256=TMrwQimSdE6imhFOUL7oAG6Kxu-8qMpGWMuMg8QpSVs,3169
|
|
|
102
103
|
scanoss/utils/file.py,sha256=62cA9a17TU9ZvfA3FY5HY4-QOajJeSrc8S6xLA_f-3M,2980
|
|
103
104
|
scanoss/utils/scanoss_scan_results_utils.py,sha256=ho9-DKefHFJlVZkw4gXOmMI-mgPIbV9Y2ftkI83fC1k,1727
|
|
104
105
|
scanoss/utils/simhash.py,sha256=6iu8DOcecPAY36SZjCOzrrLMT9oIE7-gI6QuYwUQ7B0,5793
|
|
105
|
-
scanoss-1.
|
|
106
|
-
scanoss-1.
|
|
107
|
-
scanoss-1.
|
|
108
|
-
scanoss-1.
|
|
109
|
-
scanoss-1.
|
|
110
|
-
scanoss-1.
|
|
106
|
+
scanoss-1.45.0.dist-info/licenses/LICENSE,sha256=LLUaXoiyOroIbr5ubAyrxBOwSRLTm35ETO2FmLpy8QQ,1074
|
|
107
|
+
scanoss-1.45.0.dist-info/METADATA,sha256=xPca-0CXnZ6JXbrAuZVBY47jk-glQLmhNpATuinZhZk,6220
|
|
108
|
+
scanoss-1.45.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
109
|
+
scanoss-1.45.0.dist-info/entry_points.txt,sha256=Uy28xnaDL5KQ7V77sZD5VLDXPNxYYzSr5tsqtiXVzAs,48
|
|
110
|
+
scanoss-1.45.0.dist-info/top_level.txt,sha256=V11PrQ6Pnrc-nDF9xnisnJ8e6-i7HqSIKVNqduRWcL8,27
|
|
111
|
+
scanoss-1.45.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|