scanoss 1.20.0__py3-none-any.whl → 1.20.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 +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 +576 -293
- 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.1.dist-info}/METADATA +1 -1
- scanoss-1.20.1.dist-info/RECORD +74 -0
- scanoss-1.20.0.dist-info/RECORD +0 -74
- {scanoss-1.20.0.dist-info → scanoss-1.20.1.dist-info}/LICENSE +0 -0
- {scanoss-1.20.0.dist-info → scanoss-1.20.1.dist-info}/WHEEL +0 -0
- {scanoss-1.20.0.dist-info → scanoss-1.20.1.dist-info}/entry_points.txt +0 -0
- {scanoss-1.20.0.dist-info → scanoss-1.20.1.dist-info}/top_level.txt +0 -0
scanoss/cli.py
CHANGED
|
@@ -1,47 +1,48 @@
|
|
|
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 argparse
|
|
25
26
|
import os
|
|
26
|
-
from pathlib import Path
|
|
27
27
|
import sys
|
|
28
|
+
from pathlib import Path
|
|
29
|
+
|
|
28
30
|
import pypac
|
|
29
31
|
|
|
32
|
+
from . import __version__
|
|
33
|
+
from .components import Components
|
|
34
|
+
from .csvoutput import CsvOutput
|
|
35
|
+
from .cyclonedx import CycloneDx
|
|
36
|
+
from .filecount import FileCount
|
|
30
37
|
from .inspection.copyleft import Copyleft
|
|
31
38
|
from .inspection.undeclared_component import UndeclaredComponent
|
|
32
|
-
from .
|
|
33
|
-
from .scanoss_settings import ScanossSettings, ScanossSettingsError
|
|
39
|
+
from .results import Results
|
|
34
40
|
from .scancodedeps import ScancodeDeps
|
|
35
|
-
from .scanner import Scanner
|
|
41
|
+
from .scanner import FAST_WINNOWING, Scanner
|
|
42
|
+
from .scanoss_settings import ScanossSettings, ScanossSettingsError
|
|
36
43
|
from .scantype import ScanType
|
|
37
|
-
from .filecount import FileCount
|
|
38
|
-
from .cyclonedx import CycloneDx
|
|
39
44
|
from .spdxlite import SpdxLite
|
|
40
|
-
from .
|
|
41
|
-
from .components import Components
|
|
42
|
-
from . import __version__
|
|
43
|
-
from .scanner import FAST_WINNOWING
|
|
44
|
-
from .results import Results
|
|
45
|
+
from .threadeddependencies import SCOPE
|
|
45
46
|
from .utils.file import validate_json_file
|
|
46
47
|
|
|
47
48
|
|
|
@@ -56,159 +57,248 @@ def setup_args() -> None:
|
|
|
56
57
|
"""
|
|
57
58
|
Setup all the command line arguments for processing
|
|
58
59
|
"""
|
|
59
|
-
parser = argparse.ArgumentParser(
|
|
60
|
+
parser = argparse.ArgumentParser(
|
|
61
|
+
description=f'SCANOSS Python CLI. Ver: {__version__}, License: MIT, Fast Winnowing: {FAST_WINNOWING}'
|
|
62
|
+
)
|
|
60
63
|
parser.add_argument('--version', '-v', action='store_true', help='Display version details')
|
|
61
64
|
|
|
62
|
-
subparsers = parser.add_subparsers(
|
|
63
|
-
|
|
65
|
+
subparsers = parser.add_subparsers(
|
|
66
|
+
title='Sub Commands', dest='subparser', description='valid subcommands', help='sub-command help'
|
|
67
|
+
)
|
|
64
68
|
# Sub-command: version
|
|
65
|
-
p_ver = subparsers.add_parser(
|
|
66
|
-
|
|
69
|
+
p_ver = subparsers.add_parser(
|
|
70
|
+
'version', aliases=['ver'], description=f'Version of SCANOSS CLI: {__version__}', help='SCANOSS version'
|
|
71
|
+
)
|
|
67
72
|
p_ver.set_defaults(func=ver)
|
|
68
73
|
|
|
69
74
|
# Sub-command: scan
|
|
70
|
-
p_scan = subparsers.add_parser(
|
|
71
|
-
|
|
72
|
-
|
|
75
|
+
p_scan = subparsers.add_parser(
|
|
76
|
+
'scan',
|
|
77
|
+
aliases=['sc'],
|
|
78
|
+
description=f'Analyse/scan the given source base: {__version__}',
|
|
79
|
+
help='Scan source code',
|
|
80
|
+
)
|
|
73
81
|
p_scan.set_defaults(func=scan)
|
|
74
82
|
p_scan.add_argument('scan_dir', metavar='FILE/DIR', type=str, nargs='?', help='A file or folder to scan')
|
|
75
|
-
p_scan.add_argument('--wfp', '-w',
|
|
76
|
-
|
|
77
|
-
p_scan.add_argument(
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
p_scan.add_argument('--files', '-e', type=str, nargs="*", help='List of files to scan.')
|
|
83
|
+
p_scan.add_argument('--wfp', '-w', type=str, help='Scan a WFP File instead of a folder (optional)')
|
|
84
|
+
p_scan.add_argument('--dep', '-p', type=str, help='Use a dependency file instead of a folder (optional)')
|
|
85
|
+
p_scan.add_argument(
|
|
86
|
+
'--stdin', '-s', metavar='STDIN-FILENAME', type=str, help='Scan the file contents supplied via STDIN (optional)'
|
|
87
|
+
)
|
|
88
|
+
p_scan.add_argument('--files', '-e', type=str, nargs='*', help='List of files to scan.')
|
|
82
89
|
p_scan.add_argument('--identify', '-i', type=str, help='Scan and identify components in SBOM file')
|
|
83
|
-
p_scan.add_argument('--ignore',
|
|
84
|
-
p_scan.add_argument('--output',
|
|
85
|
-
p_scan.add_argument(
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
90
|
+
p_scan.add_argument('--ignore', '-n', type=str, help='Ignore components specified in the SBOM file')
|
|
91
|
+
p_scan.add_argument('--output', '-o', type=str, help='Output result file name (optional - default stdout).')
|
|
92
|
+
p_scan.add_argument(
|
|
93
|
+
'--format',
|
|
94
|
+
'-f',
|
|
95
|
+
type=str,
|
|
96
|
+
choices=['plain', 'cyclonedx', 'spdxlite', 'csv'],
|
|
97
|
+
help='Result output format (optional - default: plain)',
|
|
98
|
+
)
|
|
99
|
+
p_scan.add_argument(
|
|
100
|
+
'--threads', '-T', type=int, default=5, help='Number of threads to use while scanning (optional - default 5)'
|
|
101
|
+
)
|
|
102
|
+
p_scan.add_argument(
|
|
103
|
+
'--flags',
|
|
104
|
+
'-F',
|
|
105
|
+
type=int,
|
|
106
|
+
help='Scanning engine flags (1: disable snippet matching, 2 enable snippet ids, '
|
|
107
|
+
'4: disable dependencies, 8: disable licenses, 16: disable copyrights,'
|
|
108
|
+
'32: disable vulnerabilities, 64: disable quality, 128: disable cryptography,'
|
|
109
|
+
'256: disable best match only, 512: hide identified files, '
|
|
110
|
+
'1024: enable download_url, 2048: enable GitHub full path, '
|
|
111
|
+
'4096: disable extended server stats)',
|
|
112
|
+
)
|
|
113
|
+
p_scan.add_argument(
|
|
114
|
+
'--post-size',
|
|
115
|
+
'-P',
|
|
116
|
+
type=int,
|
|
117
|
+
default=32,
|
|
118
|
+
help='Number of kilobytes to limit the post to while scanning (optional - default 32)',
|
|
119
|
+
)
|
|
120
|
+
p_scan.add_argument(
|
|
121
|
+
'--timeout',
|
|
122
|
+
'-M',
|
|
123
|
+
type=int,
|
|
124
|
+
default=180,
|
|
125
|
+
help='Timeout (in seconds) for API communication (optional - default 180)',
|
|
126
|
+
)
|
|
127
|
+
p_scan.add_argument(
|
|
128
|
+
'--retry', '-R', type=int, default=5, help='Retry limit for API communication (optional - default 5)'
|
|
129
|
+
)
|
|
102
130
|
p_scan.add_argument('--no-wfp-output', action='store_true', help='Skip WFP file generation')
|
|
103
131
|
p_scan.add_argument('--dependencies', '-D', action='store_true', help='Add Dependency scanning')
|
|
104
132
|
p_scan.add_argument('--dependencies-only', action='store_true', help='Run Dependency scanning only')
|
|
105
|
-
p_scan.add_argument(
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
133
|
+
p_scan.add_argument(
|
|
134
|
+
'--sc-command', type=str, help='Scancode command and path if required (optional - default scancode).'
|
|
135
|
+
)
|
|
136
|
+
p_scan.add_argument(
|
|
137
|
+
'--sc-timeout',
|
|
138
|
+
type=int,
|
|
139
|
+
default=600,
|
|
140
|
+
help='Timeout (in seconds) for scancode to complete (optional - default 600)',
|
|
141
|
+
)
|
|
142
|
+
p_scan.add_argument(
|
|
143
|
+
'--dep-scope', '-ds', type=SCOPE, help='Filter dependencies by scope - default all (options: dev/prod)'
|
|
144
|
+
)
|
|
145
|
+
p_scan.add_argument('--dep-scope-inc', '-dsi', type=str, help='Include dependencies with declared scopes')
|
|
111
146
|
p_scan.add_argument('--dep-scope-exc', '-dse', type=str, help='Exclude dependencies with declared scopes')
|
|
112
147
|
p_scan.add_argument(
|
|
113
|
-
'--settings',
|
|
148
|
+
'--settings',
|
|
149
|
+
'-st',
|
|
114
150
|
type=str,
|
|
115
151
|
help='Settings file to use for scanning (optional - default scanoss.json)',
|
|
116
152
|
)
|
|
117
153
|
p_scan.add_argument(
|
|
118
|
-
'--skip-settings-file',
|
|
154
|
+
'--skip-settings-file',
|
|
155
|
+
'-stf',
|
|
156
|
+
action='store_true',
|
|
119
157
|
help='Skip default settings file (scanoss.json) if it exists',
|
|
120
158
|
)
|
|
121
159
|
|
|
122
160
|
# Sub-command: fingerprint
|
|
123
|
-
p_wfp = subparsers.add_parser(
|
|
124
|
-
|
|
125
|
-
|
|
161
|
+
p_wfp = subparsers.add_parser(
|
|
162
|
+
'fingerprint',
|
|
163
|
+
aliases=['fp', 'wfp'],
|
|
164
|
+
description=f'Fingerprint the given source base: {__version__}',
|
|
165
|
+
help='Fingerprint source code',
|
|
166
|
+
)
|
|
126
167
|
p_wfp.set_defaults(func=wfp)
|
|
127
|
-
p_wfp.add_argument('scan_dir', metavar='FILE/DIR', type=str, nargs='?',
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
168
|
+
p_wfp.add_argument('scan_dir', metavar='FILE/DIR', type=str, nargs='?', help='A file or folder to scan')
|
|
169
|
+
p_wfp.add_argument(
|
|
170
|
+
'--stdin',
|
|
171
|
+
'-s',
|
|
172
|
+
metavar='STDIN-FILENAME',
|
|
173
|
+
type=str,
|
|
174
|
+
help='Fingerprint the file contents supplied via STDIN (optional)',
|
|
175
|
+
)
|
|
131
176
|
p_wfp.add_argument('--output', '-o', type=str, help='Output result file name (optional - default stdout).')
|
|
132
177
|
p_wfp.add_argument(
|
|
133
|
-
'--settings',
|
|
178
|
+
'--settings',
|
|
179
|
+
'-st',
|
|
134
180
|
type=str,
|
|
135
181
|
help='Settings file to use for fingerprinting (optional - default scanoss.json)',
|
|
136
182
|
)
|
|
137
183
|
p_wfp.add_argument(
|
|
138
|
-
'--skip-settings-file',
|
|
184
|
+
'--skip-settings-file',
|
|
185
|
+
'-stf',
|
|
186
|
+
action='store_true',
|
|
139
187
|
help='Skip default settings file (scanoss.json) if it exists',
|
|
140
188
|
)
|
|
141
189
|
|
|
142
190
|
# Sub-command: dependency
|
|
143
|
-
p_dep = subparsers.add_parser(
|
|
144
|
-
|
|
145
|
-
|
|
191
|
+
p_dep = subparsers.add_parser(
|
|
192
|
+
'dependencies',
|
|
193
|
+
aliases=['dp', 'dep'],
|
|
194
|
+
description=f'Produce dependency file summary: {__version__}',
|
|
195
|
+
help='Scan source code for dependencies, but do not decorate them',
|
|
196
|
+
)
|
|
146
197
|
p_dep.set_defaults(func=dependency)
|
|
147
198
|
p_dep.add_argument('scan_dir', metavar='FILE/DIR', type=str, nargs='?', help='A file or folder to scan')
|
|
148
199
|
p_dep.add_argument('--output', '-o', type=str, help='Output result file name (optional - default stdout).')
|
|
149
|
-
p_dep.add_argument(
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
200
|
+
p_dep.add_argument(
|
|
201
|
+
'--sc-command', type=str, help='Scancode command and path if required (optional - default scancode).'
|
|
202
|
+
)
|
|
203
|
+
p_dep.add_argument(
|
|
204
|
+
'--sc-timeout',
|
|
205
|
+
type=int,
|
|
206
|
+
default=600,
|
|
207
|
+
help='Timeout (in seconds) for scancode to complete (optional - default 600)',
|
|
208
|
+
)
|
|
153
209
|
|
|
154
210
|
# Sub-command: file_count
|
|
155
|
-
p_fc = subparsers.add_parser(
|
|
156
|
-
|
|
157
|
-
|
|
211
|
+
p_fc = subparsers.add_parser(
|
|
212
|
+
'file_count',
|
|
213
|
+
aliases=['fc'],
|
|
214
|
+
description=f'Produce a file type count summary: {__version__}',
|
|
215
|
+
help='Search the source tree and produce a file type summary',
|
|
216
|
+
)
|
|
158
217
|
p_fc.set_defaults(func=file_count)
|
|
159
218
|
p_fc.add_argument('scan_dir', metavar='DIR', type=str, nargs='?', help='A folder to search')
|
|
160
219
|
p_fc.add_argument('--output', '-o', type=str, help='Output result file name (optional - default stdout).')
|
|
161
220
|
p_fc.add_argument('--all-hidden', action='store_true', help='Scan all hidden files/folders')
|
|
162
221
|
|
|
163
222
|
# Sub-command: convert
|
|
164
|
-
p_cnv = subparsers.add_parser(
|
|
165
|
-
|
|
166
|
-
|
|
223
|
+
p_cnv = subparsers.add_parser(
|
|
224
|
+
'convert',
|
|
225
|
+
aliases=['cv', 'cnv', 'cvrt'],
|
|
226
|
+
description=f'Convert results files between formats: {__version__}',
|
|
227
|
+
help='Convert file format',
|
|
228
|
+
)
|
|
167
229
|
p_cnv.set_defaults(func=convert)
|
|
168
230
|
p_cnv.add_argument('--input', '-i', type=str, required=True, help='Input file name')
|
|
169
231
|
p_cnv.add_argument('--output', '-o', type=str, help='Output result file name (optional - default stdout).')
|
|
170
|
-
p_cnv.add_argument(
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
232
|
+
p_cnv.add_argument(
|
|
233
|
+
'--format',
|
|
234
|
+
'-f',
|
|
235
|
+
type=str,
|
|
236
|
+
choices=['cyclonedx', 'spdxlite', 'csv'],
|
|
237
|
+
default='spdxlite',
|
|
238
|
+
help='Output format (optional - default: spdxlite)',
|
|
239
|
+
)
|
|
240
|
+
p_cnv.add_argument(
|
|
241
|
+
'--input-format', type=str, choices=['plain'], default='plain', help='Input format (optional - default: plain)'
|
|
242
|
+
)
|
|
174
243
|
|
|
175
244
|
# Sub-command: component
|
|
176
|
-
p_comp = subparsers.add_parser(
|
|
177
|
-
|
|
178
|
-
|
|
245
|
+
p_comp = subparsers.add_parser(
|
|
246
|
+
'component',
|
|
247
|
+
aliases=['comp'],
|
|
248
|
+
description=f'SCANOSS Component commands: {__version__}',
|
|
249
|
+
help='Component support commands',
|
|
250
|
+
)
|
|
179
251
|
|
|
180
|
-
comp_sub = p_comp.add_subparsers(
|
|
181
|
-
|
|
252
|
+
comp_sub = p_comp.add_subparsers(
|
|
253
|
+
title='Component Commands',
|
|
254
|
+
dest='subparsercmd',
|
|
255
|
+
description='component sub-commands',
|
|
256
|
+
help='component sub-commands',
|
|
257
|
+
)
|
|
182
258
|
|
|
183
259
|
# Component Sub-command: component crypto
|
|
184
|
-
c_crypto = comp_sub.add_parser(
|
|
185
|
-
|
|
186
|
-
|
|
260
|
+
c_crypto = comp_sub.add_parser(
|
|
261
|
+
'crypto',
|
|
262
|
+
aliases=['cr'],
|
|
263
|
+
description=f'Show Cryptographic algorithms: {__version__}',
|
|
264
|
+
help='Retrieve cryptographic algorithms for the given components',
|
|
265
|
+
)
|
|
187
266
|
c_crypto.set_defaults(func=comp_crypto)
|
|
188
267
|
|
|
189
268
|
# Component Sub-command: component vulns
|
|
190
|
-
c_vulns = comp_sub.add_parser(
|
|
191
|
-
|
|
192
|
-
|
|
269
|
+
c_vulns = comp_sub.add_parser(
|
|
270
|
+
'vulns',
|
|
271
|
+
aliases=['vulnerabilities', 'vu'],
|
|
272
|
+
description=f'Show Vulnerability details: {__version__}',
|
|
273
|
+
help='Retrieve vulnerabilities for the given components',
|
|
274
|
+
)
|
|
193
275
|
c_vulns.set_defaults(func=comp_vulns)
|
|
194
276
|
|
|
195
277
|
# Component Sub-command: component semgrep
|
|
196
|
-
c_semgrep = comp_sub.add_parser(
|
|
197
|
-
|
|
198
|
-
|
|
278
|
+
c_semgrep = comp_sub.add_parser(
|
|
279
|
+
'semgrep',
|
|
280
|
+
aliases=['sp'],
|
|
281
|
+
description=f'Show Semgrep findings: {__version__}',
|
|
282
|
+
help='Retrieve semgrep issues/findings for the given components',
|
|
283
|
+
)
|
|
199
284
|
c_semgrep.set_defaults(func=comp_semgrep)
|
|
200
285
|
|
|
201
286
|
# Component Sub-command: component provenance
|
|
202
|
-
c_provenance = comp_sub.add_parser(
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
287
|
+
c_provenance = comp_sub.add_parser(
|
|
288
|
+
'provenance',
|
|
289
|
+
aliases=['prov', 'prv'],
|
|
290
|
+
description=f'Show Provenance findings: {__version__}',
|
|
291
|
+
help='Retrieve provenance for the given components',
|
|
292
|
+
)
|
|
293
|
+
c_provenance.set_defaults(func=c_provenance)
|
|
207
294
|
|
|
208
295
|
# Component Sub-command: component search
|
|
209
|
-
c_search = comp_sub.add_parser(
|
|
210
|
-
|
|
211
|
-
|
|
296
|
+
c_search = comp_sub.add_parser(
|
|
297
|
+
'search',
|
|
298
|
+
aliases=['sc'],
|
|
299
|
+
description=f'Search component details: {__version__}',
|
|
300
|
+
help='Search for a KB component',
|
|
301
|
+
)
|
|
212
302
|
c_search.add_argument('--input', '-i', type=str, help='Input file name')
|
|
213
303
|
c_search.add_argument('--search', '-s', type=str, help='Generic component search')
|
|
214
304
|
c_search.add_argument('--vendor', '-v', type=str, help='Generic component search')
|
|
@@ -219,68 +309,100 @@ def setup_args() -> None:
|
|
|
219
309
|
c_search.set_defaults(func=comp_search)
|
|
220
310
|
|
|
221
311
|
# Component Sub-command: component versions
|
|
222
|
-
c_versions = comp_sub.add_parser(
|
|
223
|
-
|
|
224
|
-
|
|
312
|
+
c_versions = comp_sub.add_parser(
|
|
313
|
+
'versions',
|
|
314
|
+
aliases=['vs'],
|
|
315
|
+
description=f'Get component version details: {__version__}',
|
|
316
|
+
help='Search for component versions',
|
|
317
|
+
)
|
|
225
318
|
c_versions.add_argument('--input', '-i', type=str, help='Input file name')
|
|
226
319
|
c_versions.add_argument('--purl', '-p', type=str, help='Generic component search')
|
|
227
320
|
c_versions.add_argument('--limit', '-l', type=int, help='Generic component search')
|
|
228
321
|
c_versions.set_defaults(func=comp_versions)
|
|
229
322
|
|
|
230
323
|
# Common purl Component sub-command options
|
|
231
|
-
for p in [c_crypto, c_vulns, c_semgrep
|
|
232
|
-
p.add_argument('--purl',
|
|
324
|
+
for p in [c_crypto, c_vulns, c_semgrep]:
|
|
325
|
+
p.add_argument('--purl', '-p', type=str, nargs='*', help='Package URL - PURL to process.')
|
|
233
326
|
p.add_argument('--input', '-i', type=str, help='Input file name')
|
|
234
327
|
# Common Component sub-command options
|
|
235
328
|
for p in [c_crypto, c_vulns, c_search, c_versions, c_semgrep, c_provenance]:
|
|
236
329
|
p.add_argument('--output', '-o', type=str, help='Output result file name (optional - default stdout).')
|
|
237
|
-
p.add_argument(
|
|
238
|
-
|
|
330
|
+
p.add_argument(
|
|
331
|
+
'--timeout',
|
|
332
|
+
'-M',
|
|
333
|
+
type=int,
|
|
334
|
+
default=600,
|
|
335
|
+
help='Timeout (in seconds) for API communication (optional - default 600)',
|
|
336
|
+
)
|
|
239
337
|
|
|
240
338
|
# Sub-command: utils
|
|
241
|
-
p_util = subparsers.add_parser(
|
|
242
|
-
|
|
243
|
-
|
|
339
|
+
p_util = subparsers.add_parser(
|
|
340
|
+
'utils',
|
|
341
|
+
aliases=['ut'],
|
|
342
|
+
description=f'SCANOSS Utility commands: {__version__}',
|
|
343
|
+
help='General utility support commands',
|
|
344
|
+
)
|
|
244
345
|
|
|
245
|
-
utils_sub = p_util.add_subparsers(
|
|
246
|
-
|
|
346
|
+
utils_sub = p_util.add_subparsers(
|
|
347
|
+
title='Utils Commands', dest='subparsercmd', description='utils sub-commands', help='utils sub-commands'
|
|
348
|
+
)
|
|
247
349
|
|
|
248
350
|
# Utils Sub-command: utils fast
|
|
249
|
-
p_f_f = utils_sub.add_parser(
|
|
250
|
-
|
|
351
|
+
p_f_f = utils_sub.add_parser(
|
|
352
|
+
'fast', description=f'Is fast winnowing enabled: {__version__}', help='SCANOSS fast winnowing'
|
|
353
|
+
)
|
|
251
354
|
p_f_f.set_defaults(func=fast)
|
|
252
355
|
|
|
253
356
|
# Utils Sub-command: utils certloc
|
|
254
|
-
p_c_loc = utils_sub.add_parser(
|
|
255
|
-
|
|
256
|
-
|
|
357
|
+
p_c_loc = utils_sub.add_parser(
|
|
358
|
+
'certloc',
|
|
359
|
+
aliases=['cl'],
|
|
360
|
+
description=f'Show location of Python CA Certs: {__version__}',
|
|
361
|
+
help='Display the location of Python CA Certs',
|
|
362
|
+
)
|
|
257
363
|
p_c_loc.set_defaults(func=utils_certloc)
|
|
258
364
|
|
|
259
365
|
# Utils Sub-command: utils cert-download
|
|
260
|
-
p_c_dwnld = utils_sub.add_parser(
|
|
261
|
-
|
|
262
|
-
|
|
366
|
+
p_c_dwnld = utils_sub.add_parser(
|
|
367
|
+
'cert-download',
|
|
368
|
+
aliases=['cdl', 'cert-dl'],
|
|
369
|
+
description=f'Download Server SSL Cert: {__version__}',
|
|
370
|
+
help="Download the specified server's SSL PEM certificate",
|
|
371
|
+
)
|
|
263
372
|
p_c_dwnld.set_defaults(func=utils_cert_download)
|
|
264
373
|
p_c_dwnld.add_argument('--hostname', '-n', required=True, type=str, help='Server hostname to download cert from.')
|
|
265
|
-
p_c_dwnld.add_argument(
|
|
266
|
-
|
|
374
|
+
p_c_dwnld.add_argument(
|
|
375
|
+
'--port', '-p', required=False, type=int, default=443, help='Server port number (default: 443).'
|
|
376
|
+
)
|
|
267
377
|
p_c_dwnld.add_argument('--output', '-o', type=str, help='Output result file name (optional - default stdout).')
|
|
268
378
|
|
|
269
379
|
# Utils Sub-command: utils pac-proxy
|
|
270
|
-
p_p_proxy = utils_sub.add_parser(
|
|
271
|
-
|
|
272
|
-
|
|
380
|
+
p_p_proxy = utils_sub.add_parser(
|
|
381
|
+
'pac-proxy',
|
|
382
|
+
aliases=['pac'],
|
|
383
|
+
description=f'Determine Proxy from PAC: {__version__}',
|
|
384
|
+
help='Use Proxy Auto-Config to determine proxy configuration',
|
|
385
|
+
)
|
|
273
386
|
p_p_proxy.set_defaults(func=utils_pac_proxy)
|
|
274
|
-
p_p_proxy.add_argument(
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
387
|
+
p_p_proxy.add_argument(
|
|
388
|
+
'--pac',
|
|
389
|
+
required=False,
|
|
390
|
+
type=str,
|
|
391
|
+
default='auto',
|
|
392
|
+
help='Proxy auto configuration. Specify a file, http url or "auto" to try to discover it.',
|
|
393
|
+
)
|
|
394
|
+
p_p_proxy.add_argument(
|
|
395
|
+
'--url',
|
|
396
|
+
required=False,
|
|
397
|
+
type=str,
|
|
398
|
+
default='https://api.osskb.org',
|
|
399
|
+
help='URL to test (default: https://api.osskb.org).',
|
|
400
|
+
)
|
|
279
401
|
|
|
280
402
|
p_results = subparsers.add_parser(
|
|
281
403
|
'results',
|
|
282
404
|
aliases=['res'],
|
|
283
|
-
description=f
|
|
405
|
+
description=f'SCANOSS Results commands: {__version__}',
|
|
284
406
|
help='Process scan results',
|
|
285
407
|
)
|
|
286
408
|
p_results.add_argument(
|
|
@@ -318,37 +440,66 @@ def setup_args() -> None:
|
|
|
318
440
|
)
|
|
319
441
|
p_results.set_defaults(func=results)
|
|
320
442
|
|
|
321
|
-
|
|
322
443
|
# Sub-command: inspect
|
|
323
|
-
p_inspect = subparsers.add_parser(
|
|
324
|
-
|
|
325
|
-
|
|
444
|
+
p_inspect = subparsers.add_parser(
|
|
445
|
+
'inspect', aliases=['insp', 'ins'], description=f'Inspect results: {__version__}', help='Inspect results'
|
|
446
|
+
)
|
|
326
447
|
# Sub-parser: inspect
|
|
327
|
-
p_inspect_sub = p_inspect.add_subparsers(
|
|
328
|
-
|
|
448
|
+
p_inspect_sub = p_inspect.add_subparsers(
|
|
449
|
+
title='Inspect Commands', dest='subparsercmd', description='Inspect sub-commands', help='Inspect sub-commands'
|
|
450
|
+
)
|
|
329
451
|
# Inspect Sub-command: inspect copyleft
|
|
330
|
-
p_copyleft = p_inspect_sub.add_parser(
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
p_copyleft.add_argument(
|
|
452
|
+
p_copyleft = p_inspect_sub.add_parser(
|
|
453
|
+
'copyleft', aliases=['cp'], description='Inspect for copyleft licenses', help='Inspect for copyleft licenses'
|
|
454
|
+
)
|
|
455
|
+
p_copyleft.add_argument(
|
|
456
|
+
'--include',
|
|
457
|
+
help='List of Copyleft licenses to append to the default list. Provide licenses as a comma-separated list.',
|
|
458
|
+
)
|
|
459
|
+
p_copyleft.add_argument(
|
|
460
|
+
'--exclude',
|
|
461
|
+
help='List of Copyleft licenses to remove from default list. Provide licenses as a comma-separated list.',
|
|
462
|
+
)
|
|
463
|
+
p_copyleft.add_argument(
|
|
464
|
+
'--explicit',
|
|
465
|
+
help='Explicit list of Copyleft licenses to consider. Provide licenses as a comma-separated list.s',
|
|
466
|
+
)
|
|
334
467
|
p_copyleft.set_defaults(func=inspect_copyleft)
|
|
335
468
|
|
|
336
469
|
# Inspect Sub-command: inspect undeclared
|
|
337
|
-
p_undeclared = p_inspect_sub.add_parser(
|
|
338
|
-
|
|
339
|
-
|
|
470
|
+
p_undeclared = p_inspect_sub.add_parser(
|
|
471
|
+
'undeclared',
|
|
472
|
+
aliases=['un'],
|
|
473
|
+
description='Inspect for undeclared components',
|
|
474
|
+
help='Inspect for undeclared components',
|
|
475
|
+
)
|
|
476
|
+
p_undeclared.add_argument(
|
|
477
|
+
'--sbom-format',
|
|
478
|
+
required=False,
|
|
479
|
+
choices=['legacy', 'settings'],
|
|
480
|
+
default='settings',
|
|
481
|
+
help='Sbom format for status output',
|
|
482
|
+
)
|
|
340
483
|
p_undeclared.set_defaults(func=inspect_undeclared)
|
|
341
484
|
|
|
342
485
|
for p in [p_copyleft, p_undeclared]:
|
|
343
486
|
p.add_argument('-i', '--input', nargs='?', help='Path to results file')
|
|
344
|
-
p.add_argument(
|
|
487
|
+
p.add_argument(
|
|
488
|
+
'-f',
|
|
489
|
+
'--format',
|
|
490
|
+
required=False,
|
|
491
|
+
choices=['json', 'md', 'jira_md'],
|
|
492
|
+
default='json',
|
|
493
|
+
help='Output format (default: json)',
|
|
494
|
+
)
|
|
345
495
|
p.add_argument('-o', '--output', type=str, help='Save details into a file')
|
|
346
496
|
p.add_argument('-s', '--status', type=str, help='Save summary data into Markdown file')
|
|
347
497
|
|
|
348
498
|
# Global Scan command options
|
|
349
499
|
for p in [p_scan]:
|
|
350
|
-
p.add_argument(
|
|
351
|
-
|
|
500
|
+
p.add_argument(
|
|
501
|
+
'--apiurl', type=str, help='SCANOSS API URL (optional - default: https://api.osskb.org/scan/direct)'
|
|
502
|
+
)
|
|
352
503
|
p.add_argument('--ignore-cert-errors', action='store_true', help='Ignore certificate errors')
|
|
353
504
|
|
|
354
505
|
# Global Scan/Fingerprint filter options
|
|
@@ -361,36 +512,74 @@ def setup_args() -> None:
|
|
|
361
512
|
p.add_argument('--skip-snippets', '-S', action='store_true', help='Skip the generation of snippets')
|
|
362
513
|
p.add_argument('--skip-extension', '-E', type=str, action='append', help='File Extension to skip.')
|
|
363
514
|
p.add_argument('--skip-folder', '-O', type=str, action='append', help='Folder to skip.')
|
|
364
|
-
p.add_argument(
|
|
365
|
-
|
|
515
|
+
p.add_argument(
|
|
516
|
+
'--skip-size',
|
|
517
|
+
'-Z',
|
|
518
|
+
type=int,
|
|
519
|
+
default=0,
|
|
520
|
+
help='Minimum file size to consider for fingerprinting (optional - default 0 bytes [unlimited])',
|
|
521
|
+
)
|
|
366
522
|
p.add_argument('--skip-md5', '-5', type=str, action='append', help='Skip files matching MD5.')
|
|
367
523
|
p.add_argument('--strip-hpsm', '-G', type=str, action='append', help='Strip HPSM string from WFP.')
|
|
368
524
|
p.add_argument('--strip-snippet', '-N', type=str, action='append', help='Strip Snippet ID string from WFP.')
|
|
369
525
|
|
|
370
526
|
# Global Scan/GRPC options
|
|
371
|
-
for p in [p_scan, c_crypto, c_vulns, c_search, c_versions, c_semgrep
|
|
372
|
-
p.add_argument(
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
527
|
+
for p in [p_scan, c_crypto, c_vulns, c_search, c_versions, c_semgrep]:
|
|
528
|
+
p.add_argument(
|
|
529
|
+
'--key', '-k', type=str, help='SCANOSS API Key token (optional - not required for default OSSKB URL)'
|
|
530
|
+
)
|
|
531
|
+
p.add_argument(
|
|
532
|
+
'--proxy',
|
|
533
|
+
type=str,
|
|
534
|
+
help='Proxy URL to use for connections (optional). '
|
|
535
|
+
'Can also use the environment variable "HTTPS_PROXY=<ip>:<port>" '
|
|
536
|
+
'and "grcp_proxy=<ip>:<port>" for gRPC',
|
|
537
|
+
)
|
|
538
|
+
p.add_argument(
|
|
539
|
+
'--pac',
|
|
540
|
+
type=str,
|
|
541
|
+
help='Proxy auto configuration (optional). Specify a file, http url or "auto" to try to discover it.',
|
|
542
|
+
)
|
|
543
|
+
p.add_argument(
|
|
544
|
+
'--ca-cert',
|
|
545
|
+
type=str,
|
|
546
|
+
help='Alternative certificate PEM file (optional). '
|
|
547
|
+
'Can also use the environment variable '
|
|
548
|
+
'"REQUESTS_CA_BUNDLE=/path/to/cacert.pem" and '
|
|
549
|
+
'"GRPC_DEFAULT_SSL_ROOTS_FILE_PATH=/path/to/cacert.pem" for gRPC',
|
|
550
|
+
)
|
|
383
551
|
|
|
384
552
|
# Global GRPC options
|
|
385
|
-
for p in [p_scan, c_crypto, c_vulns, c_search, c_versions, c_semgrep
|
|
386
|
-
p.add_argument(
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
553
|
+
for p in [p_scan, c_crypto, c_vulns, c_search, c_versions, c_semgrep]:
|
|
554
|
+
p.add_argument(
|
|
555
|
+
'--api2url', type=str, help='SCANOSS gRPC API 2.0 URL (optional - default: https://api.osskb.org)'
|
|
556
|
+
)
|
|
557
|
+
p.add_argument(
|
|
558
|
+
'--grpc-proxy',
|
|
559
|
+
type=str,
|
|
560
|
+
help='GRPC Proxy URL to use for connections (optional). '
|
|
561
|
+
'Can also use the environment variable "grcp_proxy=<ip>:<port>"',
|
|
562
|
+
)
|
|
390
563
|
|
|
391
564
|
# Help/Trace command options
|
|
392
|
-
for p in [
|
|
393
|
-
|
|
565
|
+
for p in [
|
|
566
|
+
p_scan,
|
|
567
|
+
p_wfp,
|
|
568
|
+
p_dep,
|
|
569
|
+
p_fc,
|
|
570
|
+
p_cnv,
|
|
571
|
+
p_c_loc,
|
|
572
|
+
p_c_dwnld,
|
|
573
|
+
p_p_proxy,
|
|
574
|
+
c_crypto,
|
|
575
|
+
c_vulns,
|
|
576
|
+
c_search,
|
|
577
|
+
c_versions,
|
|
578
|
+
c_semgrep,
|
|
579
|
+
p_results,
|
|
580
|
+
p_undeclared,
|
|
581
|
+
p_copyleft,
|
|
582
|
+
]:
|
|
394
583
|
p.add_argument('--debug', '-d', action='store_true', help='Enable debug messages')
|
|
395
584
|
p.add_argument('--trace', '-t', action='store_true', help='Enable trace messages, including API posts')
|
|
396
585
|
p.add_argument('--quiet', '-q', action='store_true', help='Enable quiet mode')
|
|
@@ -402,13 +591,17 @@ def setup_args() -> None:
|
|
|
402
591
|
if not args.subparser:
|
|
403
592
|
parser.print_help() # No sub command subcommand, print general help
|
|
404
593
|
exit(1)
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
594
|
+
elif (
|
|
595
|
+
args.subparser == 'utils'
|
|
596
|
+
or args.subparser == 'ut'
|
|
597
|
+
or args.subparser == 'component'
|
|
598
|
+
or args.subparser == 'comp'
|
|
599
|
+
or args.subparser == 'inspect'
|
|
600
|
+
or args.subparser == 'insp'
|
|
601
|
+
or args.subparser == 'ins'
|
|
602
|
+
) and not args.subparsercmd:
|
|
603
|
+
parser.parse_args([args.subparser, '--help']) # Force utils helps to be displayed
|
|
604
|
+
exit(1)
|
|
412
605
|
args.func(parser, args) # Execute the function associated with the sub-command
|
|
413
606
|
|
|
414
607
|
|
|
@@ -447,9 +640,13 @@ def file_count(parser, args):
|
|
|
447
640
|
scan_output = args.output
|
|
448
641
|
open(scan_output, 'w').close()
|
|
449
642
|
|
|
450
|
-
counter = FileCount(
|
|
451
|
-
|
|
452
|
-
|
|
643
|
+
counter = FileCount(
|
|
644
|
+
debug=args.debug,
|
|
645
|
+
quiet=args.quiet,
|
|
646
|
+
trace=args.trace,
|
|
647
|
+
scan_output=scan_output,
|
|
648
|
+
hidden_files_folders=args.all_hidden,
|
|
649
|
+
)
|
|
453
650
|
if not os.path.exists(args.scan_dir):
|
|
454
651
|
print_stderr(f'Error: Folder specified does not exist: {args.scan_dir}.')
|
|
455
652
|
exit(1)
|
|
@@ -475,7 +672,7 @@ def wfp(parser, args):
|
|
|
475
672
|
parser.parse_args([args.subparser, '-h'])
|
|
476
673
|
exit(1)
|
|
477
674
|
if args.strip_hpsm and not args.hpsm and not args.quiet:
|
|
478
|
-
print_stderr(
|
|
675
|
+
print_stderr('Warning: --strip-hpsm option supplied without enabling HPSM (--hpsm). Ignoring.')
|
|
479
676
|
scan_output: str = None
|
|
480
677
|
if args.output:
|
|
481
678
|
scan_output = args.output
|
|
@@ -492,13 +689,24 @@ def wfp(parser, args):
|
|
|
492
689
|
exit(1)
|
|
493
690
|
|
|
494
691
|
scan_options = 0 if args.skip_snippets else ScanType.SCAN_SNIPPETS.value # Skip snippet generation or not
|
|
495
|
-
scanner = Scanner(
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
692
|
+
scanner = Scanner(
|
|
693
|
+
debug=args.debug,
|
|
694
|
+
trace=args.trace,
|
|
695
|
+
quiet=args.quiet,
|
|
696
|
+
obfuscate=args.obfuscate,
|
|
697
|
+
scan_options=scan_options,
|
|
698
|
+
all_extensions=args.all_extensions,
|
|
699
|
+
all_folders=args.all_folders,
|
|
700
|
+
hidden_files_folders=args.all_hidden,
|
|
701
|
+
hpsm=args.hpsm,
|
|
702
|
+
skip_size=args.skip_size,
|
|
703
|
+
skip_extensions=args.skip_extension,
|
|
704
|
+
skip_folders=args.skip_folder,
|
|
705
|
+
skip_md5_ids=args.skip_md5,
|
|
706
|
+
strip_hpsm_ids=args.strip_hpsm,
|
|
707
|
+
strip_snippet_ids=args.strip_snippet,
|
|
708
|
+
scan_settings=scan_settings,
|
|
709
|
+
)
|
|
502
710
|
if args.stdin:
|
|
503
711
|
contents = sys.stdin.buffer.read()
|
|
504
712
|
scanner.wfp_contents(args.stdin, contents, scan_output)
|
|
@@ -539,11 +747,11 @@ def get_scan_options(args):
|
|
|
539
747
|
|
|
540
748
|
if args.debug:
|
|
541
749
|
if ScanType.SCAN_FILES.value & scan_options:
|
|
542
|
-
print_stderr(
|
|
750
|
+
print_stderr('Scan Files')
|
|
543
751
|
if ScanType.SCAN_SNIPPETS.value & scan_options:
|
|
544
|
-
print_stderr(
|
|
752
|
+
print_stderr('Scan Snippets')
|
|
545
753
|
if ScanType.SCAN_DEPENDENCIES.value & scan_options:
|
|
546
|
-
print_stderr(
|
|
754
|
+
print_stderr('Scan Dependencies')
|
|
547
755
|
if scan_options <= 0:
|
|
548
756
|
print_stderr(f'Error: No valid scan options configured: {scan_options}')
|
|
549
757
|
exit(1)
|
|
@@ -582,11 +790,17 @@ def scan(parser, args):
|
|
|
582
790
|
scan_settings = ScanossSettings(debug=args.debug, trace=args.trace, quiet=args.quiet)
|
|
583
791
|
try:
|
|
584
792
|
if args.identify:
|
|
585
|
-
scan_settings.load_json_file(args.identify, args.scan_dir).set_file_type('legacy').set_scan_type(
|
|
793
|
+
scan_settings.load_json_file(args.identify, args.scan_dir).set_file_type('legacy').set_scan_type(
|
|
794
|
+
'identify'
|
|
795
|
+
)
|
|
586
796
|
elif args.ignore:
|
|
587
|
-
scan_settings.load_json_file(args.ignore, args.scan_dir).set_file_type('legacy').set_scan_type(
|
|
797
|
+
scan_settings.load_json_file(args.ignore, args.scan_dir).set_file_type('legacy').set_scan_type(
|
|
798
|
+
'blacklist'
|
|
799
|
+
)
|
|
588
800
|
else:
|
|
589
|
-
scan_settings.load_json_file(args.settings, args.scan_dir).set_file_type('new').set_scan_type(
|
|
801
|
+
scan_settings.load_json_file(args.settings, args.scan_dir).set_file_type('new').set_scan_type(
|
|
802
|
+
'identify'
|
|
803
|
+
)
|
|
590
804
|
except ScanossSettingsError as e:
|
|
591
805
|
print_stderr(f'Error: {e}')
|
|
592
806
|
exit(1)
|
|
@@ -611,13 +825,13 @@ def scan(parser, args):
|
|
|
611
825
|
if args.skip_settings_file:
|
|
612
826
|
print_stderr('Skipping Settings file...')
|
|
613
827
|
if args.all_extensions:
|
|
614
|
-
print_stderr(
|
|
828
|
+
print_stderr('Scanning all file extensions/types...')
|
|
615
829
|
if args.all_folders:
|
|
616
|
-
print_stderr(
|
|
830
|
+
print_stderr('Scanning all folders...')
|
|
617
831
|
if args.all_hidden:
|
|
618
|
-
print_stderr(
|
|
832
|
+
print_stderr('Scanning all hidden files/folders...')
|
|
619
833
|
if args.skip_snippets:
|
|
620
|
-
print_stderr(
|
|
834
|
+
print_stderr('Skipping snippets...')
|
|
621
835
|
if args.post_size != 32:
|
|
622
836
|
print_stderr(f'Changing scanning POST size to: {args.post_size}k...')
|
|
623
837
|
if args.timeout != 180:
|
|
@@ -625,7 +839,7 @@ def scan(parser, args):
|
|
|
625
839
|
if args.retry != 5:
|
|
626
840
|
print_stderr(f'Changing scanning POST retry to: {args.retry}...')
|
|
627
841
|
if args.obfuscate:
|
|
628
|
-
print_stderr(
|
|
842
|
+
print_stderr('Obfuscating file fingerprints...')
|
|
629
843
|
if args.proxy:
|
|
630
844
|
print_stderr(f'Using Proxy {args.proxy}...')
|
|
631
845
|
if args.grpc_proxy:
|
|
@@ -635,7 +849,7 @@ def scan(parser, args):
|
|
|
635
849
|
if args.ca_cert:
|
|
636
850
|
print_stderr(f'Using Certificate {args.ca_cert}...')
|
|
637
851
|
if args.hpsm:
|
|
638
|
-
print_stderr(
|
|
852
|
+
print_stderr('Setting HPSM mode...')
|
|
639
853
|
if flags:
|
|
640
854
|
print_stderr(f'Using flags {flags}...')
|
|
641
855
|
elif not args.quiet:
|
|
@@ -654,8 +868,11 @@ def scan(parser, args):
|
|
|
654
868
|
scan_options = get_scan_options(args) # Figure out what scanning options we have
|
|
655
869
|
|
|
656
870
|
scanner = Scanner(
|
|
657
|
-
debug=args.debug,
|
|
658
|
-
|
|
871
|
+
debug=args.debug,
|
|
872
|
+
trace=args.trace,
|
|
873
|
+
quiet=args.quiet,
|
|
874
|
+
api_key=args.key,
|
|
875
|
+
url=args.apiurl,
|
|
659
876
|
scan_output=scan_output,
|
|
660
877
|
output_format=output_format,
|
|
661
878
|
flags=flags,
|
|
@@ -691,7 +908,7 @@ def scan(parser, args):
|
|
|
691
908
|
print_stderr(f'Error: Cannot specify WFP scanning if file/snippet options are disabled ({scan_options})')
|
|
692
909
|
exit(1)
|
|
693
910
|
if scanner.is_dependency_scan() and not args.dep:
|
|
694
|
-
print_stderr(
|
|
911
|
+
print_stderr('Error: Cannot specify WFP & Dependency scanning without a dependency file (--dep)')
|
|
695
912
|
exit(1)
|
|
696
913
|
scanner.scan_wfp_with_options(args.wfp, args.dep)
|
|
697
914
|
elif args.stdin:
|
|
@@ -699,21 +916,31 @@ def scan(parser, args):
|
|
|
699
916
|
if not scanner.scan_contents(args.stdin, contents):
|
|
700
917
|
exit(1)
|
|
701
918
|
elif args.files:
|
|
702
|
-
if not scanner.scan_files_with_options(
|
|
703
|
-
args.files, args.dep, scanner.winnowing.file_map
|
|
704
|
-
):
|
|
919
|
+
if not scanner.scan_files_with_options(args.files, args.dep, scanner.winnowing.file_map):
|
|
705
920
|
exit(1)
|
|
706
921
|
elif args.scan_dir:
|
|
707
922
|
if not os.path.exists(args.scan_dir):
|
|
708
923
|
print_stderr(f'Error: File or folder specified does not exist: {args.scan_dir}.')
|
|
709
924
|
exit(1)
|
|
710
925
|
if os.path.isdir(args.scan_dir):
|
|
711
|
-
if not scanner.scan_folder_with_options(
|
|
712
|
-
|
|
926
|
+
if not scanner.scan_folder_with_options(
|
|
927
|
+
args.scan_dir,
|
|
928
|
+
args.dep,
|
|
929
|
+
scanner.winnowing.file_map,
|
|
930
|
+
args.dep_scope,
|
|
931
|
+
args.dep_scope_inc,
|
|
932
|
+
args.dep_scope_exc,
|
|
933
|
+
):
|
|
713
934
|
exit(1)
|
|
714
935
|
elif os.path.isfile(args.scan_dir):
|
|
715
|
-
if not scanner.scan_file_with_options(
|
|
716
|
-
|
|
936
|
+
if not scanner.scan_file_with_options(
|
|
937
|
+
args.scan_dir,
|
|
938
|
+
args.dep,
|
|
939
|
+
scanner.winnowing.file_map,
|
|
940
|
+
args.dep_scope,
|
|
941
|
+
args.dep_scope_inc,
|
|
942
|
+
args.dep_scope_exc,
|
|
943
|
+
):
|
|
717
944
|
exit(1)
|
|
718
945
|
else:
|
|
719
946
|
print_stderr(f'Error: Path specified is neither a file or a folder: {args.scan_dir}.')
|
|
@@ -721,11 +948,12 @@ def scan(parser, args):
|
|
|
721
948
|
elif args.dep:
|
|
722
949
|
if not args.dependencies_only:
|
|
723
950
|
print_stderr(
|
|
724
|
-
|
|
951
|
+
'Error: No file or folder specified to scan. Please add --dependencies-only to decorate dependency file only.'
|
|
725
952
|
)
|
|
726
953
|
exit(1)
|
|
727
|
-
if not scanner.scan_folder_with_options(
|
|
728
|
-
|
|
954
|
+
if not scanner.scan_folder_with_options(
|
|
955
|
+
'.', args.dep, scanner.winnowing.file_map, args.dep_scope, args.dep_scope_inc, args.dep_scope_exc
|
|
956
|
+
):
|
|
729
957
|
exit(1)
|
|
730
958
|
else:
|
|
731
959
|
print_stderr('No action found to process')
|
|
@@ -754,9 +982,9 @@ def dependency(parser, args):
|
|
|
754
982
|
scan_output = args.output
|
|
755
983
|
open(scan_output, 'w').close()
|
|
756
984
|
|
|
757
|
-
sc_deps = ScancodeDeps(
|
|
758
|
-
|
|
759
|
-
|
|
985
|
+
sc_deps = ScancodeDeps(
|
|
986
|
+
debug=args.debug, quiet=args.quiet, trace=args.trace, sc_command=args.sc_command, timeout=args.sc_timeout
|
|
987
|
+
)
|
|
760
988
|
if not sc_deps.get_dependencies(what_to_scan=args.scan_dir, result_output=scan_output):
|
|
761
989
|
exit(1)
|
|
762
990
|
|
|
@@ -778,17 +1006,17 @@ def convert(parser, args):
|
|
|
778
1006
|
success = False
|
|
779
1007
|
if args.format == 'cyclonedx':
|
|
780
1008
|
if not args.quiet:
|
|
781
|
-
print_stderr(
|
|
1009
|
+
print_stderr('Producing CycloneDX report...')
|
|
782
1010
|
cdx = CycloneDx(debug=args.debug, output_file=args.output)
|
|
783
1011
|
success = cdx.produce_from_file(args.input)
|
|
784
1012
|
elif args.format == 'spdxlite':
|
|
785
1013
|
if not args.quiet:
|
|
786
|
-
print_stderr(
|
|
1014
|
+
print_stderr('Producing SPDX Lite report...')
|
|
787
1015
|
spdxlite = SpdxLite(debug=args.debug, output_file=args.output)
|
|
788
1016
|
success = spdxlite.produce_from_file(args.input)
|
|
789
1017
|
elif args.format == 'csv':
|
|
790
1018
|
if not args.quiet:
|
|
791
|
-
print_stderr(
|
|
1019
|
+
print_stderr('Producing CSV report...')
|
|
792
1020
|
csvo = CsvOutput(debug=args.debug, output_file=args.output)
|
|
793
1021
|
success = csvo.produce_from_file(args.input)
|
|
794
1022
|
else:
|
|
@@ -796,6 +1024,7 @@ def convert(parser, args):
|
|
|
796
1024
|
if not success:
|
|
797
1025
|
exit(1)
|
|
798
1026
|
|
|
1027
|
+
|
|
799
1028
|
def inspect_copyleft(parser, args):
|
|
800
1029
|
"""
|
|
801
1030
|
Run the "inspect" sub-command
|
|
@@ -820,12 +1049,22 @@ def inspect_copyleft(parser, args):
|
|
|
820
1049
|
status_output = args.status
|
|
821
1050
|
open(status_output, 'w').close()
|
|
822
1051
|
|
|
823
|
-
i_copyleft = Copyleft(
|
|
824
|
-
|
|
825
|
-
|
|
1052
|
+
i_copyleft = Copyleft(
|
|
1053
|
+
debug=args.debug,
|
|
1054
|
+
trace=args.trace,
|
|
1055
|
+
quiet=args.quiet,
|
|
1056
|
+
filepath=args.input,
|
|
1057
|
+
format_type=args.format,
|
|
1058
|
+
status=status_output,
|
|
1059
|
+
output=output,
|
|
1060
|
+
include=args.include,
|
|
1061
|
+
exclude=args.exclude,
|
|
1062
|
+
explicit=args.explicit,
|
|
1063
|
+
)
|
|
826
1064
|
status, _ = i_copyleft.run()
|
|
827
1065
|
sys.exit(status)
|
|
828
1066
|
|
|
1067
|
+
|
|
829
1068
|
def inspect_undeclared(parser, args):
|
|
830
1069
|
"""
|
|
831
1070
|
Run the "inspect" sub-command
|
|
@@ -849,20 +1088,30 @@ def inspect_undeclared(parser, args):
|
|
|
849
1088
|
if args.status:
|
|
850
1089
|
status_output = args.status
|
|
851
1090
|
open(status_output, 'w').close()
|
|
852
|
-
i_undeclared = UndeclaredComponent(
|
|
853
|
-
|
|
854
|
-
|
|
1091
|
+
i_undeclared = UndeclaredComponent(
|
|
1092
|
+
debug=args.debug,
|
|
1093
|
+
trace=args.trace,
|
|
1094
|
+
quiet=args.quiet,
|
|
1095
|
+
filepath=args.input,
|
|
1096
|
+
format_type=args.format,
|
|
1097
|
+
status=status_output,
|
|
1098
|
+
output=output,
|
|
1099
|
+
sbom_format=args.sbom_format,
|
|
1100
|
+
)
|
|
855
1101
|
status, _ = i_undeclared.run()
|
|
856
1102
|
sys.exit(status)
|
|
857
1103
|
|
|
1104
|
+
|
|
858
1105
|
def utils_certloc(*_):
|
|
859
1106
|
"""
|
|
860
1107
|
Run the "utils certloc" sub-command
|
|
861
1108
|
:param _: ignored/unused
|
|
862
1109
|
"""
|
|
863
1110
|
import certifi
|
|
1111
|
+
|
|
864
1112
|
print(f'CA Cert File: {certifi.where()}')
|
|
865
1113
|
|
|
1114
|
+
|
|
866
1115
|
def utils_cert_download(_, args):
|
|
867
1116
|
"""
|
|
868
1117
|
Run the "utils cert-download" sub-command
|
|
@@ -897,7 +1146,9 @@ def utils_cert_download(_, args):
|
|
|
897
1146
|
if not args.quiet:
|
|
898
1147
|
print_stderr(f'Certificate {index} - CN: {cn}')
|
|
899
1148
|
if sys.version_info[0] >= 3:
|
|
900
|
-
print(
|
|
1149
|
+
print(
|
|
1150
|
+
(crypto.dump_certificate(crypto.FILETYPE_PEM, cert).decode('utf-8')).strip(), file=file
|
|
1151
|
+
) # Print the downloaded PEM certificate
|
|
901
1152
|
else:
|
|
902
1153
|
print((crypto.dump_certificate(crypto.FILETYPE_PEM, cert)).strip(), file=file)
|
|
903
1154
|
except SSL.Error as e:
|
|
@@ -919,8 +1170,9 @@ def utils_pac_proxy(_, args):
|
|
|
919
1170
|
:param args: Parsed arguments
|
|
920
1171
|
"""
|
|
921
1172
|
from pypac.resolver import ProxyResolver
|
|
1173
|
+
|
|
922
1174
|
if not args.pac:
|
|
923
|
-
print_stderr(
|
|
1175
|
+
print_stderr('Error: No pac file option specified.')
|
|
924
1176
|
exit(1)
|
|
925
1177
|
pac_file = get_pac_file(args.pac)
|
|
926
1178
|
if pac_file is None:
|
|
@@ -974,9 +1226,18 @@ def comp_crypto(parser, args):
|
|
|
974
1226
|
print_stderr(f'Error: Certificate file does not exist: {args.ca_cert}.')
|
|
975
1227
|
exit(1)
|
|
976
1228
|
pac_file = get_pac_file(args.pac)
|
|
977
|
-
comps = Components(
|
|
978
|
-
|
|
979
|
-
|
|
1229
|
+
comps = Components(
|
|
1230
|
+
debug=args.debug,
|
|
1231
|
+
trace=args.trace,
|
|
1232
|
+
quiet=args.quiet,
|
|
1233
|
+
grpc_url=args.api2url,
|
|
1234
|
+
api_key=args.key,
|
|
1235
|
+
ca_cert=args.ca_cert,
|
|
1236
|
+
proxy=args.proxy,
|
|
1237
|
+
grpc_proxy=args.grpc_proxy,
|
|
1238
|
+
pac=pac_file,
|
|
1239
|
+
timeout=args.timeout,
|
|
1240
|
+
)
|
|
980
1241
|
if not comps.get_crypto_details(args.input, args.purl, args.output):
|
|
981
1242
|
exit(1)
|
|
982
1243
|
|
|
@@ -999,12 +1260,22 @@ def comp_vulns(parser, args):
|
|
|
999
1260
|
print_stderr(f'Error: Certificate file does not exist: {args.ca_cert}.')
|
|
1000
1261
|
exit(1)
|
|
1001
1262
|
pac_file = get_pac_file(args.pac)
|
|
1002
|
-
comps = Components(
|
|
1003
|
-
|
|
1004
|
-
|
|
1263
|
+
comps = Components(
|
|
1264
|
+
debug=args.debug,
|
|
1265
|
+
trace=args.trace,
|
|
1266
|
+
quiet=args.quiet,
|
|
1267
|
+
grpc_url=args.api2url,
|
|
1268
|
+
api_key=args.key,
|
|
1269
|
+
ca_cert=args.ca_cert,
|
|
1270
|
+
proxy=args.proxy,
|
|
1271
|
+
grpc_proxy=args.grpc_proxy,
|
|
1272
|
+
pac=pac_file,
|
|
1273
|
+
timeout=args.timeout,
|
|
1274
|
+
)
|
|
1005
1275
|
if not comps.get_vulnerabilities(args.input, args.purl, args.output):
|
|
1006
1276
|
exit(1)
|
|
1007
1277
|
|
|
1278
|
+
|
|
1008
1279
|
def comp_semgrep(parser, args):
|
|
1009
1280
|
"""
|
|
1010
1281
|
Run the "component semgrep" sub-command
|
|
@@ -1023,35 +1294,21 @@ def comp_semgrep(parser, args):
|
|
|
1023
1294
|
print_stderr(f'Error: Certificate file does not exist: {args.ca_cert}.')
|
|
1024
1295
|
exit(1)
|
|
1025
1296
|
pac_file = get_pac_file(args.pac)
|
|
1026
|
-
comps = Components(
|
|
1027
|
-
|
|
1028
|
-
|
|
1297
|
+
comps = Components(
|
|
1298
|
+
debug=args.debug,
|
|
1299
|
+
trace=args.trace,
|
|
1300
|
+
quiet=args.quiet,
|
|
1301
|
+
grpc_url=args.api2url,
|
|
1302
|
+
api_key=args.key,
|
|
1303
|
+
ca_cert=args.ca_cert,
|
|
1304
|
+
proxy=args.proxy,
|
|
1305
|
+
grpc_proxy=args.grpc_proxy,
|
|
1306
|
+
pac=pac_file,
|
|
1307
|
+
timeout=args.timeout,
|
|
1308
|
+
)
|
|
1029
1309
|
if not comps.get_semgrep_details(args.input, args.purl, args.output):
|
|
1030
1310
|
exit(1)
|
|
1031
1311
|
|
|
1032
|
-
def comp_provenance(parser, args):
|
|
1033
|
-
"""
|
|
1034
|
-
Run the "component provenance" sub-command
|
|
1035
|
-
Parameters
|
|
1036
|
-
----------
|
|
1037
|
-
parser: ArgumentParser
|
|
1038
|
-
command line parser object
|
|
1039
|
-
args: Namespace
|
|
1040
|
-
Parsed arguments
|
|
1041
|
-
"""
|
|
1042
|
-
if (not args.purl and not args.input) or (args.purl and args.input):
|
|
1043
|
-
print_stderr('Please specify an input file or purl to decorate (--purl or --input)')
|
|
1044
|
-
parser.parse_args([args.subparser, args.subparsercmd, '-h'])
|
|
1045
|
-
exit(1)
|
|
1046
|
-
if args.ca_cert and not os.path.exists(args.ca_cert):
|
|
1047
|
-
print_stderr(f'Error: Certificate file does not exist: {args.ca_cert}.')
|
|
1048
|
-
exit(1)
|
|
1049
|
-
pac_file = get_pac_file(args.pac)
|
|
1050
|
-
comps = Components(debug=args.debug, trace=args.trace, quiet=args.quiet, grpc_url=args.api2url, api_key=args.key,
|
|
1051
|
-
ca_cert=args.ca_cert, proxy=args.proxy, grpc_proxy=args.grpc_proxy, pac=pac_file,
|
|
1052
|
-
timeout=args.timeout)
|
|
1053
|
-
if not comps.get_provenance_details(args.input, args.purl, args.output):
|
|
1054
|
-
exit(1)
|
|
1055
1312
|
|
|
1056
1313
|
def comp_search(parser, args):
|
|
1057
1314
|
"""
|
|
@@ -1063,8 +1320,9 @@ def comp_search(parser, args):
|
|
|
1063
1320
|
args: Namespace
|
|
1064
1321
|
Parsed arguments
|
|
1065
1322
|
"""
|
|
1066
|
-
if (
|
|
1067
|
-
|
|
1323
|
+
if (not args.input and not args.search and not args.vendor and not args.comp) or (
|
|
1324
|
+
args.input and (args.search or args.vendor or args.comp)
|
|
1325
|
+
):
|
|
1068
1326
|
print_stderr('Please specify an input file or search terms (--input or --search, or --vendor or --comp.)')
|
|
1069
1327
|
parser.parse_args([args.subparser, args.subparsercmd, '-h'])
|
|
1070
1328
|
exit(1)
|
|
@@ -1073,12 +1331,28 @@ def comp_search(parser, args):
|
|
|
1073
1331
|
print_stderr(f'Error: Certificate file does not exist: {args.ca_cert}.')
|
|
1074
1332
|
exit(1)
|
|
1075
1333
|
pac_file = get_pac_file(args.pac)
|
|
1076
|
-
comps = Components(
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1334
|
+
comps = Components(
|
|
1335
|
+
debug=args.debug,
|
|
1336
|
+
trace=args.trace,
|
|
1337
|
+
quiet=args.quiet,
|
|
1338
|
+
grpc_url=args.api2url,
|
|
1339
|
+
api_key=args.key,
|
|
1340
|
+
ca_cert=args.ca_cert,
|
|
1341
|
+
proxy=args.proxy,
|
|
1342
|
+
grpc_proxy=args.grpc_proxy,
|
|
1343
|
+
pac=pac_file,
|
|
1344
|
+
timeout=args.timeout,
|
|
1345
|
+
)
|
|
1346
|
+
if not comps.search_components(
|
|
1347
|
+
args.output,
|
|
1348
|
+
json_file=args.input,
|
|
1349
|
+
search=args.search,
|
|
1350
|
+
vendor=args.vendor,
|
|
1351
|
+
comp=args.comp,
|
|
1352
|
+
package=args.package,
|
|
1353
|
+
limit=args.limit,
|
|
1354
|
+
offset=args.offset,
|
|
1355
|
+
):
|
|
1082
1356
|
exit(1)
|
|
1083
1357
|
|
|
1084
1358
|
|
|
@@ -1101,9 +1375,18 @@ def comp_versions(parser, args):
|
|
|
1101
1375
|
print_stderr(f'Error: Certificate file does not exist: {args.ca_cert}.')
|
|
1102
1376
|
exit(1)
|
|
1103
1377
|
pac_file = get_pac_file(args.pac)
|
|
1104
|
-
comps = Components(
|
|
1105
|
-
|
|
1106
|
-
|
|
1378
|
+
comps = Components(
|
|
1379
|
+
debug=args.debug,
|
|
1380
|
+
trace=args.trace,
|
|
1381
|
+
quiet=args.quiet,
|
|
1382
|
+
grpc_url=args.api2url,
|
|
1383
|
+
api_key=args.key,
|
|
1384
|
+
ca_cert=args.ca_cert,
|
|
1385
|
+
proxy=args.proxy,
|
|
1386
|
+
grpc_proxy=args.grpc_proxy,
|
|
1387
|
+
pac=pac_file,
|
|
1388
|
+
timeout=args.timeout,
|
|
1389
|
+
)
|
|
1107
1390
|
if not comps.get_component_versions(args.output, json_file=args.input, purl=args.purl, limit=args.limit):
|
|
1108
1391
|
exit(1)
|
|
1109
1392
|
|
|
@@ -1120,13 +1403,13 @@ def results(parser, args):
|
|
|
1120
1403
|
"""
|
|
1121
1404
|
if not args.filepath:
|
|
1122
1405
|
print_stderr('ERROR: Please specify a file containing the results')
|
|
1123
|
-
parser.parse_args([args.subparser,
|
|
1406
|
+
parser.parse_args([args.subparser, '-h'])
|
|
1124
1407
|
exit(1)
|
|
1125
1408
|
|
|
1126
1409
|
file_path = Path(args.filepath).resolve()
|
|
1127
1410
|
|
|
1128
1411
|
if not file_path.is_file():
|
|
1129
|
-
print_stderr(f
|
|
1412
|
+
print_stderr(f'The specified file {args.filepath} does not exist')
|
|
1130
1413
|
exit(1)
|
|
1131
1414
|
|
|
1132
1415
|
results = Results(
|
|
@@ -1155,5 +1438,5 @@ def main():
|
|
|
1155
1438
|
setup_args()
|
|
1156
1439
|
|
|
1157
1440
|
|
|
1158
|
-
if __name__ ==
|
|
1441
|
+
if __name__ == '__main__':
|
|
1159
1442
|
main()
|