Skip to content

Commit e797550

Browse files
authored
Add support for CycloneDX XML inputs #1136 (#1137)
Signed-off-by: tdruez <tdruez@nexb.com>
1 parent 8afebab commit e797550

File tree

8 files changed

+2502
-46
lines changed

8 files changed

+2502
-46
lines changed

CHANGELOG.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
Changelog
22
=========
33

4+
v34.2.0 (unreleased)
5+
--------------------
6+
7+
- Add support for CycloneDX XML inputs.
8+
https://github.com/nexB/scancode.io/issues/1136
9+
410
v34.1.0 (2024-03-27)
511
--------------------
612

scanpipe/pipes/cyclonedx.py

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
from cyclonedx.schema import SchemaVersion
3333
from cyclonedx.validation import ValidationError
3434
from cyclonedx.validation.json import JsonStrictValidator
35+
from defusedxml import ElementTree as SafeElementTree
3536
from packageurl import PackageURL
3637

3738

@@ -121,10 +122,18 @@ def validate_document(document):
121122

122123
def is_cyclonedx_bom(input_location):
123124
"""Return True if the file at `input_location` is a CycloneDX BOM."""
124-
with suppress(Exception):
125-
data = json.loads(Path(input_location).read_text())
126-
if data.get("bomFormat") == "CycloneDX":
127-
return True
125+
if str(input_location).endswith(".json"):
126+
with suppress(Exception):
127+
data = json.loads(Path(input_location).read_text())
128+
if data.get("bomFormat") == "CycloneDX":
129+
return True
130+
131+
elif str(input_location).endswith(".xml"):
132+
with suppress(Exception):
133+
et = SafeElementTree.parse(input_location)
134+
if "cyclonedx" in et.getroot().tag:
135+
return True
136+
128137
return False
129138

130139

@@ -164,11 +173,6 @@ def cyclonedx_component_to_package_data(cdx_component):
164173
}
165174

166175

167-
def get_bom(cyclonedx_document):
168-
"""Return CycloneDX BOM object."""
169-
return Bom.from_json(data=cyclonedx_document)
170-
171-
172176
def get_components(bom):
173177
"""Return list of components from CycloneDX BOM."""
174178
return list(bom._get_all_components())
@@ -177,13 +181,23 @@ def get_components(bom):
177181
def resolve_cyclonedx_packages(input_location):
178182
"""Resolve the packages from the `input_location` CycloneDX document file."""
179183
input_path = Path(input_location)
180-
cyclonedx_document = json.loads(input_path.read_text())
184+
document_data = input_path.read_text()
181185

182-
if errors := validate_document(cyclonedx_document):
183-
error_msg = f'CycloneDX document "{input_path.name}" is not valid:\n{errors}'
184-
raise ValueError(error_msg)
186+
if str(input_location).endswith(".xml"):
187+
cyclonedx_document = SafeElementTree.fromstring(document_data)
188+
cyclonedx_bom = Bom.from_xml(cyclonedx_document)
185189

186-
cyclonedx_bom = get_bom(cyclonedx_document)
187-
components = get_components(cyclonedx_bom)
190+
elif str(input_location).endswith(".json"):
191+
cyclonedx_document = json.loads(document_data)
192+
if errors := validate_document(cyclonedx_document):
193+
error_msg = (
194+
f'CycloneDX document "{input_path.name}" is not valid:\n{errors}'
195+
)
196+
raise ValueError(error_msg)
197+
cyclonedx_bom = Bom.from_json(data=cyclonedx_document)
198+
199+
else:
200+
return []
188201

202+
components = get_components(cyclonedx_bom)
189203
return [cyclonedx_component_to_package_data(component) for component in components]

scanpipe/pipes/resolve.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -264,17 +264,17 @@ def get_default_package_type(input_location):
264264
if handler.is_datafile(input_location):
265265
return handler.default_package_type
266266

267-
if input_location.endswith((".spdx", ".spdx.json")):
268-
return "spdx"
267+
if input_location.endswith((".spdx", ".spdx.json")):
268+
return "spdx"
269269

270-
if input_location.endswith((".bom.json", ".cdx.json")):
271-
return "cyclonedx"
270+
if input_location.endswith(("bom.json", ".cdx.json", "bom.xml", ".cdx.xml")):
271+
return "cyclonedx"
272272

273-
if input_location.endswith(".json"):
274-
if cyclonedx.is_cyclonedx_bom(input_location):
275-
return "cyclonedx"
276-
if spdx.is_spdx_document(input_location):
277-
return "spdx"
273+
if input_location.endswith((".json", ".xml")):
274+
if cyclonedx.is_cyclonedx_bom(input_location):
275+
return "cyclonedx"
276+
if spdx.is_spdx_document(input_location):
277+
return "spdx"
278278

279279

280280
# Mapping between `default_package_type` its related resolver functions

0 commit comments

Comments
 (0)