diff --git a/swh/web/api/apiurls.py b/swh/web/api/apiurls.py --- a/swh/web/api/apiurls.py +++ b/swh/web/api/apiurls.py @@ -56,12 +56,14 @@ def __init__(self, url_pattern=None, view_name=None, methods=['GET', 'HEAD', 'OPTIONS'], throttle_scope='swh_api', - api_version='1'): + api_version='1', + checksum_args=None): super().__init__() self.url_pattern = '^' + api_version + url_pattern + '$' self.view_name = view_name self.methods = methods self.throttle_scope = throttle_scope + self.checksum_args = checksum_args def __call__(self, f): # create a DRF view from the wrapped function @@ -76,4 +78,9 @@ # register the route and its view in the endpoints index APIUrls.add_url_pattern(self.url_pattern, api_view_f, self.view_name) + + if self.checksum_args: + APIUrls.add_redirect_for_checksum_args(self.view_name, + [self.url_pattern], + self.checksum_args) return f diff --git a/swh/web/api/views/directory.py b/swh/web/api/views/directory.py --- a/swh/web/api/views/directory.py +++ b/swh/web/api/views/directory.py @@ -10,9 +10,11 @@ from swh.web.api.views.utils import api_lookup -@api_route(r'/directory/(?P[0-9a-f]+)/', 'api-directory') +@api_route(r'/directory/(?P[0-9a-f]+)/', 'api-directory', + checksum_args=['sha1_git']) @api_route(r'/directory/(?P[0-9a-f]+)/(?P.+)/', - 'api-directory') + 'api-directory', + checksum_args=['sha1_git']) @api_doc('/directory/') def api_directory(request, sha1_git, path=None): """ diff --git a/swh/web/browse/browseurls.py b/swh/web/browse/browseurls.py --- a/swh/web/browse/browseurls.py +++ b/swh/web/browse/browseurls.py @@ -24,9 +24,10 @@ reverse the url """ # noqa - def __init__(self, *url_patterns, view_name=None): + def __init__(self, *url_patterns, view_name=None, checksum_args=None): super().__init__() self.url_patterns = [] + self.checksum_args = checksum_args for url_pattern in url_patterns: self.url_patterns.append('^' + url_pattern + '$') self.view_name = view_name @@ -35,4 +36,10 @@ # register the route and its view in the browse endpoints index for url_pattern in self.url_patterns: BrowseUrls.add_url_pattern(url_pattern, f, self.view_name) + + if self.checksum_args: + BrowseUrls.add_redirect_for_checksum_args(self.view_name, + self.url_patterns, + self.checksum_args) + return f diff --git a/swh/web/browse/views/directory.py b/swh/web/browse/views/directory.py --- a/swh/web/browse/views/directory.py +++ b/swh/web/browse/views/directory.py @@ -23,7 +23,8 @@ @browse_route(r'directory/(?P[0-9a-f]+)/', r'directory/(?P[0-9a-f]+)/(?P.+)/', - view_name='browse-directory') + view_name='browse-directory', + checksum_args=['sha1_git']) def directory_browse(request, sha1_git, path=None): """Django view for browsing the content of a directory identified by its sha1_git value. diff --git a/swh/web/browse/views/release.py b/swh/web/browse/views/release.py --- a/swh/web/browse/views/release.py +++ b/swh/web/browse/views/release.py @@ -19,7 +19,8 @@ @browse_route(r'release/(?P[0-9a-f]+)/', - view_name='browse-release') + view_name='browse-release', + checksum_args=['sha1_git']) def release_browse(request, sha1_git): """ Django view that produces an HTML display of a release diff --git a/swh/web/browse/views/revision.py b/swh/web/browse/views/revision.py --- a/swh/web/browse/views/revision.py +++ b/swh/web/browse/views/revision.py @@ -137,7 +137,8 @@ @browse_route(r'revision/(?P[0-9a-f]+)/diff/', - view_name='diff-revision') + view_name='diff-revision', + checksum_args=['sha1_git']) def _revision_diff(request, sha1_git): """ Browse internal endpoint to compute revision diff @@ -175,7 +176,8 @@ @browse_route(r'revision/(?P[0-9a-f]+)/log/', - view_name='browse-revision-log') + view_name='browse-revision-log', + checksum_args=['sha1_git']) def revision_log_browse(request, sha1_git): """ Django view that produces an HTML display of the history @@ -254,7 +256,8 @@ @browse_route(r'revision/(?P[0-9a-f]+)/', r'revision/(?P[0-9a-f]+)/(?P.+)/', - view_name='browse-revision') + view_name='browse-revision', + checksum_args=['sha1_git']) def revision_browse(request, sha1_git, extra_path=None): """ Django view that produces an HTML display of a revision diff --git a/swh/web/browse/views/snapshot.py b/swh/web/browse/views/snapshot.py --- a/swh/web/browse/views/snapshot.py +++ b/swh/web/browse/views/snapshot.py @@ -17,7 +17,8 @@ @browse_route(r'snapshot/(?P[0-9a-f]+)/', - view_name='browse-snapshot') + view_name='browse-snapshot', + checksum_args=['snapshot_id']) def snapshot_browse(request, snapshot_id): """Django view for browsing the content of a snapshot. @@ -31,7 +32,8 @@ @browse_route(r'snapshot/(?P[0-9a-f]+)/directory/', r'snapshot/(?P[0-9a-f]+)/directory/(?P.+)/', - view_name='browse-snapshot-directory') + view_name='browse-snapshot-directory', + checksum_args=['snapshot_id']) def snapshot_directory_browse(request, snapshot_id, path=None): """Django view for browsing the content of a directory collected in a snapshot. @@ -48,7 +50,8 @@ @browse_route(r'snapshot/(?P[0-9a-f]+)/content/(?P.+)/', - view_name='browse-snapshot-content') + view_name='browse-snapshot-content', + checksum_args=['snapshot_id']) def snapshot_content_browse(request, snapshot_id, path): """Django view that produces an HTML display of a content collected in a snapshot. @@ -59,7 +62,8 @@ @browse_route(r'snapshot/(?P[0-9a-f]+)/log/', - view_name='browse-snapshot-log') + view_name='browse-snapshot-log', + checksum_args=['snapshot_id']) def snapshot_log_browse(request, snapshot_id): """Django view that produces an HTML display of revisions history (aka the commit log) collected in a snapshot. @@ -70,7 +74,8 @@ @browse_route(r'snapshot/(?P[0-9a-f]+)/branches/', - view_name='browse-snapshot-branches') + view_name='browse-snapshot-branches', + checksum_args=['snapshot_id']) def snapshot_branches_browse(request, snapshot_id): """Django view that produces an HTML display of the list of releases collected in a snapshot. @@ -81,7 +86,8 @@ @browse_route(r'snapshot/(?P[0-9a-f]+)/releases/', - view_name='browse-snapshot-releases') + view_name='browse-snapshot-releases', + checksum_args=['snapshot_id']) def snapshot_releases_browse(request, snapshot_id): """Django view that produces an HTML display of the list of releases collected in a snapshot. diff --git a/swh/web/common/urlsindex.py b/swh/web/common/urlsindex.py --- a/swh/web/common/urlsindex.py +++ b/swh/web/common/urlsindex.py @@ -4,6 +4,7 @@ # See top-level LICENSE file for more information from django.conf.urls import url +from django.shortcuts import redirect class UrlsIndex(object): @@ -19,7 +20,7 @@ scope = 'default' @classmethod - def add_url_pattern(cls, url_pattern, view, view_name): + def add_url_pattern(cls, url_pattern, view, view_name=None): """ Class method that adds an url pattern to the current scope. @@ -36,6 +37,23 @@ else: cls._urlpatterns[cls.scope].append(url(url_pattern, view)) + @classmethod + def add_redirect_for_checksum_args(cls, view_name, url_patterns, + checksum_args): + new_view_name = view_name+'-uppercase-checksum' + for url_pattern in url_patterns: + url_pattern_upper = url_pattern.replace('[0-9a-f]', + '[0-9a-fA-F]') + + def view_redirect(request, *args, **kwargs): + for checksum_arg in checksum_args: + checksum_upper = kwargs[checksum_arg] + kwargs[checksum_arg] = checksum_upper.lower() + return redirect(view_name, *args, **kwargs) + + cls.add_url_pattern(url_pattern_upper, view_redirect, + new_view_name) + @classmethod def get_url_patterns(cls): """ diff --git a/swh/web/tests/api/views/test_directory.py b/swh/web/tests/api/views/test_directory.py --- a/swh/web/tests/api/views/test_directory.py +++ b/swh/web/tests/api/views/test_directory.py @@ -74,6 +74,19 @@ 'reason': ('Directory entry with path %s from %s not found' % (path, directory))}) + @given(directory()) + def test_api_directory_uppercase(self, directory): + url = reverse('api-directory-uppercase-checksum', + url_args={'sha1_git': directory.upper()}) + + resp = self.client.get(url) + self.assertEqual(resp.status_code, 302) + + redirect_url = reverse('api-directory', + url_args={'sha1_git': directory}) + + self.assertEqual(resp['location'], redirect_url) + @classmethod def _enrich_dir_data(cls, dir_data): if dir_data['type'] == 'file': diff --git a/swh/web/tests/browse/views/test_directory.py b/swh/web/tests/browse/views/test_directory.py --- a/swh/web/tests/browse/views/test_directory.py +++ b/swh/web/tests/browse/views/test_directory.py @@ -116,3 +116,16 @@ resp = self.client.get(dir_url) self.assertEqual(resp.status_code, 404) self.assertTemplateUsed('browse/error.html') + + @given(directory()) + def test_directory_uppercase(self, directory): + url = reverse('browse-directory-uppercase-checksum', + url_args={'sha1_git': directory.upper()}) + + resp = self.client.get(url) + self.assertEqual(resp.status_code, 302) + + redirect_url = reverse('browse-directory', + url_args={'sha1_git': directory}) + + self.assertEqual(resp['location'], redirect_url) diff --git a/swh/web/tests/browse/views/test_release.py b/swh/web/tests/browse/views/test_release.py --- a/swh/web/tests/browse/views/test_release.py +++ b/swh/web/tests/browse/views/test_release.py @@ -98,3 +98,16 @@ url_args={'swh_id': swh_rel_id}) self.assertContains(resp, swh_rel_id) self.assertContains(resp, swh_rel_id_url) + + @given(release()) + def test_release_uppercase(self, release): + url = reverse('browse-release-uppercase-checksum', + url_args={'sha1_git': release.upper()}) + + resp = self.client.get(url) + self.assertEqual(resp.status_code, 302) + + redirect_url = reverse('browse-release', + url_args={'sha1_git': release}) + + self.assertEqual(resp['location'], redirect_url) diff --git a/swh/web/tests/browse/views/test_revision.py b/swh/web/tests/browse/views/test_revision.py --- a/swh/web/tests/browse/views/test_revision.py +++ b/swh/web/tests/browse/views/test_revision.py @@ -247,3 +247,16 @@ self.assertTemplateUsed('error.html') self.assertContains(resp, 'the origin mentioned in your request' ' appears broken', status_code=404) + + @given(revision()) + def test_revision_uppercase(self, revision): + url = reverse('browse-revision-uppercase-checksum', + url_args={'sha1_git': revision.upper()}) + + resp = self.client.get(url) + self.assertEqual(resp.status_code, 302) + + redirect_url = reverse('browse-revision', + url_args={'sha1_git': revision}) + + self.assertEqual(resp['location'], redirect_url)