Skip to content

Commit c2c9bc1

Browse files
committed
Add support for fetching Package URLs (fetch_package_url) #1383
Signed-off-by: tdruez <[email protected]>
1 parent b1d0f95 commit c2c9bc1

File tree

2 files changed

+36
-0
lines changed

2 files changed

+36
-0
lines changed

scanpipe/pipes/fetch.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@
3939
from commoncode import command
4040
from commoncode.hash import multi_checksums
4141
from commoncode.text import python_safe_name
42+
from packageurl import PackageURL
43+
from packageurl.contrib import purl2url
4244
from plugincode.location_provider import get_location
4345
from requests import auth as request_auth
4446

@@ -356,6 +358,17 @@ def fetch_git_repo(url, to=None):
356358
)
357359

358360

361+
def fetch_package_url(url):
362+
# Ensure the provided Package URL is valid, or raise a ValueError.
363+
PackageURL.from_string(url)
364+
365+
# Resolve a Download URL using purl2url.
366+
if download_url := purl2url.get_download_url(url):
367+
return fetch_http(download_url)
368+
369+
raise ValueError(f"Could not resolve a download URL for {url}.")
370+
371+
359372
SCHEME_TO_FETCHER_MAPPING = {
360373
"http": fetch_http,
361374
"https": fetch_http,
@@ -371,6 +384,9 @@ def get_fetcher(url):
371384
if url.rstrip("/").endswith(".git"):
372385
return fetch_git_repo
373386

387+
if url.startswith("pkg:"):
388+
return fetch_package_url
389+
374390
# Not using `urlparse(url).scheme` for the scheme as it converts to lower case.
375391
scheme = url.split("://")[0]
376392

scanpipe/tests/pipes/test_fetch.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ def test_scanpipe_pipes_fetch_get_fetcher(self):
4242
git_http_url = "https://github.com/aboutcode-org/scancode.io.git"
4343
self.assertEqual(fetch.fetch_git_repo, fetch.get_fetcher(git_http_url))
4444
self.assertEqual(fetch.fetch_git_repo, fetch.get_fetcher(git_http_url + "/"))
45+
self.assertEqual(fetch.fetch_package_url, fetch.get_fetcher("pkg:npm/[email protected]"))
4546

4647
with self.assertRaises(ValueError) as cm:
4748
fetch.get_fetcher("")
@@ -88,6 +89,25 @@ def test_scanpipe_pipes_fetch_http(self, mock_get):
8889
downloaded_file = fetch.fetch_http(url)
8990
self.assertTrue(Path(downloaded_file.directory, "another_name.zip").exists())
9091

92+
@mock.patch("requests.sessions.Session.get")
93+
def test_scanpipe_pipes_fetch_package_url(self, mock_get):
94+
package_url = "pkg:not_a_valid_purl"
95+
with self.assertRaises(ValueError) as cm:
96+
fetch.fetch_package_url(package_url)
97+
expected = f"purl is missing the required type component: '{package_url}'."
98+
self.assertEqual(expected, str(cm.exception))
99+
100+
package_url = "pkg:generic/name@version"
101+
with self.assertRaises(ValueError) as cm:
102+
fetch.fetch_package_url(package_url)
103+
expected = f"Could not resolve a download URL for {package_url}."
104+
self.assertEqual(expected, str(cm.exception))
105+
106+
package_url = "pkg:npm/[email protected]"
107+
mock_get.return_value = make_mock_response(url="https://exa.com/filename.zip")
108+
downloaded_file = fetch.fetch_package_url(package_url)
109+
self.assertTrue(Path(downloaded_file.directory, "filename.zip").exists())
110+
91111
@mock.patch("scanpipe.pipes.fetch.get_docker_image_platform")
92112
@mock.patch("scanpipe.pipes.fetch._get_skopeo_location")
93113
@mock.patch("scanpipe.pipes.fetch.run_command_safely")

0 commit comments

Comments
 (0)