diff --git a/notebook/base/handlers.py b/notebook/base/handlers.py index 8b2cbc99f..111aeca6d 100644 --- a/notebook/base/handlers.py +++ b/notebook/base/handlers.py @@ -564,7 +564,7 @@ class FilesRedirectHandler(IPythonHandler): cm = self.contents_manager if cm.dir_exists(path): # it's a *directory*, redirect to /tree - url = url_path_join(self.base_url, 'tree', path) + url = url_path_join(self.base_url, 'tree', url_escape(path)) else: orig_path = path # otherwise, redirect to /files @@ -580,8 +580,7 @@ class FilesRedirectHandler(IPythonHandler): if not cm.file_exists(path=path): raise web.HTTPError(404) - url = url_path_join(self.base_url, 'files', path) - url = url_escape(url) + url = url_path_join(self.base_url, 'files', url_escape(path)) self.log.debug("Redirecting %s to %s", self.request.path, url) self.redirect(url) diff --git a/notebook/services/contents/handlers.py b/notebook/services/contents/handlers.py index 521aae5fd..a3955d321 100644 --- a/notebook/services/contents/handlers.py +++ b/notebook/services/contents/handlers.py @@ -87,9 +87,9 @@ class ContentsHandler(APIHandler): path : unicode The API path of the file, such as "foo/bar.txt". """ - return url_escape(url_path_join( - self.base_url, 'api', 'contents', path - )) + return url_path_join( + self.base_url, 'api', 'contents', url_escape(path) + ) def _finish_model(self, model, location=True): """Finish a JSON request with a model, setting relevant headers, etc.""" @@ -280,8 +280,8 @@ class CheckpointsHandler(APIHandler): checkpoint = yield gen.maybe_future(cm.create_checkpoint(path)) data = json.dumps(checkpoint, default=date_default) location = url_path_join(self.base_url, 'api/contents', - path, 'checkpoints', checkpoint['id']) - self.set_header('Location', url_escape(location)) + url_escape(path), 'checkpoints', url_escape(checkpoint['id'])) + self.set_header('Location', location) self.set_status(201) self.finish(data) diff --git a/notebook/services/contents/tests/test_contents_api.py b/notebook/services/contents/tests/test_contents_api.py index 57f69cc24..800a3a20f 100644 --- a/notebook/services/contents/tests/test_contents_api.py +++ b/notebook/services/contents/tests/test_contents_api.py @@ -338,7 +338,7 @@ class APITest(NotebookTestBase): def _check_created(self, resp, path, type='notebook'): self.assertEqual(resp.status_code, 201) location_header = py3compat.str_to_unicode(resp.headers['Location']) - self.assertEqual(location_header, url_escape(url_path_join(u'/api/contents', path))) + self.assertEqual(location_header, url_path_join(self.url_prefix, u'api/contents', url_escape(path))) rjson = resp.json() self.assertEqual(rjson['name'], path.rsplit('/', 1)[-1]) self.assertEqual(rjson['path'], path) diff --git a/notebook/services/kernels/handlers.py b/notebook/services/kernels/handlers.py index 863aa7fd8..7b17ef5b6 100644 --- a/notebook/services/kernels/handlers.py +++ b/notebook/services/kernels/handlers.py @@ -43,8 +43,8 @@ class MainKernelHandler(APIHandler): kernel_id = km.start_kernel(kernel_name=model['name']) model = km.kernel_model(kernel_id) - location = url_path_join(self.base_url, 'api', 'kernels', kernel_id) - self.set_header('Location', url_escape(location)) + location = url_path_join(self.base_url, 'api', 'kernels', url_escape(kernel_id)) + self.set_header('Location', location) self.set_status(201) self.finish(json.dumps(model)) diff --git a/notebook/services/kernels/tests/test_kernels_api.py b/notebook/services/kernels/tests/test_kernels_api.py index eb6c36d64..cf8a725b5 100644 --- a/notebook/services/kernels/tests/test_kernels_api.py +++ b/notebook/services/kernels/tests/test_kernels_api.py @@ -63,29 +63,33 @@ class KernelAPITest(NotebookTestBase): # POST request r = self.kern_api._req('POST', '') kern1 = r.json() - self.assertEqual(r.headers['location'], '/api/kernels/' + kern1['id']) + self.assertEqual(r.headers['location'], url_path_join(self.url_prefix, 'api/kernels', kern1['id'])) self.assertEqual(r.status_code, 201) self.assertIsInstance(kern1, dict) - self.assertEqual(r.headers['Content-Security-Policy'], ( - "frame-ancestors 'self'; " - "report-uri /api/security/csp-report; " - "default-src 'none'" - )) + report_uri = url_path_join(self.url_prefix, 'api/security/csp-report') + expected_csp = '; '.join([ + "frame-ancestors 'self'", + 'report-uri ' + report_uri, + "default-src 'none'" + ]) + self.assertEqual(r.headers['Content-Security-Policy'], expected_csp) def test_main_kernel_handler(self): # POST request r = self.kern_api.start() kern1 = r.json() - self.assertEqual(r.headers['location'], '/api/kernels/' + kern1['id']) + self.assertEqual(r.headers['location'], url_path_join(self.url_prefix, 'api/kernels', kern1['id'])) self.assertEqual(r.status_code, 201) self.assertIsInstance(kern1, dict) - self.assertEqual(r.headers['Content-Security-Policy'], ( - "frame-ancestors 'self'; " - "report-uri /api/security/csp-report; " - "default-src 'none'" - )) + report_uri = url_path_join(self.url_prefix, 'api/security/csp-report') + expected_csp = '; '.join([ + "frame-ancestors 'self'", + 'report-uri ' + report_uri, + "default-src 'none'" + ]) + self.assertEqual(r.headers['Content-Security-Policy'], expected_csp) # GET request r = self.kern_api.list() @@ -110,7 +114,7 @@ class KernelAPITest(NotebookTestBase): # Restart a kernel r = self.kern_api.restart(kern2['id']) - self.assertEqual(r.headers['Location'], '/api/kernels/'+kern2['id']) + self.assertEqual(r.headers['Location'], url_path_join(self.url_prefix, 'api/kernels', kern2['id'])) rekern = r.json() self.assertEqual(rekern['id'], kern2['id']) self.assertEqual(rekern['name'], kern2['name']) diff --git a/notebook/services/sessions/handlers.py b/notebook/services/sessions/handlers.py index 888d518e9..30b74c8d2 100644 --- a/notebook/services/sessions/handlers.py +++ b/notebook/services/sessions/handlers.py @@ -12,7 +12,7 @@ from tornado import web from ...base.handlers import APIHandler, json_errors from jupyter_client.jsonutil import date_default -from notebook.utils import url_path_join, url_escape +from notebook.utils import url_path_join from jupyter_client.kernelspec import NoSuchKernel @@ -64,7 +64,7 @@ class SessionRootHandler(APIHandler): return location = url_path_join(self.base_url, 'api', 'sessions', model['id']) - self.set_header('Location', url_escape(location)) + self.set_header('Location', location) self.set_status(201) self.finish(json.dumps(model, default=date_default)) diff --git a/notebook/services/sessions/tests/test_sessions_api.py b/notebook/services/sessions/tests/test_sessions_api.py index 564dddc3c..1a2dc8003 100644 --- a/notebook/services/sessions/tests/test_sessions_api.py +++ b/notebook/services/sessions/tests/test_sessions_api.py @@ -92,7 +92,7 @@ class SessionAPITest(NotebookTestBase): newsession = resp.json() self.assertIn('id', newsession) self.assertEqual(newsession['notebook']['path'], 'foo/nb1.ipynb') - self.assertEqual(resp.headers['Location'], '/api/sessions/{0}'.format(newsession['id'])) + self.assertEqual(resp.headers['Location'], self.url_prefix + 'api/sessions/{0}'.format(newsession['id'])) sessions = self.sess_api.list().json() self.assertEqual(sessions, [newsession]) diff --git a/notebook/tree/handlers.py b/notebook/tree/handlers.py index f199ffaa4..35b6d0ab0 100644 --- a/notebook/tree/handlers.py +++ b/notebook/tree/handlers.py @@ -12,13 +12,14 @@ class TreeHandler(IPythonHandler): """Render the tree view, listing notebooks, etc.""" def generate_breadcrumbs(self, path): - breadcrumbs = [(url_escape(url_path_join(self.base_url, 'tree')), '')] - comps = path.split('/') - ncomps = len(comps) - for i in range(ncomps): - if comps[i]: - link = url_escape(url_path_join(self.base_url, 'tree', *comps[0:i+1])) - breadcrumbs.append((link, comps[i])) + breadcrumbs = [(url_path_join(self.base_url, 'tree'), '')] + parts = path.split('/') + for i in range(len(parts)): + if parts[i]: + link = url_path_join(self.base_url, 'tree', + url_escape(url_path_join(*parts[:i+1])), + ) + breadcrumbs.append((link, parts[i])) return breadcrumbs def generate_page_title(self, path): @@ -53,9 +54,9 @@ class TreeHandler(IPythonHandler): model = cm.get(path, content=False) # redirect to /api/notebooks if it's a notebook, otherwise /api/files service = 'notebooks' if model['type'] == 'notebook' else 'files' - url = url_escape(url_path_join( - self.base_url, service, path, - )) + url = url_path_join( + self.base_url, service, url_escape(path), + ) self.log.debug("Redirecting %s to %s", self.request.path, url) self.redirect(url) else: