From a82a51019993d0ef0d5bb785484d0ea4c64963c8 Mon Sep 17 00:00:00 2001 From: Anastasis Germanidis Date: Sun, 16 Jul 2017 19:19:09 -0400 Subject: [PATCH 1/4] Use static file handler when files are local --- notebook/base/handlers.py | 5 ++++- notebook/files/handlers.py | 7 +++++++ notebook/services/contents/filemanager.py | 5 +++++ notebook/services/contents/manager.py | 3 +++ notebook/tests/test_files.py | 2 +- 5 files changed, 20 insertions(+), 2 deletions(-) diff --git a/notebook/base/handlers.py b/notebook/base/handlers.py index cf3e183f09..e255467532 100755 --- a/notebook/base/handlers.py +++ b/notebook/base/handlers.py @@ -475,7 +475,10 @@ def get(self, path): name = path.rsplit('/', 1)[-1] self.set_header('Content-Type', 'application/json') self.set_header('Content-Disposition','attachment; filename="%s"' % escape.url_escape(name)) - + elif self.get_argument("download", False): + name = path.rsplit('/', 1)[-1] + self.set_header('Content-Disposition','attachment; filename="%s"' % escape.url_escape(name)) + return web.StaticFileHandler.get(self, path) def set_headers(self): diff --git a/notebook/files/handlers.py b/notebook/files/handlers.py index 2c77c9d5e0..4eaeb03955 100644 --- a/notebook/files/handlers.py +++ b/notebook/files/handlers.py @@ -26,6 +26,13 @@ def head(self, path): @web.authenticated def get(self, path, include_body=True): cm = self.contents_manager + + if cm.files_handler_class: + return cm.files_handler_class(self.application, self.request, path=cm.root_dir)._execute( + [t(self.request) for t in self.application.transforms], + path + ) + if cm.is_hidden(path): self.log.info("Refusing to serve hidden file, via 404 Error") raise web.HTTPError(404) diff --git a/notebook/services/contents/filemanager.py b/notebook/services/contents/filemanager.py index a05253ac3c..46902f2ddf 100644 --- a/notebook/services/contents/filemanager.py +++ b/notebook/services/contents/filemanager.py @@ -28,6 +28,7 @@ is_hidden, is_file_hidden, to_api_path, ) +from notebook.base.handlers import AuthenticatedFileHandler try: from os.path import samefile @@ -146,6 +147,10 @@ def _validate_root_dir(self, proposal): def _checkpoints_class_default(self): return FileCheckpoints + @default('files_handler_class') + def _files_handler_class_default(self): + return AuthenticatedFileHandler + def is_hidden(self, path): """Does the API style path correspond to a hidden directory or file? diff --git a/notebook/services/contents/manager.py b/notebook/services/contents/manager.py index c24d767c83..acc703261a 100644 --- a/notebook/services/contents/manager.py +++ b/notebook/services/contents/manager.py @@ -28,6 +28,7 @@ default, ) from ipython_genutils.py3compat import string_types +from notebook.base.handlers import IPythonHandler copy_pat = re.compile(r'\-Copy\d*\.') @@ -129,6 +130,8 @@ def _default_checkpoints_kwargs(self): log=self.log, ) + files_handler_class = Type(IPythonHandler, allow_none=True, config=True) + # ContentsManager API part 1: methods that must be # implemented in subclasses. diff --git a/notebook/tests/test_files.py b/notebook/tests/test_files.py index 8345f3c467..3dc6087f0a 100644 --- a/notebook/tests/test_files.py +++ b/notebook/tests/test_files.py @@ -95,7 +95,7 @@ def test_contents_manager(self): r = self.request('GET', 'files/test.txt') self.assertEqual(r.status_code, 200) - self.assertEqual(r.headers['content-type'], 'text/plain; charset=UTF-8') + self.assertEqual('text/plain', r.headers['content-type']) self.assertEqual(r.text, 'foobar') def test_download(self): From 539c2f7521a367932eb169b26157e202e1ec52eb Mon Sep 17 00:00:00 2001 From: Anastasis Germanidis Date: Wed, 19 Jul 2017 09:42:26 -0400 Subject: [PATCH 2/4] Added charset handling on AuthenticatedFileHandler --- notebook/base/handlers.py | 12 ++++++++++++ notebook/tests/test_files.py | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/notebook/base/handlers.py b/notebook/base/handlers.py index e255467532..3bf3aca54d 100755 --- a/notebook/base/handlers.py +++ b/notebook/base/handlers.py @@ -5,6 +5,7 @@ import functools import json +import mimetypes import os import re import sys @@ -481,6 +482,17 @@ def get(self, path): return web.StaticFileHandler.get(self, path) + def get_content_type(self): + _, name = self.absolute_path.rsplit('/', 1) + if name.endswith('.ipynb'): + return 'application/x-ipynb+json' + else: + cur_mime = mimetypes.guess_type(name)[0] + if cur_mime == 'text/plain': + return 'text/plain; charset=UTF-8' + else: + return super(AuthenticatedFileHandler, self).get_content_type() + def set_headers(self): super(AuthenticatedFileHandler, self).set_headers() # disable browser caching, rely on 304 replies for savings diff --git a/notebook/tests/test_files.py b/notebook/tests/test_files.py index 3dc6087f0a..8345f3c467 100644 --- a/notebook/tests/test_files.py +++ b/notebook/tests/test_files.py @@ -95,7 +95,7 @@ def test_contents_manager(self): r = self.request('GET', 'files/test.txt') self.assertEqual(r.status_code, 200) - self.assertEqual('text/plain', r.headers['content-type']) + self.assertEqual(r.headers['content-type'], 'text/plain; charset=UTF-8') self.assertEqual(r.text, 'foobar') def test_download(self): From 180bbe88f614f3e134c67c4b9fbe5f168470518a Mon Sep 17 00:00:00 2001 From: Anastasis Germanidis Date: Wed, 19 Jul 2017 09:44:58 -0400 Subject: [PATCH 3/4] Cleaning up AuthenticatedFileHandler.get --- notebook/base/handlers.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/notebook/base/handlers.py b/notebook/base/handlers.py index 3bf3aca54d..7e21878b66 100755 --- a/notebook/base/handlers.py +++ b/notebook/base/handlers.py @@ -472,11 +472,7 @@ class AuthenticatedFileHandler(IPythonHandler, web.StaticFileHandler): @web.authenticated def get(self, path): - if os.path.splitext(path)[1] == '.ipynb': - name = path.rsplit('/', 1)[-1] - self.set_header('Content-Type', 'application/json') - self.set_header('Content-Disposition','attachment; filename="%s"' % escape.url_escape(name)) - elif self.get_argument("download", False): + if os.path.splitext(path)[1] == '.ipynb' or self.get_argument("download", False): name = path.rsplit('/', 1)[-1] self.set_header('Content-Disposition','attachment; filename="%s"' % escape.url_escape(name)) From 95a53b775e24aaaef9ae0f74d34feea13acd6c72 Mon Sep 17 00:00:00 2001 From: Anastasis Germanidis Date: Wed, 19 Jul 2017 10:10:19 -0400 Subject: [PATCH 4/4] Fixed bug in get_content_type --- notebook/base/handlers.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/notebook/base/handlers.py b/notebook/base/handlers.py index 7e21878b66..68006e8c51 100755 --- a/notebook/base/handlers.py +++ b/notebook/base/handlers.py @@ -479,7 +479,11 @@ def get(self, path): return web.StaticFileHandler.get(self, path) def get_content_type(self): - _, name = self.absolute_path.rsplit('/', 1) + path = self.absolute_path.strip('/') + if '/' in path: + _, name = path.rsplit('/', 1) + else: + name = path if name.endswith('.ipynb'): return 'application/x-ipynb+json' else: