diff --git a/jupyterhub/handlers/base.py b/jupyterhub/handlers/base.py index 5cd0a267..a850a7c8 100644 --- a/jupyterhub/handlers/base.py +++ b/jupyterhub/handlers/base.py @@ -44,6 +44,7 @@ from ..utils import ( get_accepted_mimetype, get_browser_protocol, maybe_future, + url_escape_path, url_path_join, ) @@ -1519,6 +1520,7 @@ class UserUrlHandler(BaseHandler): server_name = '' else: server_name = '' + escaped_server_name = url_escape_path(server_name) spawner = user.spawners[server_name] if spawner.ready: @@ -1537,7 +1539,10 @@ class UserUrlHandler(BaseHandler): pending_url = url_concat( url_path_join( - self.hub.base_url, 'spawn-pending', user.escaped_name, server_name + self.hub.base_url, + 'spawn-pending', + user.escaped_name, + escaped_server_name, ), {'next': self.request.uri}, ) @@ -1551,7 +1556,9 @@ class UserUrlHandler(BaseHandler): # page *in* the server is not found, we return a 424 instead of a 404. # We allow retaining the old behavior to support older JupyterLab versions spawn_url = url_concat( - url_path_join(self.hub.base_url, "spawn", user.escaped_name, server_name), + url_path_join( + self.hub.base_url, "spawn", user.escaped_name, escaped_server_name + ), {"next": self.request.uri}, ) self.set_status( diff --git a/jupyterhub/handlers/pages.py b/jupyterhub/handlers/pages.py index b7aad49c..6d5c7482 100644 --- a/jupyterhub/handlers/pages.py +++ b/jupyterhub/handlers/pages.py @@ -14,7 +14,7 @@ from tornado.httputil import url_concat from .. import __version__ from ..metrics import SERVER_POLL_DURATION_SECONDS, ServerPollStatus from ..scopes import needs_scope -from ..utils import maybe_future, url_path_join +from ..utils import maybe_future, url_escape_path, url_path_join from .base import BaseHandler @@ -284,7 +284,10 @@ class SpawnHandler(BaseHandler): # which may get handled by the default server if they aren't ready yet pending_url = url_path_join( - self.hub.base_url, "spawn-pending", user.escaped_name, server_name + self.hub.base_url, + "spawn-pending", + user.escaped_name, + url_escape_path(server_name), ) pending_url = self.append_query_parameters(pending_url, exclude=['next']) @@ -353,6 +356,7 @@ class SpawnPendingHandler(BaseHandler): if server_name and server_name not in user.spawners: raise web.HTTPError(404, f"{user.name} has no such server {server_name}") + escaped_server_name = url_escape_path(server_name) spawner = user.spawners[server_name] if spawner.ready: @@ -375,7 +379,7 @@ class SpawnPendingHandler(BaseHandler): exc = spawner._spawn_future.exception() self.log.error("Previous spawn for %s failed: %s", spawner._log_name, exc) spawn_url = url_path_join( - self.hub.base_url, "spawn", user.escaped_name, server_name + self.hub.base_url, "spawn", user.escaped_name, escaped_server_name ) self.set_status(500) html = await self.render_template( @@ -428,7 +432,7 @@ class SpawnPendingHandler(BaseHandler): # serving the expected page if status is not None: spawn_url = url_path_join( - self.hub.base_url, "spawn", user.escaped_name, server_name + self.hub.base_url, "spawn", user.escaped_name, escaped_server_name ) html = await self.render_template( "not_running.html", diff --git a/jupyterhub/tests/test_named_servers.py b/jupyterhub/tests/test_named_servers.py index 52cc2e30..5751d7ff 100644 --- a/jupyterhub/tests/test_named_servers.py +++ b/jupyterhub/tests/test_named_servers.py @@ -91,6 +91,7 @@ async def test_default_server(app, named_servers): ('trevor', 'trevor', False), ('$p~c|a! ch@rs', '%24p~c%7Ca%21%20ch@rs', False), ('$p~c|a! ch@rs', '%24p~c%7Ca%21%20ch@rs', True), + ('hash#?question', 'hash%23%3Fquestion', True), ], ) async def test_create_named_server( @@ -111,7 +112,7 @@ async def test_create_named_server( assert r.status_code == 201 assert r.text == '' - url = url_path_join(public_url(app, user), servername, 'env') + url = url_path_join(public_url(app, user), request_servername, 'env') expected_url = url_path_join(public_url(app, user), escapedname, 'env') r = await async_requests.get(url, cookies=cookies) r.raise_for_status()