diff --git a/notebook/base/handlers.py b/notebook/base/handlers.py index 29f708e3a..74527d645 100755 --- a/notebook/base/handlers.py +++ b/notebook/base/handlers.py @@ -853,13 +853,18 @@ class APIVersionHandler(APIHandler): class TrailingSlashHandler(web.RequestHandler): """Simple redirect handler that strips trailing slashes - + This should be the first, highest priority handler. """ - + def get(self): - self.redirect(self.request.uri.rstrip('/')) - + path, *rest = self.request.uri.partition("?") + # trim trailing *and* leading / + # to avoid misinterpreting repeated '//' + path = "/" + path.strip("/") + new_uri = "".join([path, *rest]) + self.redirect(new_uri) + post = put = get @@ -910,6 +915,7 @@ class RedirectWithParams(web.RequestHandler): url = sep.join([self._url, self.request.query]) self.redirect(url, permanent=self._permanent) + class PrometheusMetricsHandler(IPythonHandler): """ Return prometheus metrics for this notebook server diff --git a/notebook/tests/test_paths.py b/notebook/tests/test_paths.py index 11e981c46..33f44afe4 100644 --- a/notebook/tests/test_paths.py +++ b/notebook/tests/test_paths.py @@ -2,10 +2,13 @@ import re from notebook.base.handlers import path_regex +from notebook.utils import url_path_join +from .launchnotebook import NotebookTestBase # build regexps that tornado uses: path_pat = re.compile('^' + '/x%s' % path_regex + '$') + def test_path_regex(): for path in ( '/x', @@ -29,3 +32,18 @@ def test_path_regex_bad(): '/y/x/foo', ): assert not re.match(path_pat, path) + + +class RedirectTestCase(NotebookTestBase): + def test_trailing_slash(self): + for uri, expected in ( + ("/notebooks/mynotebook/", "/notebooks/mynotebook"), + ("////foo///", "/foo"), + ("//example.com/", "/example.com"), + ("/has/param/?hasparam=true", "/has/param?hasparam=true"), + ): + r = self.request("GET", uri, allow_redirects=False) + print(uri, expected) + assert r.status_code == 302 + assert "Location" in r.headers + assert r.headers["Location"] == url_path_join(self.url_prefix, expected)