Skip to content

Commit ea4ad00

Browse files
authored
Add support for Composer in purl2url (#144)
Signed-off-by: Camille Moulin <cmoulin@inno3.fr>
1 parent a46d424 commit ea4ad00

5 files changed

Lines changed: 51 additions & 0 deletions

File tree

src/packageurl/contrib/purl2url.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,23 @@ def build_pypi_repo_url(purl):
238238
return f"https://pypi.org/project/{name}/"
239239

240240

241+
@repo_router.route("pkg:composer/.*")
242+
def build_composer_repo_url(purl):
243+
"""
244+
Return a composer repo URL from the `purl` string.
245+
"""
246+
purl_data = PackageURL.from_string(purl)
247+
248+
name = purl_data.name
249+
version = purl_data.version
250+
namespace = purl_data.namespace
251+
252+
if name and version:
253+
return f"https://packagist.org/packages/{namespace}/{name}#{version}"
254+
elif name:
255+
return f"https://packagist.org/packages/{namespace}/{name}"
256+
257+
241258
@repo_router.route("pkg:nuget/.*")
242259
def build_nuget_repo_url(purl):
243260
"""

src/packageurl/contrib/url2purl.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,21 @@ def build_pypi_purl(uri):
340340
return purl_from_pattern("pypi", pypi_pattern, last_segment)
341341

342342

343+
# https://packagist.org/packages/webmozart/assert#1.9.1
344+
@purl_router.route("https?://packagist.org/packages/.*")
345+
def build_composer_purl(uri):
346+
# We use a more general route pattern instead of using `composer_pattern`
347+
# below by itself because we want to capture all packagist download URLs,
348+
# even the ones that are not completely formed. This helps prevent url2purl
349+
# from attempting to create a generic PackageURL from an invalid packagist
350+
# download URL.
351+
352+
# https://packagist.org/packages/ralouphie/getallheaders
353+
# https://packagist.org/packages/symfony/process#v7.0.0-BETA3
354+
composer_pattern = r"^https?://packagist\.org/packages/(?P<namespace>[^/]+)/(?P<name>[^\#]+?)(\#(?P<version>.+))?$"
355+
return purl_from_pattern("composer", composer_pattern, uri)
356+
357+
343358
# http://nuget.org/packages/EntityFramework/4.2.0.0
344359
# https://www.nuget.org/api/v2/package/Newtonsoft.Json/11.0.1
345360
nuget_www_pattern = r"^https?://.*nuget.org/(api/v2/)?packages?/(?P<name>.+)/(?P<version>.+)$"

tests/contrib/data/url2purl.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,9 @@
121121
"https://rubygems.org/downloads/unf-0.1.3.gem": "pkg:gem/unf@0.1.3",
122122
"https://rubygems.org/downloads/yajl-ruby-1.2.0.gem": "pkg:gem/yajl-ruby@1.2.0",
123123
"https://rubygems.org/gems/i18n-js-3.0.11.gem": "pkg:gem/i18n-js@3.0.11",
124+
"https://packagist.org/packages/webmozart/assert":"pkg:composer/webmozart/assert",
125+
"https://packagist.org/packages/guzzlehttp/psr7#2.6.1":"pkg:composer/guzzlehttp/psr7@2.6.1",
126+
"https://packagist.org/packages/symfony/process#v7.0.0-BETA3":"pkg:composer/symfony/process@v7.0.0-BETA3",
124127
"https://pypi.org/packages/source/z/zc.recipe.egg/zc.recipe.egg-2.0.0.tar.gz": "pkg:pypi/zc.recipe.egg@2.0.0",
125128
"https://pypi.org/project/widgetsnbextension": "pkg:pypi/widgetsnbextension",
126129
"https://pypi.org/project/widgetsnbextension/3.0.7/": "pkg:pypi/widgetsnbextension@3.0.7",

tests/contrib/test_purl2url.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ def test_purl2url_get_repo_url():
5353
"pkg:pypi/sortedcontainers": "https://pypi.org/project/sortedcontainers/",
5454
"pkg:pypi/sortedcontainers@2.4.0": "https://pypi.org/project/sortedcontainers/2.4.0/",
5555
"pkg:pypi/packageurl_python": "https://pypi.org/project/packageurl-python/",
56+
"pkg:composer/psr/log": "https://packagist.org/packages/psr/log",
57+
"pkg:composer/psr/log@1.1.3": "https://packagist.org/packages/psr/log#1.1.3",
5658
"pkg:npm/is-npm": "https://www.npmjs.com/package/is-npm",
5759
"pkg:npm/is-npm@1.0.0": "https://www.npmjs.com/package/is-npm/v/1.0.0",
5860
"pkg:nuget/System.Text.Json": "https://www.nuget.org/packages/System.Text.Json",
@@ -95,6 +97,7 @@ def test_purl2url_get_download_url():
9597
"pkg:rubygems/package-name": None,
9698
"pkg:bitbucket/birkenfeld": None,
9799
"pkg:pypi/sortedcontainers@2.4.0": None,
100+
"pkg:composer/psr/log@1.1.3": None,
98101
"pkg:golang/xorm.io/xorm@v0.8.2": None,
99102
"pkg:golang/gopkg.in/ldap.v3@v3.1.0": None,
100103
}
@@ -132,6 +135,7 @@ def test_purl2url_get_inferred_urls():
132135
"https://gitlab.com/tg1999/firebase/-/archive/1a122122/firebase-1a122122.tar.gz",
133136
],
134137
"pkg:pypi/sortedcontainers@2.4.0": ["https://pypi.org/project/sortedcontainers/2.4.0/"],
138+
"pkg:composer/psr/log@1.1.3": ["https://packagist.org/packages/psr/log#1.1.3"],
135139
"pkg:rubygems/package-name": ["https://rubygems.org/gems/package-name"],
136140
"pkg:bitbucket/birkenfeld": [],
137141
}

tests/data/test-suite-data.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,18 @@
191191
"subpath": null,
192192
"is_invalid": false
193193
},
194+
{
195+
"description": "valid packagist purl",
196+
"purl": "pkg:composer/guzzlehttp/promises@2.0.2",
197+
"canonical_purl": "pkg:composer/guzzlehttp/promises@2.0.2",
198+
"type": "composer",
199+
"namespace": "guzzlehttp",
200+
"name": "promises",
201+
"version": "2.0.2",
202+
"qualifiers": null,
203+
"subpath": null,
204+
"is_invalid": false
205+
},
194206
{
195207
"description": "rpm often use qualifiers",
196208
"purl": "pkg:Rpm/fedora/curl@7.50.3-1.fc25?Arch=i386&Distro=fedora-25",

0 commit comments

Comments
 (0)