fosslight-util 2.1.28__tar.gz → 2.1.30__tar.gz

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.
Files changed (47) hide show
  1. {fosslight_util-2.1.28/src/fosslight_util.egg-info → fosslight_util-2.1.30}/PKG-INFO +1 -2
  2. {fosslight_util-2.1.28 → fosslight_util-2.1.30}/requirements.txt +1 -1
  3. {fosslight_util-2.1.28 → fosslight_util-2.1.30}/setup.py +1 -1
  4. {fosslight_util-2.1.28 → fosslight_util-2.1.30}/src/fosslight_util/_get_downloadable_url.py +31 -5
  5. {fosslight_util-2.1.28 → fosslight_util-2.1.30}/src/fosslight_util/download.py +78 -11
  6. fosslight_util-2.1.30/src/fosslight_util/get_pom_license.py +120 -0
  7. {fosslight_util-2.1.28 → fosslight_util-2.1.30/src/fosslight_util.egg-info}/PKG-INFO +1 -2
  8. {fosslight_util-2.1.28 → fosslight_util-2.1.30}/src/fosslight_util.egg-info/SOURCES.txt +1 -0
  9. {fosslight_util-2.1.28 → fosslight_util-2.1.30}/src/fosslight_util.egg-info/requires.txt +0 -1
  10. {fosslight_util-2.1.28 → fosslight_util-2.1.30}/LICENSE +0 -0
  11. {fosslight_util-2.1.28 → fosslight_util-2.1.30}/MANIFEST.in +0 -0
  12. {fosslight_util-2.1.28 → fosslight_util-2.1.30}/README.md +0 -0
  13. {fosslight_util-2.1.28 → fosslight_util-2.1.30}/setup.cfg +0 -0
  14. {fosslight_util-2.1.28 → fosslight_util-2.1.30}/src/fosslight_util/__init__.py +0 -0
  15. {fosslight_util-2.1.28 → fosslight_util-2.1.30}/src/fosslight_util/compare_yaml.py +0 -0
  16. {fosslight_util-2.1.28 → fosslight_util-2.1.30}/src/fosslight_util/constant.py +0 -0
  17. {fosslight_util-2.1.28 → fosslight_util-2.1.30}/src/fosslight_util/correct.py +0 -0
  18. {fosslight_util-2.1.28 → fosslight_util-2.1.30}/src/fosslight_util/cover.py +0 -0
  19. {fosslight_util-2.1.28 → fosslight_util-2.1.30}/src/fosslight_util/exclude.py +0 -0
  20. {fosslight_util-2.1.28 → fosslight_util-2.1.30}/src/fosslight_util/help.py +0 -0
  21. {fosslight_util-2.1.28 → fosslight_util-2.1.30}/src/fosslight_util/oss_item.py +0 -0
  22. {fosslight_util-2.1.28 → fosslight_util-2.1.30}/src/fosslight_util/output_format.py +0 -0
  23. {fosslight_util-2.1.28 → fosslight_util-2.1.30}/src/fosslight_util/parsing_yaml.py +0 -0
  24. {fosslight_util-2.1.28 → fosslight_util-2.1.30}/src/fosslight_util/read_excel.py +0 -0
  25. {fosslight_util-2.1.28 → fosslight_util-2.1.30}/src/fosslight_util/resources/frequentLicenselist.json +0 -0
  26. {fosslight_util-2.1.28 → fosslight_util-2.1.30}/src/fosslight_util/resources/frequent_license_nick_list.json +0 -0
  27. {fosslight_util-2.1.28 → fosslight_util-2.1.30}/src/fosslight_util/resources/licenses.json +0 -0
  28. {fosslight_util-2.1.28 → fosslight_util-2.1.30}/src/fosslight_util/set_log.py +0 -0
  29. {fosslight_util-2.1.28 → fosslight_util-2.1.30}/src/fosslight_util/spdx_licenses.py +0 -0
  30. {fosslight_util-2.1.28 → fosslight_util-2.1.30}/src/fosslight_util/timer_thread.py +0 -0
  31. {fosslight_util-2.1.28 → fosslight_util-2.1.30}/src/fosslight_util/write_cyclonedx.py +0 -0
  32. {fosslight_util-2.1.28 → fosslight_util-2.1.30}/src/fosslight_util/write_excel.py +0 -0
  33. {fosslight_util-2.1.28 → fosslight_util-2.1.30}/src/fosslight_util/write_opossum.py +0 -0
  34. {fosslight_util-2.1.28 → fosslight_util-2.1.30}/src/fosslight_util/write_scancodejson.py +0 -0
  35. {fosslight_util-2.1.28 → fosslight_util-2.1.30}/src/fosslight_util/write_spdx.py +0 -0
  36. {fosslight_util-2.1.28 → fosslight_util-2.1.30}/src/fosslight_util/write_txt.py +0 -0
  37. {fosslight_util-2.1.28 → fosslight_util-2.1.30}/src/fosslight_util/write_yaml.py +0 -0
  38. {fosslight_util-2.1.28 → fosslight_util-2.1.30}/src/fosslight_util.egg-info/dependency_links.txt +0 -0
  39. {fosslight_util-2.1.28 → fosslight_util-2.1.30}/src/fosslight_util.egg-info/entry_points.txt +0 -0
  40. {fosslight_util-2.1.28 → fosslight_util-2.1.30}/src/fosslight_util.egg-info/top_level.txt +0 -0
  41. {fosslight_util-2.1.28 → fosslight_util-2.1.30}/tests/test_cyclonedx.py +0 -0
  42. {fosslight_util-2.1.28 → fosslight_util-2.1.30}/tests/test_download.py +0 -0
  43. {fosslight_util-2.1.28 → fosslight_util-2.1.30}/tests/test_opossum.py +0 -0
  44. {fosslight_util-2.1.28 → fosslight_util-2.1.30}/tests/test_spdx_licenses.py +0 -0
  45. {fosslight_util-2.1.28 → fosslight_util-2.1.30}/tests/test_text.py +0 -0
  46. {fosslight_util-2.1.28 → fosslight_util-2.1.30}/tests/test_write_output.py +0 -0
  47. {fosslight_util-2.1.28 → fosslight_util-2.1.30}/tests/test_write_yaml.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fosslight_util
3
- Version: 2.1.28
3
+ Version: 2.1.30
4
4
  Summary: FOSSLight Util
5
5
  Home-page: https://github.com/fosslight/fosslight_util
6
6
  Download-URL: https://github.com/fosslight/fosslight_util
@@ -20,7 +20,6 @@ Requires-Dist: progress
20
20
  Requires-Dist: PyYAML
21
21
  Requires-Dist: lastversion
22
22
  Requires-Dist: coloredlogs
23
- Requires-Dist: python3-wget
24
23
  Requires-Dist: beautifulsoup4
25
24
  Requires-Dist: jsonmerge
26
25
  Requires-Dist: spdx-tools==0.8.*; sys_platform == "linux"
@@ -5,7 +5,7 @@ progress
5
5
  PyYAML
6
6
  lastversion
7
7
  coloredlogs
8
- python3-wget
8
+
9
9
  beautifulsoup4
10
10
  jsonmerge
11
11
  spdx-tools==0.8.*;sys_platform=="linux"
@@ -14,7 +14,7 @@ with open('requirements.txt', 'r', 'utf-8') as f:
14
14
  if __name__ == "__main__":
15
15
  setup(
16
16
  name='fosslight_util',
17
- version='2.1.28',
17
+ version='2.1.30',
18
18
  package_dir={"": "src"},
19
19
  packages=find_packages(where='src'),
20
20
  description='FOSSLight Util',
@@ -499,13 +499,39 @@ def get_download_location_for_maven(link):
499
499
 
500
500
  try:
501
501
  if link.startswith('mvnrepository.com/artifact/'):
502
- dn_loc_split = link.replace('mvnrepository.com/', '').split('/')
503
- group_id = dn_loc_split[1].replace('.', '/')
504
- dn_loc = 'https://repo1.maven.org/maven2/' + group_id + '/' + dn_loc_split[2] + '/' + dn_loc_split[3]
502
+ parts = link.replace('mvnrepository.com/artifact/', '').split('/')
503
+ if len(parts) < 2:
504
+ raise Exception('invalid mvnrepository artifact url')
505
+ group_raw = parts[0]
506
+ artifact_id = parts[1]
507
+ version = parts[2] if len(parts) > 2 and parts[2] else ''
508
+ group_path = group_raw.replace('.', '/')
509
+
510
+ repo_base = f'https://repo1.maven.org/maven2/{group_path}/{artifact_id}'
511
+ try:
512
+ urlopen(repo_base)
513
+ if version:
514
+ dn_loc = f'{repo_base}/{version}'
515
+ else:
516
+ new_link = repo_base
517
+ ret = True
518
+ return ret, new_link
519
+ except Exception:
520
+ google_base = f'https://dl.google.com/android/maven2/{group_path}/{artifact_id}'
521
+ if version:
522
+ google_sources = f'{google_base}/{version}/{artifact_id}-{version}-sources.jar'
523
+ try:
524
+ res_g = urlopen(google_sources)
525
+ if res_g.getcode() == 200:
526
+ ret = True
527
+ return ret, google_sources
528
+ except Exception:
529
+ pass
530
+ new_link = google_base
531
+ ret = True
532
+ return ret, new_link
505
533
 
506
534
  elif link.startswith('repo1.maven.org/maven2/'):
507
- dn_loc_split = link.replace('repo1.maven.org/maven2/', '').split('/')
508
-
509
535
  if link.endswith('.tar.gz') or link.endswith('.jar') or link.endswith('.tar.xz'):
510
536
  new_link = 'https://' + link
511
537
  ret = True
@@ -4,7 +4,7 @@
4
4
  # SPDX-License-Identifier: Apache-2.0
5
5
  import os
6
6
  import sys
7
- import wget
7
+ import requests
8
8
  import tarfile
9
9
  import zipfile
10
10
  import logging
@@ -58,6 +58,22 @@ def alarm_handler(signum, frame):
58
58
  raise TimeOutException(f'Timeout ({SIGNAL_TIMEOUT} sec)', 1)
59
59
 
60
60
 
61
+ def is_downloadable(url):
62
+ try:
63
+ h = requests.head(url, allow_redirects=True)
64
+ header = h.headers
65
+ content_type = header.get('content-type')
66
+ if 'text/html' in content_type.lower():
67
+ return False
68
+ content_disposition = header.get('content-disposition')
69
+ if content_disposition and 'attachment' in content_disposition.lower():
70
+ return True
71
+ return True
72
+ except Exception as e:
73
+ logger.warning(f"is_downloadable - failed: {e}")
74
+ return False
75
+
76
+
61
77
  def change_src_link_to_https(src_link):
62
78
  src_link = src_link.replace("git://", "https://")
63
79
  if src_link.endswith(".git"):
@@ -381,25 +397,30 @@ def download_wget(link, target_dir, compressed_only, checkout_to):
381
397
  link = new_link
382
398
 
383
399
  if compressed_only:
400
+ # Check if link ends with known compression extensions
384
401
  for ext in compression_extension:
385
402
  if link.endswith(ext):
386
403
  success = True
387
404
  break
388
- if not success:
389
- if pkg_type == 'cargo':
390
- success = True
391
405
  else:
392
- success = True
406
+ # If get_downloadable_url found a downloadable file, proceed
407
+ if ret:
408
+ success = True
409
+ else:
410
+ # No downloadable file found in package repositories, verify link is downloadable
411
+ if not is_downloadable(link):
412
+ raise Exception('Not a downloadable link (link:{0})'.format(link))
413
+ success = True
393
414
 
415
+ # Fallback: verify link is downloadable for compressed_only case
394
416
  if not success:
395
- raise Exception('Not supported compression type (link:{0})'.format(link))
417
+ if is_downloadable(link):
418
+ success = True
419
+ else:
420
+ raise Exception('Not a downloadable link (link:{0})'.format(link))
396
421
 
397
422
  logger.info(f"wget: {link}")
398
- if pkg_type == 'cargo':
399
- outfile = os.path.join(target_dir, f'{oss_name}.tar.gz')
400
- downloaded_file = wget.download(link, out=outfile)
401
- else:
402
- downloaded_file = wget.download(link, target_dir)
423
+ downloaded_file = download_file(link, target_dir)
403
424
  if platform.system() != "Windows":
404
425
  signal.alarm(0)
405
426
  else:
@@ -416,6 +437,49 @@ def download_wget(link, target_dir, compressed_only, checkout_to):
416
437
  return success, downloaded_file, msg, oss_name, oss_version
417
438
 
418
439
 
440
+ def download_file(url, target_dir):
441
+ local_path = ""
442
+ try:
443
+ try:
444
+ h = requests.head(url, allow_redirects=True)
445
+ final_url = h.url or url
446
+ headers = h.headers
447
+ except Exception:
448
+ final_url = url
449
+ headers = {}
450
+
451
+ with requests.get(final_url, stream=True, allow_redirects=True) as r:
452
+ r.raise_for_status()
453
+
454
+ filename = ""
455
+ cd = r.headers.get("Content-Disposition") or headers.get("Content-Disposition")
456
+ if cd:
457
+ m_star = re.search(r"filename\*=(?:UTF-8'')?([^;\r\n]+)", cd)
458
+ if m_star:
459
+ filename = urllib.parse.unquote(m_star.group(1).strip('"\''))
460
+ else:
461
+ m = re.search(r"filename=([^;\r\n]+)", cd)
462
+ if m:
463
+ filename = m.group(1).strip('"\'')
464
+ if not filename:
465
+ final_for_name = r.url or final_url
466
+ filename = os.path.basename(urllib.parse.urlparse(final_for_name).path)
467
+ if not filename:
468
+ filename = "downloaded_file"
469
+ if os.path.isdir(target_dir):
470
+ local_path = os.path.join(target_dir, filename)
471
+ else:
472
+ local_path = target_dir
473
+
474
+ with open(local_path, 'wb') as f:
475
+ for chunk in r.iter_content(chunk_size=8192):
476
+ f.write(chunk)
477
+ except Exception as e:
478
+ logger.warning(f"download_file - failed: {e}")
479
+ return None
480
+ return local_path
481
+
482
+
419
483
  def extract_compressed_dir(src_dir, target_dir, remove_after_extract=True):
420
484
  logger.debug(f"Extract Dir: {src_dir}")
421
485
  try:
@@ -450,6 +514,9 @@ def extract_compressed_file(fname, extract_path, remove_after_extract=True, comp
450
514
  decompress_bz2(fname, extract_path)
451
515
  elif fname.endswith(".whl"):
452
516
  unzip(fname, extract_path)
517
+ elif fname.endswith(".crate"):
518
+ with contextlib.closing(tarfile.open(fname, "r:gz")) as t:
519
+ t.extractall(path=extract_path)
453
520
  else:
454
521
  is_compressed_file = False
455
522
  if compressed_only:
@@ -0,0 +1,120 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ # Copyright (c) 2025 LG Electronics Inc.
4
+ # SPDX-License-Identifier: Apache-2.0
5
+
6
+ import os
7
+ import logging
8
+ import urllib.request
9
+ from urllib.error import URLError, HTTPError
10
+ from defusedxml.ElementTree import fromstring as xml_fromstring
11
+ import fosslight_util.constant as constant
12
+
13
+ logger = logging.getLogger(constant.LOGGER_NAME)
14
+
15
+
16
+ def get_license_from_pom(group_id: str = None,
17
+ artifact_id: str = None,
18
+ version: str = None,
19
+ pom_path: str = None,
20
+ check_parent: bool = True) -> str:
21
+
22
+ def build_urls(g, a, v):
23
+ group_path = g.replace('.', '/')
24
+ name = f"{a}-{v}.pom"
25
+ repo1 = f"https://repo1.maven.org/maven2/{group_path}/{a}/{v}/{name}"
26
+ google = f"https://dl.google.com/android/maven2/{group_path}/{a}/{v}/{name}"
27
+ return [repo1, google]
28
+
29
+ def fetch_pom(g, a, v):
30
+ for url in build_urls(g, a, v):
31
+ try:
32
+ with urllib.request.urlopen(url) as resp:
33
+ return resp.read().decode('utf-8')
34
+ except (HTTPError, URLError):
35
+ continue
36
+ return None
37
+
38
+ def extract_licenses(root):
39
+ licenses_elem = root.find('{*}licenses')
40
+ if licenses_elem is not None:
41
+ names = []
42
+ for lic in licenses_elem.findall('{*}license'):
43
+ name = lic.findtext('{*}name')
44
+ if name:
45
+ names.append(name.replace(',', ''))
46
+ if names:
47
+ return ', '.join(names)
48
+ return None
49
+
50
+ def extract_parent_info(root):
51
+ parent = root.find('{*}parent')
52
+ if parent is not None:
53
+ g = parent.findtext('{*}groupId') or parent.findtext('groupId')
54
+ a = parent.findtext('{*}artifactId') or parent.findtext('artifactId')
55
+ v = parent.findtext('{*}version') or parent.findtext('version')
56
+ if g and a and v:
57
+ return g, a, v
58
+ return None, None, None
59
+
60
+ visited = set()
61
+
62
+ def find_license_in_pom_recursive(g, a, v, check_parent_flag):
63
+ key = (g, a, v)
64
+ if key in visited:
65
+ return ''
66
+ visited.add(key)
67
+ content = fetch_pom(g, a, v)
68
+ if not content:
69
+ return ''
70
+ try:
71
+ root = xml_fromstring(content)
72
+ except Exception:
73
+ return ''
74
+ licenses = extract_licenses(root)
75
+ if licenses:
76
+ return licenses
77
+ if not check_parent_flag:
78
+ return ''
79
+ pg, pa, pv = extract_parent_info(root)
80
+ if pg and pa and pv:
81
+ return find_license_in_pom_recursive(pg, pa, pv, check_parent_flag)
82
+ return ''
83
+
84
+ try:
85
+ if pom_path:
86
+ if not os.path.exists(pom_path):
87
+ logger.warning(f"POM file not found: {pom_path}")
88
+ return ''
89
+ try:
90
+ with open(pom_path, 'r', encoding='utf-8') as f:
91
+ content = f.read()
92
+ xml_start = content.find('<?xml')
93
+ if xml_start > 0:
94
+ content = content[xml_start:]
95
+ elif xml_start == -1:
96
+ root_start = content.find('<project')
97
+ if root_start > 0:
98
+ content = content[root_start:]
99
+ root = xml_fromstring(content)
100
+ except Exception as e:
101
+ logger.warning(f"Failed to parse POM file {pom_path}: {e}")
102
+ return ''
103
+
104
+ licenses = extract_licenses(root)
105
+ if licenses:
106
+ return licenses
107
+ if not check_parent:
108
+ return ''
109
+ pg, pa, pv = extract_parent_info(root)
110
+ if pg and pa and pv:
111
+ return find_license_in_pom_recursive(pg, pa, pv, check_parent)
112
+
113
+ logger.debug(f"No license info found in local POM: {pom_path}, Retry with remote fetch.")
114
+
115
+ if not (group_id and artifact_id and version):
116
+ return ''
117
+ return find_license_in_pom_recursive(group_id, artifact_id, version, check_parent)
118
+ except Exception as e:
119
+ logger.warning(f"Error getting license from POM: {e}")
120
+ return ''
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fosslight_util
3
- Version: 2.1.28
3
+ Version: 2.1.30
4
4
  Summary: FOSSLight Util
5
5
  Home-page: https://github.com/fosslight/fosslight_util
6
6
  Download-URL: https://github.com/fosslight/fosslight_util
@@ -20,7 +20,6 @@ Requires-Dist: progress
20
20
  Requires-Dist: PyYAML
21
21
  Requires-Dist: lastversion
22
22
  Requires-Dist: coloredlogs
23
- Requires-Dist: python3-wget
24
23
  Requires-Dist: beautifulsoup4
25
24
  Requires-Dist: jsonmerge
26
25
  Requires-Dist: spdx-tools==0.8.*; sys_platform == "linux"
@@ -11,6 +11,7 @@ src/fosslight_util/correct.py
11
11
  src/fosslight_util/cover.py
12
12
  src/fosslight_util/download.py
13
13
  src/fosslight_util/exclude.py
14
+ src/fosslight_util/get_pom_license.py
14
15
  src/fosslight_util/help.py
15
16
  src/fosslight_util/oss_item.py
16
17
  src/fosslight_util/output_format.py
@@ -5,7 +5,6 @@ progress
5
5
  PyYAML
6
6
  lastversion
7
7
  coloredlogs
8
- python3-wget
9
8
  beautifulsoup4
10
9
  jsonmerge
11
10
  setuptools>=65.5.1
File without changes