From 738c4978e0fdcc76a67629d3377b264401cd3f52 Mon Sep 17 00:00:00 2001 From: Tushar Goel Date: Mon, 28 Jul 2025 16:18:29 +0530 Subject: [PATCH 1/7] Add support for batch 1 download URLs Signed-off-by: Tushar Goel --- src/packageurl/contrib/purl2url.py | 66 ++++++++++++++++++++++++++++++ tests/contrib/test_purl2url.py | 8 +++- 2 files changed, 72 insertions(+), 2 deletions(-) diff --git a/src/packageurl/contrib/purl2url.py b/src/packageurl/contrib/purl2url.py index a2dfff2..1eb07e5 100644 --- a/src/packageurl/contrib/purl2url.py +++ b/src/packageurl/contrib/purl2url.py @@ -443,6 +443,72 @@ def build_repo_download_url(purl): return get_repo_download_url(purl) +@download_router.route("pkg:hex/.*") +def build_hex_download_url(purl): + """ + Return a hex download URL from the `purl` string. + """ + purl_data = PackageURL.from_string(purl) + + name = purl_data.name + version = purl_data.version + + if name and version: + return f"https://repo.hex.pm/tarballs/{name}-{version}.tar" + + +@download_router.route("pkg:golang/.*") +def build_golang_download_url(purl): + """ + Return a golang download URL from the `purl` string. + """ + purl_data = PackageURL.from_string(purl) + + namespace = purl_data.namespace + name = purl_data.name + version = purl_data.version + + if not name: + return + + if namespace: + name = f"{namespace}/{name}" + + if name and version: + return f"https://proxy.golang.org/{name}/@v/{version}.zip" + + +@download_router.route("pkg:pub/.*") +def build_pub_download_url(purl): + """ + Return a pub download URL from the `purl` string. + """ + purl_data = PackageURL.from_string(purl) + + name = purl_data.name + version = purl_data.version + + if name and version: + return f"https://pub.dev/api/archives/{name}-{version}.tar.gz" + + +@download_router.route("pkg:swift/.*") +def build_swift_download_url(purl): + """ + Return a Swift Package download URL from the `purl` string. + """ + purl_data = PackageURL.from_string(purl) + + name = purl_data.name + version = purl_data.version + namespace = purl_data.namespace + + if not (namespace or name or version): + return + + return f"https://{namespace}/{name}/archive/{version}.zip" + + def get_repo_download_url(purl): """ Return ``download_url`` if present in ``purl`` qualifiers or diff --git a/tests/contrib/test_purl2url.py b/tests/contrib/test_purl2url.py index fee98d4..35db4fb 100644 --- a/tests/contrib/test_purl2url.py +++ b/tests/contrib/test_purl2url.py @@ -98,6 +98,12 @@ def test_purl2url_get_download_url(): "pkg:maven/org.apache.commons/commons-io@1.3.2?repository_url=https://repo1.maven.org/maven2": "https://repo1.maven.org/maven2/org/apache/commons/commons-io/1.3.2/commons-io-1.3.2.jar", "pkg:maven/org.apache.commons/commons-io@1.3.2?type=pom": "https://repo.maven.apache.org/maven2/org/apache/commons/commons-io/1.3.2/commons-io-1.3.2.pom", "pkg:maven/org.apache.commons/commons-math3@3.6.1?classifier=sources": "https://repo.maven.apache.org/maven2/org/apache/commons/commons-math3/3.6.1/commons-math3-3.6.1-sources.jar", + "pkg:hex/plug@1.11.1": "https://repo.hex.pm/tarballs/plug-1.11.1.tar", + "pkg:golang/xorm.io/xorm@v0.8.2": "https://proxy.golang.org/xorm.io/xorm/@v/v0.8.2.zip", + "pkg:golang/gopkg.in/ldap.v3@v3.1.0": "https://proxy.golang.org/gopkg.in/ldap.v3/@v/v3.1.0.zip", + "pkg:pub/http@0.13.3": "https://pub.dev/api/archives/http-0.13.3.tar.gz", + "pkg:swift/github.com/Alamofire/Alamofire@5.4.3": "https://github.com/Alamofire/Alamofire/archive/5.4.3.zip", + "pkg:swift/github.com/RxSwiftCommunity/RxFlow@2.12.4": "https://github.com/RxSwiftCommunity/RxFlow/archive/2.12.4.zip", # From `download_url` qualifier "pkg:github/yarnpkg/yarn@1.3.2?download_url=https://github.com/yarnpkg/yarn/releases/download/v1.3.2/yarn-v1.3.2.tar.gz&version_prefix=v": "https://github.com/yarnpkg/yarn/releases/download/v1.3.2/yarn-v1.3.2.tar.gz", "pkg:generic/lxc-master.tar.gz?download_url=https://salsa.debian.org/lxc-team/lxc/-/archive/master/lxc-master.tar.gz": "https://salsa.debian.org/lxc-team/lxc/-/archive/master/lxc-master.tar.gz", @@ -112,8 +118,6 @@ def test_purl2url_get_download_url(): "pkg:bitbucket/birkenfeld": None, "pkg:pypi/sortedcontainers@2.4.0": None, "pkg:composer/psr/log@1.1.3": None, - "pkg:golang/xorm.io/xorm@v0.8.2": None, - "pkg:golang/gopkg.in/ldap.v3@v3.1.0": None, } for purl, url in purls_url.items(): From d899eff5d3ecbc88b22b82f2cab1e21bf007fe64 Mon Sep 17 00:00:00 2001 From: Tushar Goel Date: Mon, 28 Jul 2025 16:24:11 +0530 Subject: [PATCH 2/7] Fix linting issues Signed-off-by: Tushar Goel --- src/packageurl/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/packageurl/__init__.py b/src/packageurl/__init__.py index d1e6837..2b7e052 100644 --- a/src/packageurl/__init__.py +++ b/src/packageurl/__init__.py @@ -72,7 +72,7 @@ def unquote(s: AnyStr) -> str: Return a percent-decoded unicode string, given an `s` byte or unicode string. """ - unquoted = _percent_unquote(s) # type:ignore[arg-type] # typeshed is incorrect here + unquoted = _percent_unquote(s) if not isinstance(unquoted, str): unquoted = unquoted.decode("utf-8") return unquoted From 957430e1639e5694eaf00b6dde05bad314d50785 Mon Sep 17 00:00:00 2001 From: Tushar Goel Date: Mon, 28 Jul 2025 21:06:48 +0530 Subject: [PATCH 3/7] Support golang purls with upper case characters Signed-off-by: Tushar Goel --- src/packageurl/contrib/purl2url.py | 26 +++++++++++++++++++++++++- tests/contrib/test_purl2url.py | 1 + 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/packageurl/contrib/purl2url.py b/src/packageurl/contrib/purl2url.py index 1eb07e5..445f5ee 100644 --- a/src/packageurl/contrib/purl2url.py +++ b/src/packageurl/contrib/purl2url.py @@ -474,8 +474,11 @@ def build_golang_download_url(purl): if namespace: name = f"{namespace}/{name}" + ename = escape_path(name) + eversion = escape_path(version) + if name and version: - return f"https://proxy.golang.org/{name}/@v/{version}.zip" + return f"https://proxy.golang.org/{ename}/@v/{eversion}.zip" @download_router.route("pkg:pub/.*") @@ -536,3 +539,24 @@ def get_repo_download_url(purl): return get_repo_download_url_by_package_type( type=type, namespace=namespace, name=name, version=version ) + + +# TODO: https://github.com/package-url/packageurl-python/issues/196 +def escape_path(path: str) -> str: + """ + Return an case-encoded module path or version name. + + This is done by replacing every uppercase letter with an exclamation mark followed by the + corresponding lower-case letter, in order to avoid ambiguity when serving from case-insensitive + file systems. + + See https://golang.org/ref/mod#goproxy-protocol. + """ + escaped_path = "" + for c in path: + if c >= "A" and c <= "Z": + # replace uppercase with !lowercase + escaped_path += "!" + chr(ord(c) + ord("a") - ord("A")) + else: + escaped_path += c + return escaped_path diff --git a/tests/contrib/test_purl2url.py b/tests/contrib/test_purl2url.py index 35db4fb..52d1ec3 100644 --- a/tests/contrib/test_purl2url.py +++ b/tests/contrib/test_purl2url.py @@ -101,6 +101,7 @@ def test_purl2url_get_download_url(): "pkg:hex/plug@1.11.1": "https://repo.hex.pm/tarballs/plug-1.11.1.tar", "pkg:golang/xorm.io/xorm@v0.8.2": "https://proxy.golang.org/xorm.io/xorm/@v/v0.8.2.zip", "pkg:golang/gopkg.in/ldap.v3@v3.1.0": "https://proxy.golang.org/gopkg.in/ldap.v3/@v/v3.1.0.zip", + "pkg:golang/example.com/M.v3@v3.1.0": "https://proxy.golang.org/example.com/!m.v3/@v/v3.1.0.zip", "pkg:pub/http@0.13.3": "https://pub.dev/api/archives/http-0.13.3.tar.gz", "pkg:swift/github.com/Alamofire/Alamofire@5.4.3": "https://github.com/Alamofire/Alamofire/archive/5.4.3.zip", "pkg:swift/github.com/RxSwiftCommunity/RxFlow@2.12.4": "https://github.com/RxSwiftCommunity/RxFlow/archive/2.12.4.zip", From 027173b77d61c32705dfc69460f1955696abc0ee Mon Sep 17 00:00:00 2001 From: Tushar Goel Date: Tue, 29 Jul 2025 11:17:49 +0530 Subject: [PATCH 4/7] Add todo issue Signed-off-by: Tushar Goel --- src/packageurl/contrib/purl2url.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/packageurl/contrib/purl2url.py b/src/packageurl/contrib/purl2url.py index 445f5ee..71cb1fb 100644 --- a/src/packageurl/contrib/purl2url.py +++ b/src/packageurl/contrib/purl2url.py @@ -471,6 +471,7 @@ def build_golang_download_url(purl): if not name: return + # TODO: https://github.com/package-url/packageurl-python/issues/197 if namespace: name = f"{namespace}/{name}" From cea437be9dad915980558e757ff1eec13d98027b Mon Sep 17 00:00:00 2001 From: Tushar Goel Date: Tue, 29 Jul 2025 12:35:17 +0530 Subject: [PATCH 5/7] Address review comments Signed-off-by: Tushar Goel --- src/packageurl/contrib/purl2url.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/packageurl/contrib/purl2url.py b/src/packageurl/contrib/purl2url.py index 71cb1fb..6926150 100644 --- a/src/packageurl/contrib/purl2url.py +++ b/src/packageurl/contrib/purl2url.py @@ -475,8 +475,8 @@ def build_golang_download_url(purl): if namespace: name = f"{namespace}/{name}" - ename = escape_path(name) - eversion = escape_path(version) + ename = escape_golang_path(name) + eversion = escape_golang_path(version) if name and version: return f"https://proxy.golang.org/{ename}/@v/{eversion}.zip" @@ -543,7 +543,7 @@ def get_repo_download_url(purl): # TODO: https://github.com/package-url/packageurl-python/issues/196 -def escape_path(path: str) -> str: +def escape_golang_path(path: str) -> str: """ Return an case-encoded module path or version name. From 240106e11ac879947f428bf05bda5d6cac43a164 Mon Sep 17 00:00:00 2001 From: Tushar Goel Date: Tue, 29 Jul 2025 12:38:49 +0530 Subject: [PATCH 6/7] Add CHANGELOG Signed-off-by: Tushar Goel --- CHANGELOG.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index b8288c7..bd906c3 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,12 @@ Changelog ========= +0.17.2 (2025-07-29) +------------------- + +- Add support for getting download URL for Golang, Hex, Pub and Swift in ``purl2url``. + https://github.com/package-url/packageurl-python/pull/195 + 0.17.1 (2025-06-06) ------------------- From 9a0bbc1cefd3eab43477ca7ab1cf526a944ce784 Mon Sep 17 00:00:00 2001 From: Tushar Goel Date: Tue, 29 Jul 2025 12:39:45 +0530 Subject: [PATCH 7/7] Bump version Signed-off-by: Tushar Goel --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 1391450..1c187d6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = packageurl-python -version = 0.17.1 +version = 0.17.2 license = MIT description = A purl aka. Package URL parser and builder long_description = file:README.rst