fosslight-dependency 3.0.7__py3-none-any.whl → 4.1.30__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.
Files changed (46) hide show
  1. fosslight_dependency/LICENSES/LICENSE +201 -0
  2. fosslight_dependency/LICENSES/LicenseRef-3rd_party_licenses.txt +1254 -0
  3. fosslight_dependency/__init__.py +0 -1
  4. fosslight_dependency/_analyze_dependency.py +130 -0
  5. fosslight_dependency/_graph_convertor.py +67 -0
  6. fosslight_dependency/_help.py +79 -0
  7. fosslight_dependency/_package_manager.py +397 -0
  8. fosslight_dependency/cli.py +127 -0
  9. fosslight_dependency/constant.py +57 -0
  10. fosslight_dependency/dependency_item.py +103 -0
  11. fosslight_dependency/package_manager/Android.py +90 -0
  12. fosslight_dependency/package_manager/Cargo.py +144 -0
  13. fosslight_dependency/package_manager/Carthage.py +130 -0
  14. fosslight_dependency/package_manager/Cocoapods.py +194 -0
  15. fosslight_dependency/package_manager/Go.py +179 -0
  16. fosslight_dependency/package_manager/Gradle.py +123 -0
  17. fosslight_dependency/package_manager/Helm.py +106 -0
  18. fosslight_dependency/package_manager/Maven.py +274 -0
  19. fosslight_dependency/package_manager/Npm.py +296 -0
  20. fosslight_dependency/package_manager/Nuget.py +368 -0
  21. fosslight_dependency/package_manager/Pnpm.py +155 -0
  22. fosslight_dependency/package_manager/Pub.py +241 -0
  23. fosslight_dependency/package_manager/Pypi.py +395 -0
  24. fosslight_dependency/package_manager/Swift.py +159 -0
  25. fosslight_dependency/package_manager/Unity.py +118 -0
  26. fosslight_dependency/package_manager/Yarn.py +231 -0
  27. fosslight_dependency/package_manager/__init__.py +0 -0
  28. fosslight_dependency/run_dependency_scanner.py +393 -0
  29. fosslight_dependency-4.1.30.dist-info/METADATA +213 -0
  30. fosslight_dependency-4.1.30.dist-info/RECORD +37 -0
  31. {fosslight_dependency-3.0.7.dist-info → fosslight_dependency-4.1.30.dist-info}/WHEEL +1 -1
  32. fosslight_dependency-4.1.30.dist-info/entry_points.txt +2 -0
  33. fosslight_dependency-4.1.30.dist-info/licenses/LICENSES/Apache-2.0.txt +201 -0
  34. fosslight_dependency-4.1.30.dist-info/licenses/LICENSES/LicenseRef-3rd_party_licenses.txt +1254 -0
  35. fosslight_dependency-4.1.30.dist-info/licenses/LICENSES/MIT.txt +21 -0
  36. fosslight_dependency/_version.py +0 -1
  37. fosslight_dependency/analyze_dependency.py +0 -1090
  38. fosslight_dependency/third_party/askalono/askalono.exe +0 -0
  39. fosslight_dependency/third_party/askalono/askalono_macos +0 -0
  40. fosslight_dependency/third_party/nomos/nomossa +0 -0
  41. fosslight_dependency-3.0.7.dist-info/3rd_party_licenses.txt +0 -726
  42. fosslight_dependency-3.0.7.dist-info/METADATA +0 -51
  43. fosslight_dependency-3.0.7.dist-info/RECORD +0 -13
  44. fosslight_dependency-3.0.7.dist-info/entry_points.txt +0 -3
  45. {fosslight_dependency-3.0.7.dist-info → fosslight_dependency-4.1.30.dist-info/licenses}/LICENSE +0 -0
  46. {fosslight_dependency-3.0.7.dist-info → fosslight_dependency-4.1.30.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,274 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ # Copyright (c) 2021 LG Electronics Inc.
4
+ # SPDX-License-Identifier: Apache-2.0
5
+
6
+ import os
7
+ import logging
8
+ import subprocess
9
+ import shutil
10
+ from bs4 import BeautifulSoup as bs
11
+ from defusedxml.ElementTree import parse
12
+ import re
13
+ import fosslight_util.constant as constant
14
+ import fosslight_dependency.constant as const
15
+ from fosslight_dependency._package_manager import PackageManager
16
+ from fosslight_dependency._package_manager import version_refine, get_url_to_purl, change_file_mode
17
+ from fosslight_dependency.dependency_item import DependencyItem, change_dependson_to_purl
18
+ from fosslight_util.get_pom_license import get_license_from_pom
19
+ from fosslight_util.oss_item import OssItem
20
+
21
+ logger = logging.getLogger(constant.LOGGER_NAME)
22
+
23
+
24
+ class Maven(PackageManager):
25
+ package_manager_name = const.MAVEN
26
+
27
+ dn_url = 'https://mvnrepository.com/artifact/'
28
+ input_file_name = os.path.join('target', 'generated-resources', 'licenses.xml')
29
+ is_run_plugin = False
30
+ output_custom_dir = ''
31
+
32
+ def __init__(self, input_dir, output_dir, output_custom_dir):
33
+ super().__init__(self.package_manager_name, self.dn_url, input_dir, output_dir)
34
+ self.is_run_plugin = False
35
+
36
+ if output_custom_dir:
37
+ self.output_custom_dir = output_custom_dir
38
+ self.input_file_name = os.path.join(output_custom_dir, os.sep.join(self.input_file_name.split(os.sep)[1:]))
39
+
40
+ self.append_input_package_list_file(self.input_file_name)
41
+
42
+ def __del__(self):
43
+ if self.is_run_plugin:
44
+ self.clean_run_maven_plugin_output()
45
+
46
+ def run_plugin(self):
47
+ ret = True
48
+
49
+ if not os.path.isfile(self.input_file_name):
50
+ pom_backup = 'pom.xml_backup'
51
+
52
+ ret = self.add_plugin_in_pom(pom_backup)
53
+ if ret:
54
+ ret_plugin = self.run_maven_plugin()
55
+ if ret_plugin:
56
+ self.is_run_plugin = True
57
+
58
+ if os.path.isfile(pom_backup):
59
+ shutil.move(pom_backup, const.SUPPORT_PACKAE.get(self.package_manager_name))
60
+ else:
61
+ self.set_direct_dependencies(False)
62
+
63
+ return ret
64
+
65
+ def add_plugin_in_pom(self, pom_backup):
66
+ ret = False
67
+ xml = 'xml'
68
+
69
+ manifest_file = const.SUPPORT_PACKAE.get(self.package_manager_name)
70
+ if os.path.isfile(manifest_file) != 1:
71
+ logger.error(f"{manifest_file} is not existed in this directory.")
72
+ return ret
73
+
74
+ try:
75
+ shutil.move(manifest_file, pom_backup)
76
+
77
+ license_maven_plugin = '<plugin>\
78
+ <groupId>org.codehaus.mojo</groupId>\
79
+ <artifactId>license-maven-plugin</artifactId>\
80
+ <version>2.0.0</version>\
81
+ <executions>\
82
+ <execution>\
83
+ <id>aggregate-download-licenses</id>\
84
+ <goals>\
85
+ <goal>aggregate-download-licenses</goal>\
86
+ </goals>\
87
+ </execution>\
88
+ </executions>\
89
+ <configuration>\
90
+ <excludedScopes>test</excludedScopes>\
91
+ </configuration>\
92
+ </plugin>'
93
+
94
+ tmp_plugin = bs(license_maven_plugin, xml)
95
+
96
+ license_maven_plugins = f"<plugins>{license_maven_plugin}<plugins>"
97
+ tmp_plugins = bs(license_maven_plugins, xml)
98
+
99
+ with open(pom_backup, 'r', encoding='utf8') as f:
100
+ f_xml = f.read()
101
+ f_content = bs(f_xml, xml)
102
+
103
+ build = f_content.find('build')
104
+ if build is not None:
105
+ plugins = build.find('plugins')
106
+ if plugins is not None:
107
+ plugins.append(tmp_plugin.plugin)
108
+ ret = True
109
+ else:
110
+ build.append(tmp_plugins.plugins)
111
+ ret = True
112
+ except Exception as e:
113
+ ret = False
114
+ logger.error(f"Failed to add plugin in pom : {e}")
115
+
116
+ if ret:
117
+ with open(manifest_file, "w", encoding='utf8') as f_w:
118
+ f_w.write(f_content.prettify(formatter="minimal").encode().decode('utf-8'))
119
+
120
+ return ret
121
+
122
+ def clean_run_maven_plugin_output(self):
123
+ directory_name = os.path.dirname(self.input_file_name)
124
+ licenses_path = os.path.join(directory_name, 'licenses')
125
+ if os.path.isdir(directory_name):
126
+ if os.path.isdir(licenses_path):
127
+ shutil.rmtree(licenses_path)
128
+ os.remove(self.input_file_name)
129
+
130
+ if len(os.listdir(directory_name)) == 0:
131
+ shutil.rmtree(directory_name)
132
+
133
+ top_path = self.input_file_name.split(os.sep)[0]
134
+ if len(os.listdir(top_path)) == 0:
135
+ shutil.rmtree(top_path)
136
+
137
+ def run_maven_plugin(self):
138
+ ret_plugin = True
139
+ logger.info('Run maven license scanning plugin with temporary pom.xml')
140
+ current_mode = ''
141
+ if os.path.isfile('mvnw') or os.path.isfile('mvnw.cmd'):
142
+ if self.platform == const.WINDOWS:
143
+ cmd_mvn = "mvnw.cmd"
144
+ else:
145
+ cmd_mvn = "./mvnw"
146
+ current_mode = change_file_mode(cmd_mvn)
147
+ else:
148
+ cmd_mvn = "mvn"
149
+ cmd = f"{cmd_mvn} license:aggregate-download-licenses"
150
+
151
+ ret = subprocess.call(cmd, shell=True)
152
+ if ret != 0:
153
+ logger.error(f"Failed to run maven plugin: {cmd}")
154
+ ret_plugin = False
155
+
156
+ if ret_plugin:
157
+ cmd = f"{cmd_mvn} dependency:tree"
158
+ try:
159
+ ret_txt = subprocess.check_output(cmd, text=True, shell=True)
160
+ if ret_txt is not None:
161
+ self.parse_dependency_tree(ret_txt)
162
+ self.set_direct_dependencies(True)
163
+ else:
164
+ logger.error(f"Failed to run: {cmd}")
165
+ self.set_direct_dependencies(False)
166
+ except Exception as e:
167
+ logger.error(f"Failed to run '{cmd}': {e}")
168
+ self.set_direct_dependencies(False)
169
+ if current_mode:
170
+ change_file_mode(cmd_mvn, current_mode)
171
+ return ret_plugin
172
+
173
+ def create_dep_stack(self, dep_line):
174
+ dep_stack = []
175
+ cur_flag = ''
176
+ dep_level = -1
177
+ dep_level_plus = False
178
+ for line in dep_line.split('\n'):
179
+ try:
180
+ if not re.search(r'[.*INFO.*]', line):
181
+ continue
182
+ if len(line) <= 7:
183
+ continue
184
+ line = line[7:]
185
+
186
+ prev_flag = cur_flag
187
+ prev_dep_level = dep_level
188
+ dep_level = line.count("|")
189
+
190
+ re_result = re.findall(r'([\+|\\]\-)\s([^\:\s]+\:[^\:\s]+)\:(?:[^\:\s]+)\:([^\:\s]+)\:([^\:\s]+)', line)
191
+ if re_result:
192
+ cur_flag = re_result[0][0]
193
+ if (prev_flag == '\\-') and (prev_dep_level == dep_level):
194
+ dep_level_plus = True
195
+ if dep_level_plus and (prev_flag == '\\-') and (prev_dep_level != dep_level):
196
+ dep_level_plus = False
197
+ if dep_level_plus:
198
+ dep_level += 1
199
+ if re_result[0][3] == 'test':
200
+ continue
201
+ dep_name = f'{re_result[0][1]}({re_result[0][2]})'
202
+ dep_stack = dep_stack[:dep_level] + [dep_name]
203
+ yield dep_stack[:dep_level], dep_name
204
+ else:
205
+ cur_flag = ''
206
+ except Exception as e:
207
+ logger.warning(f"Failed to parse dependency tree: {e}")
208
+
209
+ def parse_dependency_tree(self, f_name):
210
+ try:
211
+ for stack, name in self.create_dep_stack(f_name):
212
+ if len(stack) == 0:
213
+ self.direct_dep_list.append(name)
214
+ else:
215
+ if stack[-1] not in self.relation_tree:
216
+ self.relation_tree[stack[-1]] = []
217
+ self.relation_tree[stack[-1]].append(name)
218
+ except Exception as e:
219
+ logger.warning(f'Fail to parse maven dependency tree:{e}')
220
+
221
+ def parse_oss_information(self, f_name):
222
+ with open(f_name, 'r', encoding='utf8') as input_fp:
223
+ tree = parse(input_fp)
224
+
225
+ root = tree.getroot()
226
+ dependencies = root.find("dependencies")
227
+ purl_dict = {}
228
+
229
+ for d in dependencies.iter("dependency"):
230
+ dep_item = DependencyItem()
231
+ oss_item = OssItem()
232
+ groupid = d.findtext("groupId")
233
+ artifactid = d.findtext("artifactId")
234
+ version = d.findtext("version")
235
+ oss_item.version = version_refine(version)
236
+
237
+ oss_item.name = f"{groupid}:{artifactid}"
238
+ oss_item.download_location = f"{self.dn_url}{groupid}/{artifactid}/{version}"
239
+ oss_item.homepage = f"{self.dn_url}{groupid}/{artifactid}"
240
+ dep_item.purl = get_url_to_purl(oss_item.download_location, self.package_manager_name)
241
+ purl_dict[f'{oss_item.name}({oss_item.version})'] = dep_item.purl
242
+
243
+ licenses = d.find("licenses")
244
+ if len(licenses):
245
+ license_names = []
246
+ for key_license in licenses.iter("license"):
247
+ if key_license.findtext("name") is not None:
248
+ license_names.append(key_license.findtext("name").replace(",", ""))
249
+ oss_item.license = ', '.join(license_names)
250
+ if not oss_item.license:
251
+ license_names = get_license_from_pom(groupid, artifactid, version)
252
+ if license_names:
253
+ oss_item.license = license_names
254
+
255
+ dep_key = f"{oss_item.name}({version})"
256
+
257
+ if self.direct_dep:
258
+ if len(self.direct_dep_list) > 0:
259
+ if dep_key in self.direct_dep_list:
260
+ oss_item.comment = 'direct'
261
+ else:
262
+ oss_item.comment = 'transitive'
263
+ try:
264
+ if dep_key in self.relation_tree:
265
+ dep_item.depends_on_raw = self.relation_tree[dep_key]
266
+ except Exception as e:
267
+ logger.error(f"Fail to find oss scope in dependency tree: {e}")
268
+
269
+ dep_item.oss_items.append(oss_item)
270
+ self.dep_items.append(dep_item)
271
+
272
+ if self.direct_dep:
273
+ self.dep_items = change_dependson_to_purl(purl_dict, self.dep_items)
274
+ return
@@ -0,0 +1,296 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ # Copyright (c) 2021 LG Electronics Inc.
4
+ # SPDX-License-Identifier: Apache-2.0
5
+
6
+ import os
7
+ import logging
8
+ import subprocess
9
+ import json
10
+ import shutil
11
+ import re
12
+ import requests
13
+ import fosslight_util.constant as constant
14
+ import fosslight_dependency.constant as const
15
+ from fosslight_dependency._package_manager import PackageManager, get_url_to_purl
16
+ from fosslight_dependency.dependency_item import DependencyItem, change_dependson_to_purl
17
+ from fosslight_util.oss_item import OssItem
18
+
19
+ logger = logging.getLogger(constant.LOGGER_NAME)
20
+ node_modules = 'node_modules'
21
+
22
+
23
+ class Npm(PackageManager):
24
+ package_manager_name = const.NPM
25
+
26
+ dn_url = 'https://www.npmjs.com/package/'
27
+ input_file_name = 'tmp_npm_license_output.json'
28
+ tmp_custom_json = 'custom.json'
29
+ flag_tmp_node_modules = False
30
+ _network_available = False
31
+
32
+ def __init__(self, input_dir, output_dir):
33
+ super().__init__(self.package_manager_name, self.dn_url, input_dir, output_dir)
34
+ self._check_network_available()
35
+
36
+ def __del__(self):
37
+ if os.path.isfile(os.path.join(self.input_dir, self.input_file_name)):
38
+ os.remove(os.path.join(self.input_dir, self.input_file_name))
39
+ if self.flag_tmp_node_modules:
40
+ shutil.rmtree(os.path.join(self.input_dir, node_modules), ignore_errors=True)
41
+ if os.path.exists(os.path.join(self.input_dir, self.tmp_custom_json)):
42
+ os.remove(os.path.join(self.input_dir, self.tmp_custom_json))
43
+
44
+ def run_plugin(self):
45
+ ret = self.start_license_checker()
46
+ return ret
47
+
48
+ def start_license_checker(self):
49
+ ret = True
50
+ license_checker_cmd = f'license-checker --production --json --out {self.input_file_name}'
51
+ custom_path_option = ' --customPath '
52
+ npm_install_cmd = 'npm install --production --ignore-scripts'
53
+
54
+ if os.path.isdir(node_modules) != 1:
55
+ logger.info(f"node_modules directory is not existed. So it executes '{npm_install_cmd}'.")
56
+ self.flag_tmp_node_modules = True
57
+ cmd_ret = subprocess.call(npm_install_cmd, shell=True)
58
+ if cmd_ret != 0:
59
+ logger.error(f"{npm_install_cmd} failed")
60
+ return False
61
+
62
+ # customized json file for obtaining specific items with license-checker
63
+ self.make_custom_json(self.tmp_custom_json)
64
+
65
+ cmd = license_checker_cmd + custom_path_option + self.tmp_custom_json
66
+ cmd_ret = subprocess.call(cmd, shell=True)
67
+ if cmd_ret != 0:
68
+ logger.error(f"It returns the error: {cmd}")
69
+ logger.error("Please check if the license-checker is installed.(sudo npm install -g license-checker)")
70
+ ret = False
71
+ else:
72
+ self.append_input_package_list_file(self.input_file_name)
73
+ if os.path.exists(self.tmp_custom_json):
74
+ os.remove(self.tmp_custom_json)
75
+
76
+ return ret
77
+
78
+ def make_custom_json(self, tmp_custom_json):
79
+ with open(tmp_custom_json, 'w', encoding='utf8') as custom:
80
+ custom.write(
81
+ "{\n\t\"name\": \"\",\n\t\"version\": \"\",\n\t\"licenses\": \"\",\n\t\"repository\": \
82
+ \"\",\n\t\"url\": \"\",\n\t\"copyright\": \"\",\n\t\"licenseText\": \"\"\n}\n".encode().decode("utf-8"))
83
+
84
+ def parse_rel_dependencies(self, rel_name, rel_ver, rel_dependencies):
85
+ _dependencies = 'dependencies'
86
+ _version = 'version'
87
+ _peer = 'peerMissing'
88
+
89
+ for rel_dep_name in rel_dependencies.keys():
90
+ # Optional, non-installed dependencies are listed as empty objects
91
+ if rel_dependencies[rel_dep_name] == {}:
92
+ continue
93
+ if _peer in rel_dependencies[rel_dep_name]:
94
+ if rel_dependencies[rel_dep_name][_peer]:
95
+ continue
96
+ if f'{rel_name}({rel_ver})' not in self.relation_tree:
97
+ self.relation_tree[f'{rel_name}({rel_ver})'] = []
98
+ elif f'{rel_dep_name}({rel_dependencies[rel_dep_name][_version]})' in self.relation_tree[f'{rel_name}({rel_ver})']:
99
+ continue
100
+ self.relation_tree[f'{rel_name}({rel_ver})'].append(f'{rel_dep_name}({rel_dependencies[rel_dep_name][_version]})')
101
+ if _dependencies in rel_dependencies[rel_dep_name]:
102
+ self.parse_rel_dependencies(rel_dep_name, rel_dependencies[rel_dep_name][_version],
103
+ rel_dependencies[rel_dep_name][_dependencies])
104
+
105
+ def parse_transitive_relationship(self):
106
+ _dependencies = 'dependencies'
107
+ _version = 'version'
108
+ _name = 'name'
109
+ ret = True
110
+ err_msg = ''
111
+
112
+ cmd = 'npm ls -a --omit=dev --json -s'
113
+ result = subprocess.run(cmd, shell=True, capture_output=True, text=True, encoding='utf-8')
114
+ rel_tree = result.stdout
115
+ if not rel_tree or rel_tree.strip() == '':
116
+ logger.error(f"No output for {cmd}, stderr: {result.stderr}")
117
+ ret = False
118
+ elif result.returncode > 1:
119
+ logger.error(f"'{cmd}' failed with exit code({result.returncode}), stderr: {result.stderr}")
120
+ ret = False
121
+ if ret:
122
+ if result.returncode == 1:
123
+ logger.debug(f"'{cmd}' has warnings: {result.stderr}")
124
+
125
+ try:
126
+ rel_json = json.loads(rel_tree)
127
+ if len(rel_json) < 1:
128
+ ret = False
129
+ else:
130
+ self.package_name = f'{rel_json[_name]}({rel_json.get(_version, "")})'
131
+ if _dependencies in rel_json:
132
+ self.parse_rel_dependencies(rel_json[_name], rel_json.get(_version, ""), rel_json[_dependencies])
133
+ except Exception as e:
134
+ ret = False
135
+ err_msg = e
136
+ return ret, err_msg
137
+
138
+ def parse_direct_dependencies(self):
139
+ if not self.direct_dep:
140
+ return
141
+ try:
142
+ if os.path.isfile(const.SUPPORT_PACKAE.get(self.package_manager_name)):
143
+ ret, err_msg = self.parse_transitive_relationship()
144
+ if not ret:
145
+ self.direct_dep = False
146
+ logger.warning(f'It cannot print direct/transitive dependency: {err_msg}')
147
+ else:
148
+ logger.info('Direct/transitive support is not possible because the package.json file does not exist.')
149
+ self.direct_dep = False
150
+ except Exception as e:
151
+ logger.warning(f'Cannot print direct/transitive dependency: {e}')
152
+ self.direct_dep = False
153
+
154
+ def parse_oss_information(self, f_name):
155
+ with open(f_name, 'r', encoding='utf8') as json_file:
156
+ json_data = json.load(json_file)
157
+
158
+ _licenses = 'licenses'
159
+ _repository = 'repository'
160
+ _private = 'private'
161
+
162
+ keys = [key for key in json_data]
163
+ purl_dict = {}
164
+ for i in range(0, len(keys)):
165
+ dep_item = DependencyItem()
166
+ oss_item = OssItem()
167
+ d = json_data.get(keys[i - 1])
168
+ oss_init_name = d['name']
169
+ oss_item.name = self.package_manager_name + ":" + oss_init_name
170
+
171
+ if d[_licenses]:
172
+ license_name = d[_licenses]
173
+ else:
174
+ license_name = ''
175
+
176
+ oss_item.version = d['version']
177
+ package_path = d['path']
178
+
179
+ private_pkg = False
180
+ if _private in d and d[_private]:
181
+ private_pkg = True
182
+
183
+ oss_item.download_location = ''
184
+
185
+ npm_dl_url = f"{self.dn_url}{oss_init_name}/v/{oss_item.version}"
186
+ npm_home_url = f"{self.dn_url}{oss_init_name}"
187
+ dep_item.purl = get_url_to_purl(npm_dl_url, self.package_manager_name)
188
+ purl_dict[f'{oss_init_name}({oss_item.version})'] = dep_item.purl
189
+
190
+ repo_url = d[_repository] if d[_repository] else ''
191
+ if private_pkg:
192
+ oss_item.homepage = repo_url or ''
193
+ oss_item.download_location = oss_item.homepage
194
+ oss_item.comment = 'private'
195
+ else:
196
+ npm_url_exists = False
197
+ if self._network_available is True:
198
+ npm_url_exists = self._npm_url_exists(oss_init_name)
199
+
200
+ if self._network_available and not npm_url_exists:
201
+ oss_item.homepage = repo_url or ""
202
+ oss_item.download_location = oss_item.homepage
203
+ else:
204
+ oss_item.homepage = repo_url or npm_home_url
205
+ oss_item.download_location = npm_dl_url
206
+
207
+ if self.package_name == f'{oss_init_name}({oss_item.version})':
208
+ oss_item.comment = 'root package'
209
+ elif self.direct_dep and len(self.relation_tree) > 0:
210
+ if f'{oss_init_name}({oss_item.version})' in self.relation_tree[self.package_name]:
211
+ oss_item.comment = 'direct'
212
+ else:
213
+ oss_item.comment = 'transitive'
214
+
215
+ if f'{oss_init_name}({oss_item.version})' in self.relation_tree:
216
+ dep_item.depends_on_raw = self.relation_tree[f'{oss_init_name}({oss_item.version})']
217
+
218
+ manifest_file_path = os.path.join(package_path, const.SUPPORT_PACKAE.get(self.package_manager_name))
219
+ multi_license, license_comment, multi_flag = check_multi_license(license_name, manifest_file_path)
220
+
221
+ if multi_flag:
222
+ oss_item.comment = license_comment
223
+ license_name = multi_license
224
+ else:
225
+ license_name = license_name.replace(",", "")
226
+ license_name = check_unknown_license(license_name, manifest_file_path)
227
+ oss_item.license = license_name
228
+
229
+ dep_item.oss_items.append(oss_item)
230
+ self.dep_items.append(dep_item)
231
+
232
+ if self.direct_dep:
233
+ self.dep_items = change_dependson_to_purl(purl_dict, self.dep_items)
234
+ return
235
+
236
+ def _check_network_available(self) -> bool:
237
+ test_url = 'https://registry.npmjs.org/'
238
+ try:
239
+ resp = requests.head(test_url, timeout=3, allow_redirects=True)
240
+ self._network_available = resp.status_code < 400
241
+ except Exception:
242
+ self._network_available = False
243
+ return self._network_available
244
+
245
+ def _npm_url_exists(self, package_name: str) -> bool:
246
+ url = f"https://registry.npmjs.org/{package_name}"
247
+ try:
248
+ resp = requests.head(url, timeout=3, allow_redirects=True)
249
+ if resp.status_code == 405:
250
+ resp = requests.get(url, timeout=3, allow_redirects=True)
251
+ return resp.status_code < 400
252
+ except Exception:
253
+ return False
254
+
255
+
256
+ def check_multi_license(license_name, manifest_file_path):
257
+ multi_license_list = []
258
+ multi_license = ''
259
+ license_comment = ''
260
+ multi_flag = False
261
+ try:
262
+ if isinstance(license_name, list):
263
+ for i in range(0, len(license_name)):
264
+ l_i = license_name[i].replace(",", "")
265
+ multi_license_list.append(check_unknown_license(l_i, manifest_file_path))
266
+ multi_license = ','.join(multi_license_list)
267
+ multi_flag = True
268
+ else:
269
+ if license_name.startswith('(') and license_name.endswith(')'):
270
+ license_name = license_name.lstrip('(').rstrip(')')
271
+ license_comment = license_name
272
+ multi_license = ','.join(re.split(r'OR|AND', license_name))
273
+ multi_flag = True
274
+ except Exception as e:
275
+ multi_license = license_name
276
+ logger.warning(f'Fail to parse multi license in npm: {e}')
277
+
278
+ return multi_license, license_comment, multi_flag
279
+
280
+
281
+ def check_unknown_license(license_name, manifest_file_path):
282
+ if license_name.endswith('*'):
283
+ license_name = license_name[:-1]
284
+
285
+ if license_name == 'UNKNOWN':
286
+ try:
287
+ with open(manifest_file_path, 'r') as f:
288
+ json_f = json.load(f)
289
+ for key in json_f.keys():
290
+ if key == 'license':
291
+ license_name = json_f[key]
292
+ break
293
+ except Exception as e:
294
+ logging.warning(f"Cannot check unknown license: {e}")
295
+
296
+ return license_name