fosslight-util 1.4.34__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.
@@ -3,26 +3,25 @@
3
3
  # Copyright (c) 2021 LG Electronics Inc.
4
4
  # SPDX-License-Identifier: Apache-2.0
5
5
  import logging
6
- import xlrd
6
+ from typing import List, Dict, Any
7
+ import pandas as pd
7
8
  import json
8
9
  from fosslight_util.constant import LOGGER_NAME
9
- from fosslight_util.oss_item import OssItem
10
+ from fosslight_util.oss_item import OssItem, FileItem
10
11
  from fosslight_util.parsing_yaml import set_value_switch
11
12
 
12
13
  logger = logging.getLogger(LOGGER_NAME)
13
14
  IDX_CANNOT_FOUND = -1
14
15
  PREFIX_BIN = "bin"
15
- xlrd.xlsx.ensure_elementtree_imported(False, None)
16
- xlrd.xlsx.Element_has_iter = True
16
+ SHEET_PREFIX_TO_READ = ["bin", "bom", "src"]
17
17
 
18
18
 
19
- def read_oss_report(excel_file, sheet_names=""):
20
- _oss_report_items = []
21
- xl_sheets = {}
22
- all_sheet_to_read = []
23
- not_matched_sheet = []
19
+ def read_oss_report(excel_file: str, sheet_names: str = "", basepath: str = "") -> List[FileItem]:
20
+ fileitems: List[FileItem] = []
21
+ xl_sheets: Dict[str, Any] = {}
22
+ all_sheet_to_read: List[str] = []
23
+ not_matched_sheet: List[str] = []
24
24
  any_sheet_matched = False
25
- SHEET_PREFIX_TO_READ = ["bin", "bom", "src"]
26
25
  if sheet_names:
27
26
  sheet_name_prefix_match = False
28
27
  sheet_name_to_read = sheet_names.split(",")
@@ -32,9 +31,8 @@ def read_oss_report(excel_file, sheet_names=""):
32
31
 
33
32
  try:
34
33
  logger.info(f"Read data from : {excel_file}")
35
- xl_workbook = xlrd.open_workbook(excel_file)
36
- all_sheet_in_excel = xl_workbook.sheet_names()
37
-
34
+ xl_workbook = pd.ExcelFile(excel_file, engine='openpyxl')
35
+ all_sheet_in_excel = xl_workbook.sheet_names
38
36
  for sheet_to_read in sheet_name_to_read:
39
37
  try:
40
38
  any_sheet_matched = False
@@ -44,10 +42,9 @@ def read_oss_report(excel_file, sheet_names=""):
44
42
  sheet_name_lower = sheet_name.lower()
45
43
  if (sheet_name_prefix_match and sheet_name_lower.startswith(sheet_to_read_lower)) \
46
44
  or sheet_to_read_lower == sheet_name_lower:
47
- sheet = xl_workbook.sheet_by_name(sheet_name)
48
- if sheet:
49
- xl_sheets[sheet_name] = sheet
50
- any_sheet_matched = True
45
+ sheet = pd.read_excel(excel_file, sheet_name=sheet_name, engine='openpyxl', na_values='')
46
+ xl_sheets[sheet_name] = sheet.fillna('')
47
+ any_sheet_matched = True
51
48
  if not any_sheet_matched:
52
49
  not_matched_sheet.append(sheet_to_read)
53
50
  except Exception as error:
@@ -60,11 +57,14 @@ def read_oss_report(excel_file, sheet_names=""):
60
57
  elif (not sheet_name_prefix_match) and not_matched_sheet:
61
58
  logger.warning(f"Not matched sheet name: {not_matched_sheet}")
62
59
 
60
+ filepath_list = []
63
61
  for sheet_name, xl_sheet in xl_sheets.items():
64
62
  _item_idx = {
65
63
  "ID": IDX_CANNOT_FOUND,
66
64
  "Source Name or Path": IDX_CANNOT_FOUND,
65
+ "Source Path": IDX_CANNOT_FOUND,
67
66
  "Binary Name": IDX_CANNOT_FOUND,
67
+ "Binary Path": IDX_CANNOT_FOUND,
68
68
  "OSS Name": IDX_CANNOT_FOUND,
69
69
  "OSS Version": IDX_CANNOT_FOUND,
70
70
  "License": IDX_CANNOT_FOUND,
@@ -73,48 +73,49 @@ def read_oss_report(excel_file, sheet_names=""):
73
73
  "Exclude": IDX_CANNOT_FOUND,
74
74
  "Copyright Text": IDX_CANNOT_FOUND,
75
75
  "Comment": IDX_CANNOT_FOUND,
76
- "File Name or Path": IDX_CANNOT_FOUND
76
+ "File Name or Path": IDX_CANNOT_FOUND,
77
+ "Vulnerability Link": IDX_CANNOT_FOUND,
78
+ "TLSH": IDX_CANNOT_FOUND,
79
+ "SHA1": IDX_CANNOT_FOUND
77
80
  }
78
- num_cols = xl_sheet.ncols
79
- num_rows = xl_sheet.nrows
80
- MAX_FIND_HEADER_COLUMN = 5 if num_rows > 5 else num_rows
81
- DATA_START_ROW_IDX = 1
82
- for row_idx in range(0, MAX_FIND_HEADER_COLUMN):
83
- for col_idx in range(row_idx, num_cols):
84
- cell_obj = xl_sheet.cell(row_idx, col_idx)
85
- if cell_obj.value in _item_idx:
86
- _item_idx[cell_obj.value] = col_idx
87
81
 
88
- if len([key for key, value in _item_idx.items() if value != IDX_CANNOT_FOUND]) > 3:
89
- DATA_START_ROW_IDX = row_idx + 1
90
- break
82
+ for index, value in enumerate(xl_sheet.columns.tolist()):
83
+ _item_idx[value] = index
91
84
 
92
85
  # Get all values, iterating through rows and columns
93
86
  column_keys = json.loads(json.dumps(_item_idx))
94
87
 
95
88
  is_bin = True if sheet_name.lower().startswith(PREFIX_BIN) else False
96
89
 
97
- for row_idx in range(DATA_START_ROW_IDX, xl_sheet.nrows):
98
- item = OssItem("")
99
- item.is_binary = is_bin
90
+ for row_idx, row in xl_sheet.iterrows():
100
91
  valid_row = True
101
92
  load_data_cnt = 0
102
-
93
+ source_path = row[1]
94
+ if source_path not in filepath_list:
95
+ filepath_list.append(source_path)
96
+ fileitem = FileItem(basepath)
97
+ fileitem.source_name_or_path = source_path
98
+ fileitems.append(fileitem)
99
+ else:
100
+ fileitem = next((i for i in fileitems if i.source_name_or_path == source_path), None)
101
+ fileitem.is_binary = is_bin
102
+ ossitem = OssItem()
103
103
  for column_key, column_idx in column_keys.items():
104
104
  if column_idx != IDX_CANNOT_FOUND:
105
- cell_obj = xl_sheet.cell(row_idx, column_idx)
106
- cell_value = cell_obj.value
105
+ cell_obj = xl_sheet.iloc[row_idx, column_idx]
106
+ cell_value = cell_obj
107
+
107
108
  if cell_value != "":
108
109
  if column_key != "ID":
109
110
  if column_key:
110
111
  column_key = column_key.lower().strip()
111
- set_value_switch(item, column_key, cell_value)
112
+ set_value_switch(ossitem, column_key, cell_value)
112
113
  load_data_cnt += 1
113
114
  else:
114
115
  valid_row = False if cell_value == "-" else True
115
116
  if valid_row and load_data_cnt > 0:
116
- _oss_report_items.append(item)
117
+ fileitem.oss_items.append(ossitem)
117
118
 
118
119
  except Exception as error:
119
120
  logger.error(f"Parsing a OSS Report: {error}")
120
- return _oss_report_items
121
+ return fileitems
fosslight_util/set_log.py CHANGED
@@ -6,12 +6,18 @@
6
6
  import logging
7
7
  import os
8
8
  from pathlib import Path
9
- import pkg_resources
10
9
  import sys
11
10
  import platform
12
11
  from . import constant as constant
13
12
  from lastversion import lastversion
14
13
  import coloredlogs
14
+ from typing import Tuple
15
+ from logging import Logger
16
+
17
+ try:
18
+ from importlib.metadata import version, PackageNotFoundError
19
+ except ImportError:
20
+ from importlib_metadata import version, PackageNotFoundError # Python <3.8
15
21
 
16
22
 
17
23
  def init_check_latest_version(pkg_version="", main_package_name=""):
@@ -32,6 +38,21 @@ def init_check_latest_version(pkg_version="", main_package_name=""):
32
38
  logger.debug('Cannot check the latest version:' + str(error))
33
39
 
34
40
 
41
+ def get_os_version():
42
+
43
+ logger = logging.getLogger(constant.LOGGER_NAME)
44
+
45
+ os_version = platform.system() + " " + platform.release()
46
+ if os_version == "Windows 10":
47
+ try:
48
+ windows_build = sys.getwindowsversion().build
49
+ if windows_build >= 22000:
50
+ os_version = "Windows 11"
51
+ except Exception as error:
52
+ logger.debug(str(error))
53
+ return os_version
54
+
55
+
35
56
  class CustomAdapter(logging.LoggerAdapter):
36
57
  def __init__(self, logger, extra):
37
58
  super(CustomAdapter, self).__init__(logger, {})
@@ -41,8 +62,8 @@ class CustomAdapter(logging.LoggerAdapter):
41
62
  return '[%s] %s' % (self.extra, msg), kwargs
42
63
 
43
64
 
44
- def init_log(log_file, create_file=True, stream_log_level=logging.INFO,
45
- file_log_level=logging.DEBUG, main_package_name="", path_to_analyze=""):
65
+ def init_log(log_file: str, create_file: bool = True, stream_log_level: int = logging.INFO, file_log_level: int = logging.DEBUG,
66
+ main_package_name: str = "", path_to_analyze: str = "", path_to_exclude: list = []) -> Tuple[Logger, dict]:
46
67
 
47
68
  logger = logging.getLogger(constant.LOGGER_NAME)
48
69
 
@@ -70,18 +91,22 @@ def init_log(log_file, create_file=True, stream_log_level=logging.INFO,
70
91
  _result_log = {
71
92
  "Tool Info": main_package_name,
72
93
  "Python version": _PYTHON_VERSION,
73
- "OS": platform.system()+" "+platform.release(),
94
+ "OS": get_os_version(),
74
95
  }
75
96
  if main_package_name != "":
76
97
  pkg_info = main_package_name
77
98
  try:
78
- pkg_version = pkg_resources.get_distribution(main_package_name).version
99
+ pkg_version = version(main_package_name)
79
100
  init_check_latest_version(pkg_version, main_package_name)
80
101
  pkg_info = main_package_name + " v" + pkg_version
102
+ except PackageNotFoundError:
103
+ logger.debug('Cannot check the version: Package not found')
81
104
  except Exception as error:
82
105
  logger.debug('Cannot check the version:' + str(error))
83
106
  _result_log["Tool Info"] = pkg_info
84
107
  if path_to_analyze != "":
85
108
  _result_log["Path to analyze"] = path_to_analyze
109
+ if path_to_exclude != []:
110
+ _result_log["Path to exclude"] = ", ".join(path_to_exclude)
86
111
 
87
112
  return logger, _result_log
@@ -8,6 +8,7 @@ import os
8
8
  import sys
9
9
  import json
10
10
  import traceback
11
+ from typing import Tuple
11
12
 
12
13
  _resources_dir = 'resources'
13
14
  _licenses_json_file = 'licenses.json'
@@ -34,7 +35,7 @@ def get_license_from_nick():
34
35
  return licenses
35
36
 
36
37
 
37
- def get_spdx_licenses_json():
38
+ def get_spdx_licenses_json() -> Tuple[bool, str, str]:
38
39
  success = True
39
40
  error_msg = ''
40
41
  licenses = ''
@@ -0,0 +1,210 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ # Copyright (c) 2024 LG Electronics Inc.
4
+ # Copyright (c) OWASP Foundation.
5
+ # SPDX-License-Identifier: Apache-2.0
6
+
7
+ import os
8
+ import logging
9
+ import re
10
+ from pathlib import Path
11
+ from fosslight_util.constant import (LOGGER_NAME, FOSSLIGHT_DEPENDENCY, FOSSLIGHT_SCANNER,
12
+ FOSSLIGHT_SOURCE)
13
+ import traceback
14
+
15
+ logger = logging.getLogger(LOGGER_NAME)
16
+
17
+ try:
18
+ from packageurl import PackageURL
19
+ from cyclonedx.builder.this import this_component as cdx_lib_component
20
+ from cyclonedx.exception import MissingOptionalDependencyException
21
+ from cyclonedx.factory.license import LicenseFactory
22
+ from cyclonedx.model import XsUri, ExternalReferenceType
23
+ from cyclonedx.model.bom import Bom
24
+ from cyclonedx.model.component import Component, ComponentType, HashAlgorithm, HashType, ExternalReference
25
+ from cyclonedx.output import make_outputter, BaseOutput
26
+ from cyclonedx.output.json import JsonV1Dot6
27
+ from cyclonedx.schema import OutputFormat, SchemaVersion
28
+ from cyclonedx.validation.json import JsonStrictValidator
29
+ from cyclonedx.output.json import Json as JsonOutputter
30
+ from cyclonedx.validation.xml import XmlValidator
31
+ except Exception:
32
+ logger.info('No import cyclonedx-python-lib')
33
+
34
+
35
+ def write_cyclonedx(output_file_without_ext, output_extension, scan_item):
36
+ success = True
37
+ error_msg = ''
38
+
39
+ bom = Bom()
40
+ if scan_item:
41
+ try:
42
+ cover_name = scan_item.cover.get_print_json()["Tool information"].split('(').pop(0).strip()
43
+ match = re.search(r"(.+) v([0-9.]+)", cover_name)
44
+ if match:
45
+ scanner_name = match.group(1)
46
+ else:
47
+ scanner_name = FOSSLIGHT_SCANNER
48
+ except Exception:
49
+ cover_name = FOSSLIGHT_SCANNER
50
+ scanner_name = FOSSLIGHT_SCANNER
51
+
52
+ lc_factory = LicenseFactory()
53
+ bom.metadata.tools.components.add(cdx_lib_component())
54
+ bom.metadata.tools.components.add(Component(name=scanner_name.upper(),
55
+ type=ComponentType.APPLICATION))
56
+ comp_id = 0
57
+ bom.metadata.component = root_component = Component(name='Root Component',
58
+ type=ComponentType.APPLICATION,
59
+ bom_ref=str(comp_id))
60
+ relation_tree = {}
61
+
62
+ output_dir = os.path.dirname(output_file_without_ext)
63
+ Path(output_dir).mkdir(parents=True, exist_ok=True)
64
+ try:
65
+ root_package = False
66
+ for scanner_name, file_items in scan_item.file_items.items():
67
+ for file_item in file_items:
68
+ if file_item.exclude:
69
+ continue
70
+ if scanner_name == FOSSLIGHT_SOURCE:
71
+ comp_type = ComponentType.FILE
72
+ else:
73
+ comp_type = ComponentType.LIBRARY
74
+
75
+ for oss_item in file_item.oss_items:
76
+ if oss_item.name == '' or oss_item.name == '-':
77
+ if scanner_name == FOSSLIGHT_DEPENDENCY:
78
+ continue
79
+ else:
80
+ comp_name = file_item.source_name_or_path
81
+ else:
82
+ comp_name = oss_item.name
83
+
84
+ comp_id += 1
85
+ comp = Component(type=comp_type,
86
+ name=comp_name,
87
+ bom_ref=str(comp_id))
88
+
89
+ if oss_item.version != '':
90
+ comp.version = oss_item.version
91
+ if oss_item.copyright != '':
92
+ comp.copyright = oss_item.copyright
93
+ if scanner_name == FOSSLIGHT_DEPENDENCY and file_item.purl:
94
+ comp.purl = PackageURL.from_string(file_item.purl)
95
+ if scanner_name != FOSSLIGHT_DEPENDENCY:
96
+ if file_item.checksum != '0':
97
+ comp.hashes = [HashType(alg=HashAlgorithm.SHA_1, content=file_item.checksum)]
98
+
99
+ if oss_item.download_location != '':
100
+ comp.external_references = [ExternalReference(url=XsUri(oss_item.download_location),
101
+ type=ExternalReferenceType.WEBSITE)]
102
+
103
+ oss_licenses = []
104
+ for ol in oss_item.license:
105
+ try:
106
+ oss_licenses.append(lc_factory.make_from_string(ol))
107
+ except Exception:
108
+ logger.info(f'No spdx license name: {ol}')
109
+ if oss_licenses:
110
+ comp.licenses = oss_licenses
111
+
112
+ root_package = False
113
+ if scanner_name == FOSSLIGHT_DEPENDENCY:
114
+ if oss_item.comment:
115
+ oss_comment = oss_item.comment.split('/')
116
+ for oc in oss_comment:
117
+ if oc in ['direct', 'transitive', 'root package']:
118
+ if oc == 'direct':
119
+ bom.register_dependency(root_component, [comp])
120
+ elif oc == 'root package':
121
+ root_package = True
122
+ root_component.name = comp_name
123
+ root_component.type = comp_type
124
+ comp_id -= 1
125
+ else:
126
+ bom.register_dependency(root_component, [comp])
127
+ if len(file_item.depends_on) > 0:
128
+ purl = file_item.purl
129
+ relation_tree[purl] = []
130
+ relation_tree[purl].extend(file_item.depends_on)
131
+
132
+ if not root_package:
133
+ bom.components.add(comp)
134
+
135
+ if len(bom.components) > 0:
136
+ for comp_purl in relation_tree:
137
+ comp = bom.get_component_by_purl(PackageURL.from_string(comp_purl))
138
+ if comp:
139
+ dep_comp_list = []
140
+ for dep_comp_purl in relation_tree[comp_purl]:
141
+ dep_comp = bom.get_component_by_purl(PackageURL.from_string(dep_comp_purl))
142
+ if dep_comp:
143
+ dep_comp_list.append(dep_comp)
144
+ bom.register_dependency(comp, dep_comp_list)
145
+
146
+ except Exception as e:
147
+ success = False
148
+ error_msg = f'Failed to create CycloneDX document object:{e}, {traceback.format_exc()}'
149
+ else:
150
+ success = False
151
+ error_msg = 'No item to write in output file.'
152
+
153
+ result_file = ''
154
+ if success:
155
+ result_file = output_file_without_ext + output_extension
156
+ try:
157
+ if output_extension == '.json':
158
+ write_cyclonedx_json(bom, result_file)
159
+ elif output_extension == '.xml':
160
+ write_cyclonedx_xml(bom, result_file)
161
+ else:
162
+ success = False
163
+ error_msg = f'Not supported output_extension({output_extension})'
164
+ except Exception as e:
165
+ success = False
166
+ error_msg = f'Failed to write CycloneDX document: {e}'
167
+ if os.path.exists(result_file):
168
+ os.remove(result_file)
169
+
170
+ return success, error_msg, result_file
171
+
172
+
173
+ def write_cyclonedx_json(bom, result_file):
174
+ success = True
175
+ try:
176
+ my_json_outputter: 'JsonOutputter' = JsonV1Dot6(bom)
177
+ my_json_outputter.output_to_file(result_file)
178
+ serialized_json = my_json_outputter.output_as_string(indent=2)
179
+ my_json_validator = JsonStrictValidator(SchemaVersion.V1_6)
180
+ try:
181
+ validation_errors = my_json_validator.validate_str(serialized_json)
182
+ if validation_errors:
183
+ logger.warning(f'JSON invalid, ValidationError: {repr(validation_errors)}')
184
+ except MissingOptionalDependencyException as error:
185
+ logger.debug(f'JSON-validation was skipped due to {error}')
186
+ except Exception as e:
187
+ logger.warning(f'Fail to write cyclonedx json: {e}')
188
+ success = False
189
+ return success
190
+
191
+
192
+ def write_cyclonedx_xml(bom, result_file):
193
+ success = True
194
+ try:
195
+ my_xml_outputter: BaseOutput = make_outputter(bom=bom,
196
+ output_format=OutputFormat.XML,
197
+ schema_version=SchemaVersion.V1_6)
198
+ my_xml_outputter.output_to_file(filename=result_file)
199
+ serialized_xml = my_xml_outputter.output_as_string(indent=2)
200
+ my_xml_validator = XmlValidator(SchemaVersion.V1_6)
201
+ try:
202
+ validation_errors = my_xml_validator.validate_str(serialized_xml)
203
+ if validation_errors:
204
+ logger.warning(f'XML invalid, ValidationError: {repr(validation_errors)}')
205
+ except MissingOptionalDependencyException as error:
206
+ logger.debug(f'XML-validation was skipped due to {error}')
207
+ except Exception as e:
208
+ logger.warning(f'Fail to write cyclonedx xml: {e}')
209
+ success = False
210
+ return success