fosslight-util 2.0.0__py3-none-any.whl → 2.1.28__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.
- fosslight_util/_get_downloadable_url.py +429 -63
- fosslight_util/compare_yaml.py +3 -1
- fosslight_util/constant.py +5 -1
- fosslight_util/correct.py +4 -6
- fosslight_util/download.py +216 -77
- fosslight_util/exclude.py +65 -0
- fosslight_util/help.py +14 -3
- fosslight_util/oss_item.py +24 -3
- fosslight_util/output_format.py +100 -18
- fosslight_util/set_log.py +8 -2
- fosslight_util/write_cyclonedx.py +210 -0
- fosslight_util/write_excel.py +5 -1
- fosslight_util/write_scancodejson.py +31 -14
- fosslight_util/write_spdx.py +161 -109
- {fosslight_util-2.0.0.dist-info → fosslight_util-2.1.28.dist-info}/METADATA +24 -21
- fosslight_util-2.1.28.dist-info/RECORD +32 -0
- {fosslight_util-2.0.0.dist-info → fosslight_util-2.1.28.dist-info}/WHEEL +1 -1
- {fosslight_util-2.0.0.dist-info → fosslight_util-2.1.28.dist-info}/entry_points.txt +0 -1
- fosslight_util/convert_excel_to_yaml.py +0 -69
- fosslight_util-2.0.0.dist-info/RECORD +0 -31
- {fosslight_util-2.0.0.dist-info → fosslight_util-2.1.28.dist-info/licenses}/LICENSE +0 -0
- {fosslight_util-2.0.0.dist-info → fosslight_util-2.1.28.dist-info}/top_level.txt +0 -0
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
import logging
|
|
6
6
|
import re
|
|
7
7
|
import requests
|
|
8
|
-
from npm.bindings import npm_run
|
|
9
8
|
from lastversion import latest
|
|
10
9
|
from bs4 import BeautifulSoup
|
|
11
10
|
from urllib.request import urlopen
|
|
@@ -14,120 +13,480 @@ import fosslight_util.constant as constant
|
|
|
14
13
|
logger = logging.getLogger(constant.LOGGER_NAME)
|
|
15
14
|
|
|
16
15
|
|
|
17
|
-
def
|
|
16
|
+
def version_exists(pkg_type, origin_name, version):
|
|
17
|
+
try:
|
|
18
|
+
if pkg_type in ['npm', 'npm2']:
|
|
19
|
+
r = requests.get(f"https://registry.npmjs.org/{origin_name}", timeout=5)
|
|
20
|
+
if r.status_code == 200:
|
|
21
|
+
data = r.json()
|
|
22
|
+
return version in data.get('versions', {})
|
|
23
|
+
elif pkg_type == 'pypi':
|
|
24
|
+
r = requests.get(f"https://pypi.org/pypi/{origin_name}/{version}/json", timeout=5)
|
|
25
|
+
return r.status_code == 200
|
|
26
|
+
elif pkg_type == 'maven':
|
|
27
|
+
r = requests.get(f'https://api.deps.dev/v3alpha/systems/maven/packages/{origin_name}', timeout=5)
|
|
28
|
+
if r.status_code == 200:
|
|
29
|
+
versions = r.json().get('versions', [])
|
|
30
|
+
for vobj in versions:
|
|
31
|
+
vkey = vobj.get('versionKey') or {}
|
|
32
|
+
if vkey.get('version') == version:
|
|
33
|
+
return True
|
|
34
|
+
return False
|
|
35
|
+
elif pkg_type == 'pub':
|
|
36
|
+
r = requests.get(f'https://pub.dev/api/packages/{origin_name}', timeout=5)
|
|
37
|
+
if r.status_code == 200:
|
|
38
|
+
versions = r.json().get('versions', [])
|
|
39
|
+
return any(v.get('version') == version for v in versions if isinstance(v, dict))
|
|
40
|
+
elif pkg_type == 'go':
|
|
41
|
+
if not version.startswith('v'):
|
|
42
|
+
version = f'v{version}'
|
|
43
|
+
r = requests.get(f'https://proxy.golang.org/{origin_name}/@v/list', timeout=5)
|
|
44
|
+
if r.status_code == 200:
|
|
45
|
+
listed = r.text.splitlines()
|
|
46
|
+
return version in listed
|
|
47
|
+
except Exception as e:
|
|
48
|
+
logger.info(f'version_exists check failed ({pkg_type}:{origin_name}:{version}) {e}')
|
|
49
|
+
return True
|
|
50
|
+
return False
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def extract_name_version_from_link(link, checkout_version):
|
|
18
54
|
oss_name = ""
|
|
19
55
|
oss_version = ""
|
|
56
|
+
matched = False
|
|
57
|
+
direct_maven = False
|
|
58
|
+
|
|
20
59
|
if link.startswith("www."):
|
|
21
60
|
link = link.replace("www.", "https://www.", 1)
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
61
|
+
|
|
62
|
+
if (not matched and (
|
|
63
|
+
link.startswith('https://repo1.maven.org/maven2/') or
|
|
64
|
+
link.startswith('https://dl.google.com/android/maven2/')
|
|
65
|
+
)):
|
|
66
|
+
parsed = parse_direct_maven_url(link)
|
|
67
|
+
if parsed:
|
|
68
|
+
origin_name, parsed_version = parsed
|
|
69
|
+
oss_name = origin_name # groupId:artifactId
|
|
70
|
+
oss_version = parsed_version or ""
|
|
71
|
+
matched = True
|
|
72
|
+
direct_maven = True
|
|
73
|
+
pkg_type = 'maven'
|
|
74
|
+
|
|
75
|
+
for direct_key in ["maven_repo1", "maven_google"]:
|
|
76
|
+
pattern = constant.PKG_PATTERN.get(direct_key)
|
|
77
|
+
if pattern and re.match(pattern, link):
|
|
78
|
+
parsed = parse_direct_maven_url(link)
|
|
79
|
+
if parsed:
|
|
80
|
+
origin_name, parsed_version = parsed
|
|
81
|
+
oss_name = origin_name
|
|
82
|
+
oss_version = parsed_version or ""
|
|
83
|
+
matched = True
|
|
84
|
+
direct_maven = True
|
|
85
|
+
pkg_type = 'maven'
|
|
86
|
+
break
|
|
87
|
+
|
|
88
|
+
if not matched:
|
|
89
|
+
for key, value in constant.PKG_PATTERN.items():
|
|
90
|
+
if key in ["maven_repo1", "maven_google"]:
|
|
91
|
+
continue
|
|
92
|
+
p = re.compile(value)
|
|
93
|
+
match = p.match(link)
|
|
94
|
+
if match:
|
|
95
|
+
try:
|
|
96
|
+
pkg_type = key
|
|
97
|
+
origin_name = match.group(1)
|
|
98
|
+
if (key == "pypi") or (key == "pypi2"):
|
|
99
|
+
oss_name = f"pypi:{origin_name}"
|
|
100
|
+
oss_name = re.sub(r"[-_.]+", "-", oss_name)
|
|
101
|
+
oss_version = match.group(2)
|
|
102
|
+
pkg_type = 'pypi'
|
|
103
|
+
elif key == "maven":
|
|
104
|
+
artifact = match.group(2)
|
|
105
|
+
oss_name = f"{origin_name}:{artifact}"
|
|
106
|
+
origin_name = oss_name
|
|
107
|
+
oss_version = match.group(3)
|
|
108
|
+
elif key == "npm" or key == "npm2":
|
|
109
|
+
oss_name = f"npm:{origin_name}"
|
|
110
|
+
oss_version = match.group(2)
|
|
111
|
+
elif key == "pub":
|
|
112
|
+
oss_name = f"pub:{origin_name}"
|
|
113
|
+
oss_version = match.group(2)
|
|
114
|
+
elif key == "cocoapods":
|
|
115
|
+
oss_name = f"cocoapods:{origin_name}"
|
|
116
|
+
elif key == "go":
|
|
117
|
+
if origin_name.endswith('/'):
|
|
118
|
+
origin_name = origin_name[:-1]
|
|
119
|
+
oss_name = f"go:{origin_name}"
|
|
120
|
+
oss_version = match.group(2)
|
|
121
|
+
elif key == "cargo":
|
|
122
|
+
oss_name = f"cargo:{origin_name}"
|
|
123
|
+
oss_version = match.group(2)
|
|
124
|
+
except Exception as ex:
|
|
125
|
+
logger.info(f"extract_name_version_from_link {key}:{ex}")
|
|
126
|
+
if oss_name:
|
|
127
|
+
matched = True
|
|
128
|
+
break
|
|
129
|
+
|
|
130
|
+
if not matched:
|
|
131
|
+
return "", "", link, ""
|
|
132
|
+
else:
|
|
133
|
+
need_latest = False
|
|
134
|
+
if not oss_version and checkout_version:
|
|
135
|
+
oss_version = checkout_version.strip()
|
|
136
|
+
if pkg_type in ["pypi", "maven", "npm", "npm2", "pub", "go"]:
|
|
137
|
+
if oss_version:
|
|
138
|
+
try:
|
|
139
|
+
if not version_exists(pkg_type, origin_name, oss_version):
|
|
140
|
+
logger.info(f'Version {oss_version} not found for {oss_name}; will attempt latest fallback')
|
|
141
|
+
need_latest = True
|
|
142
|
+
except Exception as e:
|
|
143
|
+
logger.info(f'Version validation failed ({oss_name}:{oss_version}) {e}; will attempt latest fallback')
|
|
144
|
+
need_latest = True
|
|
145
|
+
else:
|
|
146
|
+
need_latest = True
|
|
147
|
+
if need_latest:
|
|
148
|
+
latest_ver = get_latest_package_version(link, pkg_type, origin_name)
|
|
149
|
+
if latest_ver:
|
|
150
|
+
if oss_version and latest_ver != oss_version:
|
|
151
|
+
logger.info(f'Fallback to latest version {latest_ver} (previous invalid: {oss_version})')
|
|
152
|
+
elif not oss_version:
|
|
153
|
+
logger.info(f'Using latest version {latest_ver} (no version detected)')
|
|
154
|
+
oss_version = latest_ver
|
|
155
|
+
|
|
156
|
+
try:
|
|
157
|
+
if oss_version:
|
|
158
|
+
if pkg_type == 'maven' and direct_maven:
|
|
159
|
+
# Skip if oss_name malformed
|
|
160
|
+
if ':' in oss_name:
|
|
161
|
+
parts = oss_name.split(':', 1)
|
|
162
|
+
group_id, artifact_id = parts[0], parts[1]
|
|
163
|
+
group_path = group_id.replace('.', '/')
|
|
164
|
+
if (
|
|
165
|
+
link.startswith('https://repo1.maven.org/maven2/') or
|
|
166
|
+
link.startswith('http://repo1.maven.org/maven2/')
|
|
167
|
+
):
|
|
168
|
+
if not re.search(r'/\d[^/]*/*$', link.rstrip('/')):
|
|
169
|
+
link = (
|
|
170
|
+
f'https://repo1.maven.org/maven2/{group_path}/'
|
|
171
|
+
f'{artifact_id}/{oss_version}'
|
|
172
|
+
)
|
|
173
|
+
elif (
|
|
174
|
+
link.startswith('https://dl.google.com/android/maven2/') or
|
|
175
|
+
link.startswith('http://dl.google.com/android/maven2/')
|
|
176
|
+
):
|
|
177
|
+
if not re.search(r'/\d[^/]*/*$', link.rstrip('/')):
|
|
178
|
+
link = (
|
|
179
|
+
f'https://dl.google.com/android/maven2/{group_path}/'
|
|
180
|
+
f'{artifact_id}/{oss_version}/{artifact_id}-{oss_version}-sources.jar'
|
|
181
|
+
)
|
|
182
|
+
else:
|
|
183
|
+
logger.debug(f'Skip maven normalization due to invalid oss_name: {oss_name}')
|
|
184
|
+
else:
|
|
185
|
+
link = get_new_link_with_version(link, pkg_type, origin_name, oss_version)
|
|
186
|
+
except Exception as _e:
|
|
187
|
+
logger.info(f'Failed to build versioned link for {oss_name or origin_name}:{oss_version} {_e}')
|
|
188
|
+
|
|
189
|
+
return oss_name, oss_version, link, pkg_type
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def parse_direct_maven_url(url):
|
|
193
|
+
try:
|
|
194
|
+
clean_url = url.replace('https://', '').replace('http://', '')
|
|
195
|
+
if clean_url.startswith('repo1.maven.org/maven2/'):
|
|
196
|
+
base_path = clean_url[len('repo1.maven.org/maven2/'):]
|
|
197
|
+
elif clean_url.startswith('dl.google.com/android/maven2/'):
|
|
198
|
+
base_path = clean_url[len('dl.google.com/android/maven2/'):]
|
|
199
|
+
else:
|
|
200
|
+
return None
|
|
201
|
+
|
|
202
|
+
base_path = base_path.rstrip('/')
|
|
203
|
+
# Strip file name if ends with known artifact extension.
|
|
204
|
+
if any(base_path.endswith(ext) for ext in ['.jar', '.pom', '.aar']):
|
|
205
|
+
base_path = '/'.join(base_path.split('/')[:-1])
|
|
206
|
+
|
|
207
|
+
parts = base_path.split('/')
|
|
208
|
+
if len(parts) < 2:
|
|
209
|
+
return None
|
|
210
|
+
|
|
211
|
+
version = None
|
|
212
|
+
artifact_id = None
|
|
213
|
+
if len(parts) >= 3:
|
|
214
|
+
potential_version = parts[-1]
|
|
215
|
+
potential_artifact = parts[-2]
|
|
216
|
+
if re.search(r'\d', potential_version):
|
|
217
|
+
version = potential_version
|
|
218
|
+
artifact_id = potential_artifact
|
|
219
|
+
group_parts = parts[:-2]
|
|
220
|
+
else:
|
|
221
|
+
artifact_id = parts[-1]
|
|
222
|
+
group_parts = parts[:-1]
|
|
223
|
+
else:
|
|
224
|
+
artifact_id = parts[-1]
|
|
225
|
+
group_parts = parts[:-1]
|
|
226
|
+
|
|
227
|
+
group_id = '.'.join(group_parts)
|
|
228
|
+
if not group_id or not artifact_id:
|
|
229
|
+
return None
|
|
230
|
+
|
|
231
|
+
maven_name = f"{group_id}:{artifact_id}"
|
|
232
|
+
return maven_name, version
|
|
233
|
+
except Exception as e:
|
|
234
|
+
logger.debug(f'Failed to parse direct Maven URL {url}: {e}')
|
|
235
|
+
return None
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def get_new_link_with_version(link, pkg_type, oss_name, oss_version):
|
|
239
|
+
if pkg_type == "pypi":
|
|
240
|
+
link = f'https://pypi.org/project/{oss_name}/{oss_version}'
|
|
241
|
+
elif pkg_type == "maven":
|
|
242
|
+
oss_name = oss_name.replace(':', '/')
|
|
243
|
+
link = f'https://mvnrepository.com/artifact/{oss_name}/{oss_version}'
|
|
244
|
+
elif pkg_type == "npm" or pkg_type == "npm2":
|
|
245
|
+
link = f'https://www.npmjs.com/package/{oss_name}/v/{oss_version}'
|
|
246
|
+
elif pkg_type == "pub":
|
|
247
|
+
link = f'https://pub.dev/packages/{oss_name}/versions/{oss_version}'
|
|
248
|
+
elif pkg_type == "go":
|
|
249
|
+
if not oss_version.startswith('v'):
|
|
250
|
+
oss_version = f'v{oss_version}'
|
|
251
|
+
link = f'https://pkg.go.dev/{oss_name}@{oss_version}'
|
|
252
|
+
elif pkg_type == "cargo":
|
|
253
|
+
link = f'https://crates.io/crates/{oss_name}/{oss_version}'
|
|
254
|
+
return link
|
|
53
255
|
|
|
54
256
|
|
|
55
257
|
def get_latest_package_version(link, pkg_type, oss_name):
|
|
56
258
|
find_version = ''
|
|
57
|
-
link_with_version = link
|
|
58
259
|
|
|
59
260
|
try:
|
|
60
261
|
if pkg_type in ['npm', 'npm2']:
|
|
61
|
-
|
|
62
|
-
if
|
|
63
|
-
find_version =
|
|
64
|
-
link_with_version = f'https://www.npmjs.com/package/{oss_name}/v/{find_version}'
|
|
262
|
+
npm_response = requests.get(f"https://registry.npmjs.org/{oss_name}")
|
|
263
|
+
if npm_response.status_code == 200:
|
|
264
|
+
find_version = npm_response.json().get("dist-tags", {}).get("latest")
|
|
65
265
|
elif pkg_type == 'pypi':
|
|
66
266
|
find_version = str(latest(oss_name, at='pip', output_format='version', pre_ok=True))
|
|
67
|
-
link_with_version = f'https://pypi.org/project/{oss_name}/{find_version}'
|
|
68
267
|
elif pkg_type == 'maven':
|
|
69
268
|
maven_response = requests.get(f'https://api.deps.dev/v3alpha/systems/maven/packages/{oss_name}')
|
|
70
269
|
if maven_response.status_code == 200:
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
270
|
+
versions = maven_response.json().get('versions', [])
|
|
271
|
+
if versions:
|
|
272
|
+
# Some version entries may miss publishedAt; fallback to semantic version ordering.
|
|
273
|
+
def sem_key(vstr: str):
|
|
274
|
+
# Parse semantic version with optional prerelease label
|
|
275
|
+
# Examples: 1.9.0, 1.10.0-alpha, 2.0.0-rc
|
|
276
|
+
m = re.match(r'^(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:[-.]([A-Za-z0-9]+))?$', vstr)
|
|
277
|
+
if not m:
|
|
278
|
+
return (0, 0, 0, 999)
|
|
279
|
+
major = int(m.group(1) or 0)
|
|
280
|
+
minor = int(m.group(2) or 0)
|
|
281
|
+
patch = int(m.group(3) or 0)
|
|
282
|
+
label = (m.group(4) or '').lower()
|
|
283
|
+
# Assign label weights: stable > rc > beta > alpha
|
|
284
|
+
label_weight_map = {
|
|
285
|
+
'alpha': -3,
|
|
286
|
+
'beta': -2,
|
|
287
|
+
'rc': -1
|
|
288
|
+
}
|
|
289
|
+
weight = label_weight_map.get(label, 0 if label == '' else -4)
|
|
290
|
+
return (major, minor, patch, weight)
|
|
291
|
+
|
|
292
|
+
with_pub = [v for v in versions if v.get('publishedAt')]
|
|
293
|
+
if with_pub:
|
|
294
|
+
cand = max(with_pub, key=lambda v: v.get('publishedAt'))
|
|
295
|
+
else:
|
|
296
|
+
decorated = []
|
|
297
|
+
for v in versions:
|
|
298
|
+
vkey = v.get('versionKey', {})
|
|
299
|
+
ver = vkey.get('version', '')
|
|
300
|
+
if ver:
|
|
301
|
+
decorated.append((sem_key(ver), ver, v))
|
|
302
|
+
if decorated:
|
|
303
|
+
decorated.sort(key=lambda t: t[0])
|
|
304
|
+
stable_candidates = [t for t in decorated if t[0][3] == 0]
|
|
305
|
+
if stable_candidates:
|
|
306
|
+
cand = stable_candidates[-1][2]
|
|
307
|
+
else:
|
|
308
|
+
cand = decorated[-1][2]
|
|
309
|
+
else:
|
|
310
|
+
cand = versions[-1]
|
|
311
|
+
find_version = cand.get('versionKey', {}).get('version', '')
|
|
74
312
|
elif pkg_type == 'pub':
|
|
75
313
|
pub_response = requests.get(f'https://pub.dev/api/packages/{oss_name}')
|
|
76
314
|
if pub_response.status_code == 200:
|
|
77
315
|
find_version = pub_response.json().get('latest').get('version')
|
|
78
|
-
|
|
316
|
+
elif pkg_type == 'go':
|
|
317
|
+
go_response = requests.get(f'https://proxy.golang.org/{oss_name}/@latest')
|
|
318
|
+
if go_response.status_code == 200:
|
|
319
|
+
find_version = go_response.json().get('Version')
|
|
320
|
+
if find_version.startswith('v'):
|
|
321
|
+
find_version = find_version[1:]
|
|
79
322
|
except Exception as e:
|
|
80
|
-
logger.
|
|
81
|
-
return find_version
|
|
323
|
+
logger.info(f'Fail to get latest package version({link}:{e})')
|
|
324
|
+
return find_version
|
|
82
325
|
|
|
83
326
|
|
|
84
|
-
def get_downloadable_url(link):
|
|
327
|
+
def get_downloadable_url(link, checkout_version):
|
|
85
328
|
|
|
86
329
|
ret = False
|
|
87
330
|
result_link = link
|
|
88
331
|
|
|
89
|
-
oss_name, oss_version, new_link, pkg_type = extract_name_version_from_link(link)
|
|
332
|
+
oss_name, oss_version, new_link, pkg_type = extract_name_version_from_link(link, checkout_version)
|
|
90
333
|
new_link = new_link.replace('http://', '')
|
|
91
334
|
new_link = new_link.replace('https://', '')
|
|
92
335
|
|
|
93
336
|
if pkg_type == "pypi":
|
|
94
337
|
ret, result_link = get_download_location_for_pypi(new_link)
|
|
95
|
-
elif pkg_type == "maven" or new_link.startswith('repo1.maven.org/'):
|
|
338
|
+
elif pkg_type == "maven" or new_link.startswith('repo1.maven.org/') or new_link.startswith('dl.google.com/android/maven2/'):
|
|
96
339
|
ret, result_link = get_download_location_for_maven(new_link)
|
|
97
340
|
elif (pkg_type in ["npm", "npm2"]) or new_link.startswith('registry.npmjs.org/'):
|
|
98
341
|
ret, result_link = get_download_location_for_npm(new_link)
|
|
99
342
|
elif pkg_type == "pub":
|
|
100
343
|
ret, result_link = get_download_location_for_pub(new_link)
|
|
344
|
+
elif pkg_type == "go":
|
|
345
|
+
ret, result_link = get_download_location_for_go(new_link)
|
|
346
|
+
elif pkg_type == "cargo":
|
|
347
|
+
ret, result_link = get_download_location_for_cargo(new_link)
|
|
348
|
+
return ret, result_link, oss_name, oss_version, pkg_type
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
def get_download_location_for_cargo(link):
|
|
352
|
+
# get the url for downloading source file: https://crates.io/api/v1/crates/<name>/<version>/download
|
|
353
|
+
ret = False
|
|
354
|
+
new_link = ''
|
|
355
|
+
host = 'https://crates.io/api/v1/crates'
|
|
356
|
+
|
|
357
|
+
try:
|
|
358
|
+
dn_loc_re = re.findall(r'crates.io\/crates\/([^\/]+)\/?([^\/]*)', link)
|
|
359
|
+
if dn_loc_re:
|
|
360
|
+
oss_name = dn_loc_re[0][0]
|
|
361
|
+
oss_version = dn_loc_re[0][1]
|
|
362
|
+
|
|
363
|
+
new_link = f'{host}/{oss_name}/{oss_version}/download'
|
|
364
|
+
res = urlopen(new_link)
|
|
365
|
+
if res.getcode() == 200:
|
|
366
|
+
ret = True
|
|
367
|
+
else:
|
|
368
|
+
logger.warning(f'Cannot find the valid link for cargo (url:{new_link}')
|
|
369
|
+
except Exception as error:
|
|
370
|
+
ret = False
|
|
371
|
+
logger.warning(f'Cannot find the link for cargo (url:{link}({(new_link)})): {error}')
|
|
372
|
+
|
|
373
|
+
return ret, new_link
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def get_download_location_for_go(link):
|
|
377
|
+
# get the url for downloading source file: https://proxy.golang.org/<module>/@v/VERSION.zip
|
|
378
|
+
ret = False
|
|
379
|
+
new_link = ''
|
|
380
|
+
host = 'https://proxy.golang.org'
|
|
101
381
|
|
|
102
|
-
|
|
382
|
+
try:
|
|
383
|
+
dn_loc_re = re.findall(r'pkg.go.dev\/([^\@]+)\@?([^\/]*)', link)
|
|
384
|
+
if dn_loc_re:
|
|
385
|
+
oss_name = dn_loc_re[0][0]
|
|
386
|
+
if oss_name.endswith('/'):
|
|
387
|
+
oss_name = oss_name[:-1]
|
|
388
|
+
oss_version = dn_loc_re[0][1]
|
|
389
|
+
|
|
390
|
+
new_link = f'{host}/{oss_name}/@v/{oss_version}.zip'
|
|
391
|
+
try:
|
|
392
|
+
res = urlopen(new_link)
|
|
393
|
+
if res.getcode() == 200:
|
|
394
|
+
ret = True
|
|
395
|
+
else:
|
|
396
|
+
logger.warning(f'Cannot find the valid link for go (url:{new_link}')
|
|
397
|
+
except Exception as e:
|
|
398
|
+
logger.warning(f'Fail to find the valid link for go (url:{new_link}: {e}')
|
|
399
|
+
except Exception as error:
|
|
400
|
+
ret = False
|
|
401
|
+
logger.warning(f'Cannot find the link for go (url:{link}({(new_link)})): {error}')
|
|
402
|
+
|
|
403
|
+
return ret, new_link
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def get_available_wheel_urls(name, version):
|
|
407
|
+
try:
|
|
408
|
+
api_url = f'https://pypi.org/pypi/{name}/{version}/json'
|
|
409
|
+
response = requests.get(api_url)
|
|
410
|
+
if response.status_code == 200:
|
|
411
|
+
data = response.json()
|
|
412
|
+
wheel_urls = []
|
|
413
|
+
|
|
414
|
+
for file_info in data.get('urls', []):
|
|
415
|
+
if file_info.get('packagetype') == 'bdist_wheel':
|
|
416
|
+
wheel_urls.append(file_info.get('url'))
|
|
417
|
+
|
|
418
|
+
return wheel_urls
|
|
419
|
+
else:
|
|
420
|
+
logger.warning(f'Cannot get PyPI API data for {name}({version})')
|
|
421
|
+
return []
|
|
422
|
+
|
|
423
|
+
except Exception as error:
|
|
424
|
+
logger.warning(f'Failed to get wheel URLs from PyPI API: {error}')
|
|
425
|
+
return []
|
|
103
426
|
|
|
104
427
|
|
|
105
428
|
def get_download_location_for_pypi(link):
|
|
106
|
-
# get the url for downloading source file
|
|
429
|
+
# get the url for downloading source file: https://docs.pypi.org/api/ Predictable URLs
|
|
107
430
|
ret = False
|
|
108
431
|
new_link = ''
|
|
432
|
+
host = 'https://files.pythonhosted.org'
|
|
109
433
|
|
|
110
434
|
try:
|
|
111
435
|
dn_loc_re = re.findall(r'pypi.org\/project\/?([^\/]*)\/?([^\/]*)', link)
|
|
112
436
|
oss_name = dn_loc_re[0][0]
|
|
437
|
+
oss_name = re.sub(r"[-_.]+", "-", oss_name)
|
|
113
438
|
oss_version = dn_loc_re[0][1]
|
|
114
439
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
card_file_list = bs_obj.findAll('div', {'class': 'card file__card'})
|
|
121
|
-
|
|
122
|
-
for card_file in card_file_list:
|
|
123
|
-
file_code = card_file.find('code').text
|
|
124
|
-
if file_code.lower() == "source":
|
|
125
|
-
new_link = card_file.find('a').attrs['href']
|
|
440
|
+
# 1. Source distribution 시도
|
|
441
|
+
new_link = f'{host}/packages/source/{oss_name[0]}/{oss_name}/{oss_name}-{oss_version}.tar.gz'
|
|
442
|
+
try:
|
|
443
|
+
res = urlopen(new_link)
|
|
444
|
+
if res.getcode() == 200:
|
|
126
445
|
ret = True
|
|
127
|
-
|
|
446
|
+
return ret, new_link
|
|
447
|
+
except Exception:
|
|
448
|
+
oss_name = re.sub(r"[-]+", "_", oss_name)
|
|
449
|
+
new_link = f'{host}/packages/source/{oss_name[0]}/{oss_name}/{oss_name}-{oss_version}.tar.gz'
|
|
450
|
+
try:
|
|
451
|
+
res = urlopen(new_link)
|
|
452
|
+
if res.getcode() == 200:
|
|
453
|
+
ret = True
|
|
454
|
+
return ret, new_link
|
|
455
|
+
except Exception:
|
|
456
|
+
pass
|
|
457
|
+
|
|
458
|
+
# 2. Source distribution이 없으면 wheel 파일들을 시도
|
|
459
|
+
wheel_urls = get_available_wheel_urls(oss_name, oss_version)
|
|
460
|
+
|
|
461
|
+
if wheel_urls:
|
|
462
|
+
# Pure Python wheel을 우선적으로 찾기
|
|
463
|
+
for wheel_url in wheel_urls:
|
|
464
|
+
if 'py3-none-any' in wheel_url or 'py2.py3-none-any' in wheel_url:
|
|
465
|
+
try:
|
|
466
|
+
res = urlopen(wheel_url)
|
|
467
|
+
if res.getcode() == 200:
|
|
468
|
+
ret = True
|
|
469
|
+
new_link = wheel_url
|
|
470
|
+
logger.info(f'Using wheel file : {wheel_url}')
|
|
471
|
+
return ret, new_link
|
|
472
|
+
except Exception:
|
|
473
|
+
continue
|
|
474
|
+
|
|
475
|
+
# Pure Python wheel이 없으면 첫 번째 wheel 시도
|
|
476
|
+
if wheel_urls:
|
|
477
|
+
try:
|
|
478
|
+
res = urlopen(wheel_urls[0])
|
|
479
|
+
if res.getcode() == 200:
|
|
480
|
+
ret = True
|
|
481
|
+
new_link = wheel_urls[0]
|
|
482
|
+
logger.info(f'Using wheel file : {wheel_urls[0]}')
|
|
483
|
+
return ret, new_link
|
|
484
|
+
except Exception:
|
|
485
|
+
pass
|
|
486
|
+
|
|
128
487
|
except Exception as error:
|
|
129
488
|
ret = False
|
|
130
|
-
logger.warning('Cannot find the link for pypi (url:
|
|
489
|
+
logger.warning(f'Cannot find the link for pypi (url:{link}({new_link})) e:{str(error)}')
|
|
131
490
|
|
|
132
491
|
return ret, new_link
|
|
133
492
|
|
|
@@ -153,6 +512,13 @@ def get_download_location_for_maven(link):
|
|
|
153
512
|
return ret, new_link
|
|
154
513
|
else:
|
|
155
514
|
dn_loc = 'https://' + link
|
|
515
|
+
elif link.startswith('dl.google.com/android/maven2/'):
|
|
516
|
+
if link.endswith('.jar'):
|
|
517
|
+
new_link = 'https://' + link
|
|
518
|
+
ret = True
|
|
519
|
+
return ret, new_link
|
|
520
|
+
else:
|
|
521
|
+
dn_loc = 'https://' + link
|
|
156
522
|
else:
|
|
157
523
|
raise Exception("not valid url for maven")
|
|
158
524
|
|
fosslight_util/compare_yaml.py
CHANGED
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
# SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
|
|
6
6
|
import logging
|
|
7
|
+
from typing import List, Dict
|
|
7
8
|
from fosslight_util.constant import LOGGER_NAME
|
|
9
|
+
from fosslight_util.oss_item import FileItem
|
|
8
10
|
|
|
9
11
|
logger = logging.getLogger(LOGGER_NAME)
|
|
10
12
|
VERSION = 'version'
|
|
@@ -12,7 +14,7 @@ LICENSE = 'license'
|
|
|
12
14
|
NAME = 'name'
|
|
13
15
|
|
|
14
16
|
|
|
15
|
-
def compare_yaml(before_fileitems, after_fileitems):
|
|
17
|
+
def compare_yaml(before_fileitems: List[FileItem], after_fileitems: List[FileItem]) -> Dict[str, List]:
|
|
16
18
|
bf_raw = []
|
|
17
19
|
af_raw = []
|
|
18
20
|
for bf in before_fileitems:
|
fosslight_util/constant.py
CHANGED
|
@@ -35,13 +35,17 @@ SHEET_NAME_FOR_SCANNER = {
|
|
|
35
35
|
# pub: https://pub.dev/packages/(package)/versions/(version)
|
|
36
36
|
# Cocoapods : https://cocoapods.org/(package)
|
|
37
37
|
# go : https://pkg.go.dev/(package_name_with_slash)@(version)
|
|
38
|
+
# cargo : https://crates.io/crates/(crate_name)/(version)
|
|
38
39
|
PKG_PATTERN = {
|
|
39
40
|
"pypi": r'https?:\/\/pypi\.org\/project\/([^\/]+)[\/]?([^\/]*)',
|
|
40
41
|
"pypi2": r'https?:\/\/files\.pythonhosted\.org\/packages\/source\/[\w]\/([^\/]+)\/[\S]+-([^\-]+)\.tar\.gz',
|
|
41
42
|
"maven": r'https?:\/\/mvnrepository\.com\/artifact\/([^\/]+)\/([^\/]+)\/?([^\/]*)',
|
|
43
|
+
"maven_repo1": r'https?:\/\/repo1\.maven\.org\/maven2\/(.*)',
|
|
44
|
+
"maven_google": r'https?:\/\/dl\.google\.com\/android\/maven2\/(.*)',
|
|
42
45
|
"npm": r'https?:\/\/www\.npmjs\.com\/package\/([^\/\@]+)(?:\/v\/)?([^\/]*)',
|
|
43
46
|
"npm2": r'https?:\/\/www\.npmjs\.com\/package\/(\@[^\/]+\/[^\/]+)(?:\/v\/)?([^\/]*)',
|
|
44
47
|
"pub": r'https?:\/\/pub\.dev\/packages\/([^\/]+)(?:\/versions\/)?([^\/]*)',
|
|
45
48
|
"cocoapods": r'https?:\/\/cocoapods\.org\/pods\/([^\/]+)',
|
|
46
|
-
"go": r'https?:\/\/pkg.go.dev\/([^\@]+)\@?v?([^\/]*)'
|
|
49
|
+
"go": r'https?:\/\/pkg.go.dev\/([^\@]+)\@?v?([^\/]*)',
|
|
50
|
+
"cargo": r'https?:\/\/crates\.io\/crates\/([^\/]+)\/?([^\/]*)',
|
|
47
51
|
}
|
fosslight_util/correct.py
CHANGED
|
@@ -61,17 +61,15 @@ def correct_with_yaml(correct_filepath, path_to_scan, scan_item):
|
|
|
61
61
|
|
|
62
62
|
yaml_path_exists = True
|
|
63
63
|
exclude_fileitems.append(idx)
|
|
64
|
-
|
|
65
|
-
if not yaml_path_exists:
|
|
64
|
+
if scanner_name == FOSSLIGHT_SOURCE and not yaml_path_exists:
|
|
66
65
|
correct_item = copy.deepcopy(yaml_file_item)
|
|
67
66
|
if os.path.exists(os.path.normpath(yaml_file_item.source_name_or_path)):
|
|
68
67
|
correct_item.comment = 'Loaded from sbom-info.yaml'
|
|
69
68
|
correct_fileitems.append(correct_item)
|
|
70
69
|
else:
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
correct_fileitems.append(correct_item)
|
|
70
|
+
correct_item.exclude = True
|
|
71
|
+
correct_item.comment = 'Added by sbom-info.yaml'
|
|
72
|
+
correct_fileitems.append(correct_item)
|
|
75
73
|
if correct_fileitems:
|
|
76
74
|
scan_item.append_file_items(correct_fileitems, scanner_name)
|
|
77
75
|
find_match = True
|