Skip to content

Commit 3213622

Browse files
committed
sbom: support versions in PURLs
In case a PURL reference has a version included we use that instead of the pkgver. This is helpful in case the version format is different on pypi for example, or when referencing github and the version tag is prefixed with "v".
1 parent dc1461a commit 3213622

File tree

2 files changed

+105
-50
lines changed

2 files changed

+105
-50
lines changed

msys2_devtools/sbom.py

+60-49
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,64 @@ def parse_cpe(cpe: str) -> tuple[str, str]:
6262
raise ValueError("unknown cpe format")
6363

6464

65+
def generate_components(value) -> list[Component]:
66+
components = []
67+
68+
if not value["srcinfo"].values():
69+
return components
70+
71+
pkgver = ""
72+
pkgbase = ""
73+
for srcinfo in value["srcinfo"].values():
74+
base = parse_srcinfo(srcinfo)[0]
75+
pkgver = extract_upstream_version(base["pkgver"][0])
76+
pkgbase = base["pkgbase"][0]
77+
break
78+
79+
purls: list[PackageURL] = []
80+
cpes: list[str] = []
81+
properties = [Property(name="msys2:pkgbase", value=pkgbase)]
82+
83+
if "extra" in value and "references" in value["extra"]:
84+
pkgextra = extra_to_pkgextra_entry(value["extra"])
85+
for extra_key, extra_values in pkgextra["references"].items():
86+
for extra_value in extra_values:
87+
if extra_key == "pypi":
88+
purls.append(PackageURL('pypi', None, extra_value, pkgver))
89+
elif extra_key == "cpe":
90+
if extra_value.startswith("cpe:"):
91+
extra_value = extra_value[4:]
92+
if extra_value.startswith("2.3:"):
93+
cpe = f"cpe:{extra_value}:*:*:*:*:*:*:*:*"
94+
else:
95+
cpe = f"cpe:{extra_value}:"
96+
cpes.append(cpe)
97+
elif extra_key == "purl":
98+
purl = PackageURL.from_string(extra_value)
99+
if purl.version is None:
100+
purl = PackageURL(**{**purl.to_dict(), "version": pkgver})
101+
purls.append(purl)
102+
103+
for cpe in cpes:
104+
name = parse_cpe(cpe)[1]
105+
component = Component(name=name, version=pkgver, cpe=cpe, properties=properties)
106+
components.append(component)
107+
108+
for purl in purls:
109+
component = Component(name=purl.name, version=purl.version, purl=purl, properties=properties)
110+
components.append(component)
111+
112+
if not cpes and not purls:
113+
if pkgbase.startswith("mingw-w64-"):
114+
name = pkgbase.split("-", 2)[-1]
115+
else:
116+
name = pkgbase
117+
component = Component(name=name, version=pkgver, properties=properties)
118+
components.append(component)
119+
120+
return components
121+
122+
65123
def write_sbom(srcinfo_cache: str, sbom: str) -> None:
66124
bom = Bom()
67125
bom.metadata.component = root_component = Component(
@@ -74,55 +132,8 @@ def write_sbom(srcinfo_cache: str, sbom: str) -> None:
74132
cache = json.loads(gzip.decompress(h.read()))
75133

76134
for value in cache.values():
77-
if not value["srcinfo"].values():
78-
continue
79-
80-
pkgver = ""
81-
pkgbase = ""
82-
for srcinfo in value["srcinfo"].values():
83-
base = parse_srcinfo(srcinfo)[0]
84-
pkgver = extract_upstream_version(base["pkgver"][0])
85-
pkgbase = base["pkgbase"][0]
86-
break
87-
88-
purls: list[PackageURL] = []
89-
cpes: list[str] = []
90-
properties = [Property(name="msys2:pkgbase", value=pkgbase)]
91-
92-
if "extra" in value and "references" in value["extra"]:
93-
pkgextra = extra_to_pkgextra_entry(value["extra"])
94-
for extra_key, extra_values in pkgextra["references"].items():
95-
for extra_value in extra_values:
96-
if extra_key == "pypi":
97-
purls.append(PackageURL('pypi', None, extra_value, pkgver))
98-
elif extra_key == "cpe":
99-
if extra_value.startswith("cpe:"):
100-
extra_value = extra_value[4:]
101-
if extra_value.startswith("2.3:"):
102-
cpe = f"cpe:{extra_value}:*:*:*:*:*:*:*:*"
103-
else:
104-
cpe = f"cpe:{extra_value}:"
105-
cpes.append(cpe)
106-
elif extra_key == "purl":
107-
purls.append(PackageURL.from_string(extra_value + "@" + pkgver))
108-
109-
for cpe in cpes:
110-
name = parse_cpe(cpe)[1]
111-
component = Component(name=name, version=pkgver, cpe=cpe, properties=properties)
112-
bom.components.add(component)
113-
bom.register_dependency(root_component, [component])
114-
115-
for purl in purls:
116-
component = Component(name=purl.name, version=pkgver, purl=purl, properties=properties)
117-
bom.components.add(component)
118-
bom.register_dependency(root_component, [component])
119-
120-
if not cpes and not purls:
121-
if pkgbase.startswith("mingw-w64-"):
122-
name = pkgbase.split("-", 2)[-1]
123-
else:
124-
name = pkgbase
125-
component = Component(name=name, version=pkgver, properties=properties)
135+
components = generate_components(value)
136+
for component in components:
126137
bom.components.add(component)
127138
bom.register_dependency(root_component, [component])
128139

tests/test_sbom.py

+45-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from msys2_devtools.sbom import parse_cpe, extract_upstream_version
1+
from msys2_devtools.sbom import parse_cpe, extract_upstream_version, generate_components
22

33

44
def test_parse_cpe():
@@ -11,3 +11,47 @@ def test_extract_upstream_version():
1111
assert extract_upstream_version("1.2.3+123") == "1.2.3"
1212
assert extract_upstream_version("2~1.2.3") == "1.2.3"
1313
assert extract_upstream_version("2:1.2.3") == "1.2.3"
14+
15+
16+
def test_generate_components():
17+
assert generate_components({"srcinfo": {}}) == []
18+
srcinfo = {"mingw32": "pkgbase = foo\npkgver = 42"}
19+
20+
# none
21+
components = generate_components({"srcinfo": srcinfo, "extra": {"references": []}})
22+
assert components[0].name == "foo"
23+
assert components[0].version == "42"
24+
assert components[0].purl is None
25+
26+
# purl with version
27+
components = generate_components({"srcinfo": srcinfo, "extra": {"references": [
28+
"purl: pkg:pypi/django@1.11.1"
29+
]}})
30+
assert components[0].name == "django"
31+
assert components[0].version == "1.11.1"
32+
assert components[0].purl.to_string() == "pkg:pypi/django@1.11.1"
33+
34+
# purl with commit
35+
components = generate_components({"srcinfo": srcinfo, "extra": {"references": [
36+
"purl: pkg:github/django/django@2d34ebe49a25d0974392583d5"
37+
]}})
38+
assert components[0].name == "django"
39+
assert components[0].version == "2d34ebe49a25d0974392583d5"
40+
assert components[0].purl.to_string() == "pkg:github/django/django@2d34ebe49a25d0974392583d5"
41+
42+
# purl without version
43+
components = generate_components({"srcinfo": srcinfo, "extra": {"references": [
44+
"purl: pkg:pypi/django"
45+
]}})
46+
assert components[0].name == "django"
47+
assert components[0].version == "42"
48+
assert components[0].purl.to_string() == "pkg:pypi/django@42"
49+
50+
# cpe
51+
components = generate_components({"srcinfo": srcinfo, "extra": {"references": [
52+
"cpe: cpe:/a:djangoproject:django"
53+
]}})
54+
assert components[0].name == "django"
55+
assert components[0].version == "42"
56+
assert components[0].purl is None
57+
assert components[0].cpe == "cpe:/a:djangoproject:django:"

0 commit comments

Comments
 (0)