77from __future__ import annotations
88
99import hashlib
10+ import re
1011from dataclasses import dataclass
1112from typing import Any
13+ from urllib .parse import quote
1214
1315LANG_TO_PURL_TYPE : dict [str , str ] = {
1416 "golang" : "golang" ,
2022 "generic" : "generic" ,
2123}
2224
25+ _GITHUB_OWNER_REPO_RE = re .compile (
26+ r"(?:git\+)?https?://(?:www\.)?github\.com/([^/\s)>\"']+)/([^/\s)>\"'#]+)" ,
27+ re .IGNORECASE ,
28+ )
29+
2330
2431@dataclass
2532class RpmDep :
@@ -37,12 +44,101 @@ class BundledDep:
3744 path : str
3845 version : str
3946 lang : str # "generic", "golang", "python", ...
47+ vcs_url : str = ""
48+ download_url : str = ""
4049
4150
42- def _bundled_purl (dep : BundledDep ) -> str :
43- purl_type = LANG_TO_PURL_TYPE .get (dep .lang , "generic" )
51+ def _github_owner_repo (url : str ) -> tuple [str , str ] | None :
52+ """Parse ``(owner, repo)`` from a github.com URL, or return ``None``."""
53+ if not url :
54+ return None
55+ match = _GITHUB_OWNER_REPO_RE .search (url )
56+ if not match :
57+ return None
58+ owner = match .group (1 ).lower ().rstrip ("." )
59+ repo = match .group (2 ).lower ().rstrip ("." )
60+ if repo .endswith (".git" ):
61+ repo = repo [:- 4 ]
62+ return owner , repo
63+
64+
65+ def _bundled_purls (dep : BundledDep ) -> list [str ]:
66+ """Return one or more purls for a bundled dependency."""
4467 ver = f"@{ dep .version } " if dep .version else ""
45- return f"pkg:{ purl_type } /{ dep .path } { ver } "
68+ purl_type = LANG_TO_PURL_TYPE .get (dep .lang , "generic" )
69+
70+ if purl_type != "generic" :
71+ base = f"pkg:{ purl_type } /{ dep .path } { ver } "
72+ qualifiers : list [str ] = []
73+ if dep .vcs_url :
74+ qualifiers .append (f"vcs_url={ quote (dep .vcs_url , safe = '' )} " )
75+ if dep .download_url :
76+ qualifiers .append (f"download_url={ quote (dep .download_url , safe = '' )} " )
77+ if qualifiers :
78+ return [f"{ base } ?{ '&' .join (qualifiers )} " ]
79+ return [base ]
80+
81+ github_coords = _github_owner_repo (dep .vcs_url ) or _github_owner_repo (dep .download_url )
82+ non_github_vcs = dep .vcs_url if dep .vcs_url and not _github_owner_repo (dep .vcs_url ) else ""
83+ non_github_download = (
84+ dep .download_url if dep .download_url and not _github_owner_repo (dep .download_url ) else ""
85+ )
86+
87+ if github_coords :
88+ owner , repo = github_coords
89+ purls = [f"pkg:github/{ owner } /{ repo } { ver } " ]
90+ generic_quals : list [str ] = []
91+ if non_github_vcs :
92+ generic_quals .append (f"vcs_url={ quote (non_github_vcs , safe = '' )} " )
93+ if non_github_download :
94+ generic_quals .append (f"download_url={ quote (non_github_download , safe = '' )} " )
95+ if generic_quals :
96+ purls .append (f"pkg:generic/{ dep .path } { ver } ?{ '&' .join (generic_quals )} " )
97+ return purls
98+
99+ base = f"pkg:generic/{ dep .path } { ver } "
100+ qualifiers : list [str ] = []
101+ if dep .vcs_url :
102+ qualifiers .append (f"vcs_url={ quote (dep .vcs_url , safe = '' )} " )
103+ if dep .download_url :
104+ qualifiers .append (f"download_url={ quote (dep .download_url , safe = '' )} " )
105+ if qualifiers :
106+ return [f"{ base } ?{ '&' .join (qualifiers )} " ]
107+ return [base ]
108+
109+
110+ def _bundled_purl (dep : BundledDep ) -> str :
111+ """Primary purl for a bundled dependency (first entry from ``_bundled_purls``)."""
112+ return _bundled_purls (dep )[0 ]
113+
114+
115+ def _bundled_display_lang (dep : BundledDep ) -> str :
116+ if dep .lang != "generic" :
117+ return dep .lang
118+ if _github_owner_repo (dep .vcs_url ) or _github_owner_repo (dep .download_url ):
119+ return "github"
120+ return dep .lang
121+
122+
123+ def _bundled_display_name (dep : BundledDep ) -> str :
124+ if _bundled_display_lang (dep ) == "github" :
125+ coords = _github_owner_repo (dep .vcs_url ) or _github_owner_repo (dep .download_url )
126+ label = f"{ coords [0 ]} /{ coords [1 ]} " if coords else dep .path
127+ else :
128+ label = dep .path
129+ lang = _bundled_display_lang (dep )
130+ name = f"{ label } ({ lang } )"
131+ if dep .version :
132+ name = f"{ name } { dep .version } "
133+ return name
134+
135+
136+ def _download_location (dep : BundledDep ) -> str :
137+ if dep .vcs_url :
138+ return dep .vcs_url
139+ if dep .download_url :
140+ return dep .download_url
141+ return "NOASSERTION"
46142
47143
48144def _dep_lang_from_inner (name_inner : str ) -> tuple [str , str ]:
@@ -138,25 +234,26 @@ def bundled_provides_to_spdx_fragments(
138234 packages : list [dict [str , Any ]] = []
139235 rels : list [dict [str , Any ]] = []
140236 for b in bundled :
141- pid = _ref_id (f"{ b .path } \0 { b .version } \0 { b .lang } " )
142- name = f"{ b .path } ({ b .lang } )"
143- if b .version :
144- name = f"{ name } { b .version } "
237+ purls = _bundled_purls (b )
238+ purl = purls [0 ]
239+ pid = _ref_id (purl )
240+ external_refs = [
241+ {
242+ "referenceCategory" : "PACKAGE-MANAGER" ,
243+ "referenceType" : "purl" ,
244+ "referenceLocator" : locator ,
245+ }
246+ for locator in purls
247+ ]
145248 packages .append (
146249 {
147250 "SPDXID" : pid ,
148- "name" : name ,
251+ "name" : _bundled_display_name ( b ) ,
149252 "versionInfo" : b .version or "NOASSERTION" ,
150- "downloadLocation" : "NOASSERTION" ,
253+ "downloadLocation" : _download_location ( b ) ,
151254 "filesAnalyzed" : False ,
152255 "primaryPackagePurpose" : "LIBRARY" ,
153- "externalRefs" : [
154- {
155- "referenceCategory" : "PACKAGE-MANAGER" ,
156- "referenceType" : "purl" ,
157- "referenceLocator" : _bundled_purl (b ),
158- }
159- ],
256+ "externalRefs" : external_refs ,
160257 }
161258 )
162259 rels .append (
@@ -174,14 +271,11 @@ def bundled_provides_to_cdx_components(bundled: list[BundledDep]) -> list[dict[s
174271 components : list [dict [str , Any ]] = []
175272 for b in bundled :
176273 purl = _bundled_purl (b )
177- name = f"{ b .path } ({ b .lang } )"
178- if b .version :
179- name = f"{ name } { b .version } "
180274 components .append (
181275 {
182276 "bom-ref" : purl ,
183277 "type" : "library" ,
184- "name" : name ,
278+ "name" : _bundled_display_name ( b ) ,
185279 "version" : b .version or None ,
186280 "purl" : purl ,
187281 }
0 commit comments