From f4f59727bfbbce7f9a21994cfca7113b8ae9536e Mon Sep 17 00:00:00 2001 From: Chin Yeung Li Date: Wed, 11 Jun 2025 15:02:28 +0800 Subject: [PATCH] Verify package type and correct test data Signed-off-by: Chin Yeung Li --- src/packageurl/__init__.py | 86 +++++++++++++++++++++++++++++++++ tests/contrib/test_purl2url.py | 8 +-- tests/contrib/test_utils.py | 19 +++++--- tests/data/test-suite-data.json | 12 +++++ 4 files changed, 113 insertions(+), 12 deletions(-) diff --git a/src/packageurl/__init__.py b/src/packageurl/__init__.py index b47902d..1fa3324 100644 --- a/src/packageurl/__init__.py +++ b/src/packageurl/__init__.py @@ -47,6 +47,90 @@ # Python 3 basestring = (bytes, str) +# These are the (78) known purl types listed in the purl-spec as on +# 2025-06-11 +# https://github.com/package-url/purl-spec/blob/main/PURL-TYPES.rst +known_purl_type = [ + "alpm", + "android", + "apache", + "apk", + "atom", + "bitbucket", + "bitnami", + "bower", + "brew", + "buildroot", + "cargo", + "carthage", + "chef", + "chocolatey", + "clojars", + "cocoapods", + "composer", + "conan", + "conda", + "coreos", + "cpan", + "cran", + "crystal", + "ctan", + "deb", + "docker", + "drupal", + "dtype", + "dub", + "ebuild", + "eclipse", + "elm", + "gem", + "generic", + "gitea", + "github", + "gitlab", + "golang", + "gradle", + "guix", + "hackage", + "haxe", + "helm", + "hex", + "huggingface", + "julia", + "luarocks", + "maven", + "melpa", + "meteor", + "mlflow", + "nim", + "nix", + "npm", + "nuget", + "oci", + "opam", + "openwrt", + "osgi", + "p2", + "pear", + "pecl", + "perl6", + "platformio", + "pub", + "puppet", + "pypi", + "qpkg", + "rpm", + "sourceforge", + "sublime", + "swid", + "swift", + "terraform", + "vagrant", + "vim", + "wordpress", + "yocto", +] + """ A purl (aka. Package URL) implementation as specified at: https://github.com/package-url/purl-spec @@ -348,6 +432,8 @@ def __new__( ) for key, value in strings.items(): + if key == "type" and value not in known_purl_type: + raise ValueError(f"Invalid purl: unkown package type: {value!r}.") if value and isinstance(value, basestring) or not value: continue raise ValueError(f"Invalid purl: {key} argument must be a string: {value!r}.") diff --git a/tests/contrib/test_purl2url.py b/tests/contrib/test_purl2url.py index fee98d4..b3e2376 100644 --- a/tests/contrib/test_purl2url.py +++ b/tests/contrib/test_purl2url.py @@ -39,8 +39,8 @@ def test_purl2url_get_repo_url(): "pkg:cargo/rand@0.7.2": "https://crates.io/crates/rand/0.7.2", "pkg:cargo/abc": "https://crates.io/crates/abc", "pkg:gem/bundler@2.3.23": "https://rubygems.org/gems/bundler/versions/2.3.23", - "pkg:rubygems/bundler@2.3.23": "https://rubygems.org/gems/bundler/versions/2.3.23", - "pkg:rubygems/package-name": "https://rubygems.org/gems/package-name", + "pkg:gem/bundler@2.3.23": "https://rubygems.org/gems/bundler/versions/2.3.23", + "pkg:gem/package-name": "https://rubygems.org/gems/package-name", "pkg:bitbucket/birkenfeld/pygments-main": "https://bitbucket.org/birkenfeld/pygments-main", "pkg:bitbucket/birkenfeld/pygments-main@244fd47e07d1014f0aed9c": "https://bitbucket.org/birkenfeld/pygments-main", "pkg:bitbucket/birkenfeld/pygments-main@master#views": "https://bitbucket.org/birkenfeld/pygments-main", @@ -108,7 +108,7 @@ def test_purl2url_get_download_url(): # Not-supported "pkg:github/tg1999/fetchcode": None, "pkg:cargo/abc": None, - "pkg:rubygems/package-name": None, + "pkg:gem/package-name": None, "pkg:bitbucket/birkenfeld": None, "pkg:pypi/sortedcontainers@2.4.0": None, "pkg:composer/psr/log@1.1.3": None, @@ -155,7 +155,7 @@ def test_purl2url_get_inferred_urls(): "pkg:pypi/sortedcontainers@2.4.0": ["https://pypi.org/project/sortedcontainers/2.4.0/"], "pkg:cocoapods/AFNetworking@4.0.1": ["https://cocoapods.org/pods/AFNetworking"], "pkg:composer/psr/log@1.1.3": ["https://packagist.org/packages/psr/log#1.1.3"], - "pkg:rubygems/package-name": ["https://rubygems.org/gems/package-name"], + "pkg:gem/package-name": ["https://rubygems.org/gems/package-name"], "pkg:maven/org.apache.commons/commons-io@1.3.2": [ "https://repo.maven.apache.org/maven2/org/apache/commons/commons-io/1.3.2", "https://repo.maven.apache.org/maven2/org/apache/commons/commons-io/1.3.2/commons-io-1.3.2.jar", diff --git a/tests/contrib/test_utils.py b/tests/contrib/test_utils.py index 9e3b04a..32ec6b4 100644 --- a/tests/contrib/test_utils.py +++ b/tests/contrib/test_utils.py @@ -32,10 +32,11 @@ def test_purl_to_lookups_without_encode(): assert purl_to_lookups( - purl_str="pkg:alpine/openssl@0?arch=aarch64&distroversion=edge&reponame=main", + purl_str="pkg:apk/alpine/openssl@0?arch=aarch64&distroversion=edge&reponame=main", encode=False, ) == { - "type": "alpine", + "type": "apk", + "namespace": "alpine", "name": "openssl", "version": "0", "qualifiers": { @@ -48,10 +49,11 @@ def test_purl_to_lookups_without_encode(): def test_purl_to_lookups_with_encode(): assert purl_to_lookups( - purl_str="pkg:alpine/openssl@0?arch=aarch64&distroversion=edge&reponame=main", + purl_str="pkg:apk/alpine/openssl@0?arch=aarch64&distroversion=edge&reponame=main", encode=True, ) == { - "type": "alpine", + "type": "apk", + "namespace": "alpine", "name": "openssl", "version": "0", "qualifiers": "arch=aarch64&distroversion=edge&reponame=main", @@ -59,14 +61,15 @@ def test_purl_to_lookups_with_encode(): def test_purl_to_lookups_include_empty_fields(): - purl_str = "pkg:alpine/openssl" + purl_str = "pkg:apk/alpine/openssl" assert purl_to_lookups(purl_str) == { - "type": "alpine", + "type": "apk", + "namespace": "alpine", "name": "openssl", } assert purl_to_lookups(purl_str, include_empty_fields=True) == { - "type": "alpine", - "namespace": "", + "type": "apk", + "namespace": "alpine", "name": "openssl", "version": "", "qualifiers": "", diff --git a/tests/data/test-suite-data.json b/tests/data/test-suite-data.json index 571c568..d2b632b 100644 --- a/tests/data/test-suite-data.json +++ b/tests/data/test-suite-data.json @@ -382,5 +382,17 @@ "qualifiers": null, "subpath": "googleapis/api/annotations", "is_invalid": false + }, + { + "description": "invalid purl type", + "purl": "pkg:111_^5/example", + "canonical_purl": null, + "type": "111_^5", + "namespace": null, + "name": "example", + "version": null, + "qualifiers": null, + "subpath": null, + "is_invalid": true } ]