Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
217 changes: 215 additions & 2 deletions src/packageurl/contrib/purl2url.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
# Visit https://github.com/package-url/packageurl-python for support and
# download.

import re

from packageurl import PackageURL
from packageurl.contrib.route import NoRouteAvailable
from packageurl.contrib.route import Router
Expand Down Expand Up @@ -51,6 +53,8 @@ def get_repo_download_url_by_package_type(

repo_router = Router()
download_router = Router()
commit_router = Router()
patch_router = Router()


def _get_url_from_router(router, purl):
Expand All @@ -68,6 +72,30 @@ def get_repo_url(purl):
return _get_url_from_router(repo_router, purl)


def get_commit_url(purl):
"""
Return a Commit URL inferred from the `purl` string.
"""
commit_url = _get_url_from_router(commit_router, purl)
if commit_url:
return commit_url

purl_data = PackageURL.from_string(purl)
return purl_data.qualifiers.get("commit_url", None)


def get_patch_url(purl):
"""
Return a Patch URL inferred from the `purl` string.
"""
patch_url = _get_url_from_router(patch_router, purl)
if patch_url:
return patch_url

purl_data = PackageURL.from_string(purl)
return purl_data.qualifiers.get("patch_url", None)


def get_download_url(purl):
"""
Return a download URL inferred from the `purl` string.
Expand Down Expand Up @@ -158,18 +186,203 @@ def build_github_repo_url(purl):
return repo_url


SUB_GITLAB_DOMAINS = [r"^git\.codelinaro\.org", r"^salsa\.debian\.org", r"^gitlab\.(?!com\b)[^/]+"]


@repo_router.route("pkg:gitlab/.*")
def build_gitlab_repo_url(purl):
"""
Return a gitlab repo URL from the `purl` string.
"""
purl_data = PackageURL.from_string(purl)
namespace = purl_data.namespace
name = purl_data.name
qualifiers = purl_data.qualifiers
repository_url = qualifiers.get("repository_url")
if not (namespace and name):
return

if repository_url:
clean_url = re.sub(r"^https?://", "", repository_url)
for pattern in SUB_GITLAB_DOMAINS:
if re.match(pattern, clean_url):
return f"https://{namespace}/{name}"

return f"https://gitlab.com/{namespace}/{name}"


GIT_REPO_GENERIC = {
# cgit
(
r"git\.kernel\.org",
r"gitweb\.gentoo\.org",
"cgit\.git\.savannah\.gnu\.org",
"web\.git\.kernel\.org",
): {
"repo_url": "https://{namespace}/{name}.git",
"commit_url": "https://{namespace}/{name}.git/commit/?id={version}",
"patch_url": "https://{namespace}/{name}.git/patch/?id={version}",
},
# gitiles
(
r"android\.googlesource\.com",
r"aomedia\.googlesource\.com",
r"chromium\.googlesource\.com",
r"gerrit\.googlesource\.com",
): {
"repo_url": "https://{namespace}/{name}",
"commit_url": "https://{namespace}/{name}/+/{version}",
"patch_url": "https://{namespace}/{name}/+/{version}^!?format=TEXT", # base64 encoded
},
# allura
(r"sourceforge\.net", r"forge-allura\.apache\.org"): {
"repo_url": "https://{namespace}/{name}",
"commit_url": "https://{namespace}/{name}/ci/{version}",
"patch_url": None, # Can't find a direct patch URL
},
# gitweb
(
r"gcc\.gnu\.org/git",
r"git\.postgresql\.org",
"sourceware\.org",
"git\.openssl\.org",
"gitbox\.apache\.org",
): {
"commit_url": "https://{namespace}/?p={name}.git;a=commit;h={version}",
"repo_url": "https://{namespace}/?p={name}.git",
"patch_url": "https://{namespace}/?p={name}.git;a=patch;h={version}",
},
# gitea / forgejo
(
r"codeberg\.org",
r"gitea\.com",
r"forge\.fedoraproject\.org",
): {
"commit_url": "https://{namespace}/{name}/commit/{version}",
"repo_url": "https://{namespace}/{name}",
"patch_url": "https://{namespace}/{name}/commit/{version}.patch",
},
}


@repo_router.route("pkg:generic/.*")
def build_generic_repo_url(purl):
"""
Return a Repo URL from the `purl` string.
"""
purl_data = PackageURL.from_string(purl)
name = purl_data.name
namespace = purl_data.namespace

if not (namespace and name):
return

for patterns, template_url in GIT_REPO_GENERIC.items():
for pattern in patterns:
if not re.match(pattern, namespace):
continue

return template_url["repo_url"].format(namespace=namespace, name=name)
return


@commit_router.route("pkg:generic/.*")
def build_generic_commit_url(purl):
"""
Return a Commit URL from the `purl` string.
"""
purl_data = PackageURL.from_string(purl)
name = purl_data.name
namespace = purl_data.namespace
version = purl_data.version

if name and namespace:
return f"https://gitlab.com/{namespace}/{name}"
if not (namespace and name and version):
return

for patterns, template_url in GIT_REPO_GENERIC.items():
for pattern in patterns:
if not re.match(pattern, namespace):
continue

return template_url["commit_url"].format(
namespace=namespace, name=name, version=version
)


@patch_router.route("pkg:generic/.*")
def build_generic_patch_url(purl):
"""
Return a Patch URL from the `purl` string.
"""
purl_data = PackageURL.from_string(purl)
name = purl_data.name
namespace = purl_data.namespace
version = purl_data.version

if not (namespace and name and version):
return

for patterns, template_url in GIT_REPO_GENERIC.items():
for pattern in patterns:
if not re.match(pattern, namespace):
continue

patch_template = template_url.get("patch_url")
if patch_template:
return patch_template.format(namespace=namespace, name=name, version=version)


@commit_router.route("pkg:gitlab/.*", "pkg:bitbucket/.*", "pkg:github/.*")
def build_main_commit_url(purl):
"""
Return a github/gitlab/bitbucket Commit URL from the `purl` string.
"""
purl_data = PackageURL.from_string(purl)
purl_type = purl_data.type
name = purl_data.name
namespace = purl_data.namespace
version = purl_data.version
if not (namespace and name and version):
return

commit_url_template = {
"github": f"https://github.com/{namespace}/{name}/commit/{version}",
"gitlab": f"https://gitlab.com/{namespace}/{name}/-/commit/{version}",
"sub-gitlab": f"https://{namespace}/{name}/-/commit/{version}",
"bitbucket": f"https://bitbucket.org/{namespace}/{name}/commits/{version}",
}

if purl_type == "gitlab" and purl_data.qualifiers.get("repository_url"):
purl_type = "sub-gitlab"

return commit_url_template[purl_type].format(namespace=namespace, name=name, version=version)


@patch_router.route("pkg:gitlab/.*", "pkg:bitbucket/.*", "pkg:github/.*")
def build_main_patch_url(purl):
"""
Return a github/gitlab/bitbucket Patch URL from the `purl` string.
"""
purl_data = PackageURL.from_string(purl)
purl_type = purl_data.type
name = purl_data.name
namespace = purl_data.namespace
version = purl_data.version

if not (namespace and name and version):
return

patch_url_templates = {
"github": f"https://github.com/{namespace}/{name}/commit/{version}.patch",
"gitlab": f"https://gitlab.com/{namespace}/{name}/-/commit/{version}.patch",
"sub-gitlab": f"https://{namespace}/{name}/-/commit/{version}.patch",
"bitbucket": f"https://bitbucket.org/{namespace}/{name}/commits/{version}/raw",
}

if purl_type == "gitlab" and purl_data.qualifiers.get("repository_url"):
purl_type = "sub-gitlab"

return patch_url_templates[purl_type].format(namespace=namespace, name=name, version=version)


@repo_router.route("pkg:(gem|rubygems)/.*")
Expand Down
Loading
Loading