diff --git a/ChangeLog.md b/ChangeLog.md index 113d529..71e58f9 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -22,6 +22,7 @@ components have been scanned and how many warnings and errors there are. * Adapt `getdependencies python` to the Poetry 2.x pyproject.toml format. * `getdependencies python` now also supports uv and its `uv.lock` file. +* Have correct `file:///` uri for files in SBOM external references. ## 2.9.1 diff --git a/capycli/bom/download_sources.py b/capycli/bom/download_sources.py index 83f1ac5..cf5493b 100644 --- a/capycli/bom/download_sources.py +++ b/capycli/bom/download_sources.py @@ -110,14 +110,17 @@ def download_sources(self, sbom: Bom, source_folder: str) -> None: new = False ext_ref = CycloneDxSupport.get_ext_ref( component, ExternalReferenceType.DISTRIBUTION, CaPyCliBom.SOURCE_FILE_COMMENT) + file_uri = path + if not file_uri.startswith("file://"): + file_uri = "file:///" + file_uri if not ext_ref: ext_ref = ExternalReference( type=ExternalReferenceType.DISTRIBUTION, comment=CaPyCliBom.SOURCE_FILE_COMMENT, - url=XsUri(path)) + url=XsUri(file_uri)) new = True else: - ext_ref.url = XsUri(path) + ext_ref.url = XsUri(file_uri) ext_ref.hashes.add(HashType( alg=HashAlgorithm.SHA_1, content=sha1)) @@ -188,7 +191,7 @@ def run(self, args: Any) -> None: sys.exit(ResultCode.RESULT_ERROR_READING_BOM) if args.verbose: - print_text(" " + str(len(bom.components)) + "components written to SBOM file") + print_text(" " + str(len(bom.components)) + "components read from SBOM file") source_folder = "./" if args.source: diff --git a/capycli/bom/legacy.py b/capycli/bom/legacy.py index 099d342..36519d9 100644 --- a/capycli/bom/legacy.py +++ b/capycli/bom/legacy.py @@ -205,6 +205,8 @@ def legacy_component_to_cdx(item: Dict[str, Any]) -> Component: binaryFile = item.get("BinaryFile", "") if binaryFile: + if not binaryFile.startswith("file://"): + binaryFile = "file:///" + binaryFile ext_ref = ExternalReference( type=ExternalReferenceType.DISTRIBUTION, comment=CaPyCliBom.BINARY_FILE_COMMENT, diff --git a/capycli/bom/legacy_cx.py b/capycli/bom/legacy_cx.py index c162031..7fd3dde 100644 --- a/capycli/bom/legacy_cx.py +++ b/capycli/bom/legacy_cx.py @@ -1,5 +1,5 @@ # ------------------------------------------------------------------------------- -# Copyright (c) 2023 Siemens +# Copyright (c) 2023-2025 Siemens # All Rights Reserved. # Author: thomas.graf@siemens.com # @@ -66,10 +66,13 @@ def _convert_component(cls, component: Component) -> Component: # extra handling prop = CycloneDxSupport.get_property(component, "source-file") if prop: + file_uri = prop.value + if not file_uri.startswith("file://"): + file_uri = "file:///" + file_uri ext_ref = ExternalReference( type=ExternalReferenceType.DISTRIBUTION, comment=CaPyCliBom.SOURCE_FILE_COMMENT, - url=XsUri(prop.value)) + url=XsUri(file_uri)) prop2 = CycloneDxSupport.get_property(component, "source-file-hash") if prop2: ext_ref.hashes.add(HashType( @@ -80,10 +83,13 @@ def _convert_component(cls, component: Component) -> Component: prop = CycloneDxSupport.get_property(component, "source-file-url") if prop: + file_uri = prop.value + if not file_uri.startswith("file://"): + file_uri = "file:///" + file_uri ext_ref = ExternalReference( type=ExternalReferenceType.DISTRIBUTION, comment=CaPyCliBom.SOURCE_URL_COMMENT, - url=XsUri(prop.value)) + url=XsUri(file_uri)) prop2 = CycloneDxSupport.get_property(component, "source-file-hash") if prop2: ext_ref.hashes.add(HashType( @@ -112,10 +118,13 @@ def _convert_component(cls, component: Component) -> Component: prop = CycloneDxSupport.get_property(component, "binary-file") if prop: + file_uri = prop.value + if not file_uri.startswith("file://"): + file_uri = "file:///" + file_uri ext_ref = ExternalReference( type=ExternalReferenceType.DISTRIBUTION, comment=CaPyCliBom.BINARY_FILE_COMMENT, - url=XsUri(prop.value)) + url=XsUri(file_uri)) prop2 = CycloneDxSupport.get_property(component, "binary-file-hash") if prop2: ext_ref.hashes.add(HashType( diff --git a/capycli/common/capycli_bom_support.py b/capycli/common/capycli_bom_support.py index c330d75..bdb659f 100644 --- a/capycli/common/capycli_bom_support.py +++ b/capycli/common/capycli_bom_support.py @@ -163,12 +163,18 @@ def update_or_set_ext_ref(comp: Component, type: ExternalReferenceType, comment: @staticmethod def have_relative_ext_ref_path(ext_ref: ExternalReference, rel_to: str) -> str: if isinstance(ext_ref.url, str): - bip = pathlib.PurePath(ext_ref.url) + check_val = ext_ref.url._uri + if check_val.startswith("file:///"): + check_val = check_val[8:] + bip = pathlib.PurePath(check_val) else: - bip = pathlib.PurePath(ext_ref.url._uri) + check_val = ext_ref.url._uri + if check_val.startswith("file:///"): + check_val = check_val[8:] + bip = pathlib.PurePath(check_val) file = bip.as_posix() if os.path.isfile(file): - ext_ref.url = XsUri("file://" + bip.relative_to(rel_to).as_posix()) + ext_ref.url = XsUri("file:///" + bip.relative_to(rel_to).as_posix()) return bip.name @staticmethod @@ -223,7 +229,9 @@ def get_ext_ref_source_file(comp: Component) -> str: if (ext_ref.type == ExternalReferenceType.DISTRIBUTION) \ and (ext_ref.comment == CaPyCliBom.SOURCE_FILE_COMMENT): url = str(ext_ref.url) - if url.startswith("file://"): + if url.startswith("file:///"): + return url[8:] + elif url.startswith("file://"): return url[7:] else: return url @@ -245,7 +253,9 @@ def get_ext_ref_binary_file(comp: Component) -> str: if (ext_ref.type == ExternalReferenceType.DISTRIBUTION) \ and (ext_ref.comment == CaPyCliBom.BINARY_FILE_COMMENT): url = str(ext_ref.url) - if url.startswith("file://"): + if url.startswith("file:///"): + return url[8:] + elif url.startswith("file://"): return url[7:] else: return url diff --git a/capycli/dependencies/javascript.py b/capycli/dependencies/javascript.py index 13b3c69..2e8bfc1 100644 --- a/capycli/dependencies/javascript.py +++ b/capycli/dependencies/javascript.py @@ -62,6 +62,8 @@ def get_dependency(self, data: Dict[str, Any], sbom: Bom) -> Bom: url = dep.get("resolved", "").split('/')[-1] if url: + if not url.startswith("file://"): + url = "file:///" + url ext_ref = ExternalReference( type=ExternalReferenceType.DISTRIBUTION, comment=CaPyCliBom.BINARY_FILE_COMMENT, @@ -131,6 +133,8 @@ def get_dependency_lockversion3(self, data: Dict[str, Any], sbom: Bom) -> Bom: url = dep.get("resolved", "").split('/')[-1] if url: + if not url.startswith("file://"): + url = "file:///" + url ext_ref = ExternalReference( type=ExternalReferenceType.DISTRIBUTION, comment=CaPyCliBom.BINARY_FILE_COMMENT, diff --git a/capycli/dependencies/python.py b/capycli/dependencies/python.py index 6888176..586324b 100644 --- a/capycli/dependencies/python.py +++ b/capycli/dependencies/python.py @@ -314,10 +314,13 @@ def add_meta_data_to_bomitem(self, cxcomp: Component, package_source: str = "") for item in meta["urls"]: if "packagetype" in item: if item["packagetype"] == "bdist_wheel": + file_uri = item["filename"] + if not file_uri.startswith("file://"): + file_uri = "file:///" + file_uri ext_ref = ExternalReference( type=ExternalReferenceType.DISTRIBUTION, comment=CaPyCliBom.BINARY_FILE_COMMENT, - url=XsUri(item["filename"])) + url=XsUri(file_uri)) cxcomp.external_references.add(ext_ref) LOG.debug(" got binary file") @@ -329,10 +332,13 @@ def add_meta_data_to_bomitem(self, cxcomp: Component, package_source: str = "") LOG.debug(" got binary file url") if item["packagetype"] == "sdist": + file_uri = item["filename"] + if not file_uri.startswith("file://"): + file_uri = "file:///" + file_uri ext_ref = ExternalReference( type=ExternalReferenceType.DISTRIBUTION, comment=CaPyCliBom.SOURCE_FILE_COMMENT, - url=XsUri(item["filename"])) + url=XsUri(file_uri)) cxcomp.external_references.add(ext_ref) LOG.debug(" got source file") diff --git a/tests/test_bom_downloadsources.py b/tests/test_bom_downloadsources.py index dc2bacf..aa7c8b2 100644 --- a/tests/test_bom_downloadsources.py +++ b/tests/test_bom_downloadsources.py @@ -1,5 +1,5 @@ # ------------------------------------------------------------------------------- -# Copyright (c) 2023-2024 Siemens +# Copyright (c) 2023-2025 Siemens # All Rights Reserved. # Author: thomas.graf@siemens.com # @@ -130,7 +130,7 @@ def test_simple_bom(self) -> None: try: out = self.capture_stdout(sut.run, args) out_bom = CaPyCliBom.read_sbom(args.outputfile) - # capycli.common.json_support.write_json_to_file(out, "STDOUT.TXT") + # json_support.write_json_to_file(out, "STDOUT.TXT") self.assertTrue("Loading SBOM file" in out) self.assertTrue("sbom_for_download.json" in out) # path may vary self.assertIn("SBOM file is not relative to", out) @@ -144,11 +144,10 @@ def test_simple_bom(self) -> None: out_bom.components[0], ExternalReferenceType.DISTRIBUTION, CaPyCliBom.SOURCE_FILE_COMMENT) self.assertIsNotNone(ext_ref) if ext_ref: # only for mypy - self.assertEqual(ext_ref.url._uri, resultfile) - # if ext_ref.url is XsUri: - # self.assertEqual(ext_ref.url._uri, resultfile) - # else: - # self.assertEqual(ext_ref.url, resultfile) + check_val = ext_ref.url._uri + if check_val.startswith("file:///"): + check_val = check_val[8:] + self.assertEqual(check_val, resultfile) self.delete_file(args.outputfile) return @@ -192,7 +191,7 @@ def test_simple_bom_relative_path(self) -> None: out_bom.components[0], ExternalReferenceType.DISTRIBUTION, CaPyCliBom.SOURCE_FILE_COMMENT) self.assertIsNotNone(ext_ref) if ext_ref: # only for mypy - self.assertEqual(ext_ref.url._uri, "file://certifi-2022.12.7.tar.gz") + self.assertEqual(ext_ref.url._uri, "file:///certifi-2022.12.7.tar.gz") self.delete_file(args.outputfile) return @@ -275,7 +274,10 @@ def test_simple_bom_no_url(self) -> None: bom.components[0], ExternalReferenceType.DISTRIBUTION, CaPyCliBom.SOURCE_FILE_COMMENT) self.assertIsNotNone(ext_ref) if ext_ref: # only for mypy - self.assertEqual(str(ext_ref.url), resultfile) + check_val = ext_ref.url._uri + if check_val.startswith("file:///"): + check_val = check_val[8:] + self.assertEqual(check_val, resultfile) self.assertEqual(len(bom.components[1].external_references), 0) return @@ -288,4 +290,4 @@ def test_simple_bom_no_url(self) -> None: if __name__ == "__main__": lib = TestBomDownloadsources() - lib.test_simple_bom() + lib.test_simple_bom_relative_path()