diff --git a/notebook/base/handlers.py b/notebook/base/handlers.py index c57082fd4..3982957ea 100755 --- a/notebook/base/handlers.py +++ b/notebook/base/handlers.py @@ -612,8 +612,8 @@ class AuthenticatedFileHandler(IPythonHandler, web.StaticFileHandler): """ abs_path = super(AuthenticatedFileHandler, self).validate_absolute_path(root, absolute_path) abs_root = os.path.abspath(root) - if is_hidden(abs_path, abs_root): - self.log.info("Refusing to serve hidden file, via 404 Error") + if is_hidden(abs_path, abs_root) and not self.contents_manager.allow_hidden: + self.log.info("Refusing to serve hidden file, via 404 Error, use flag 'ContentsManager.allow_hidden' to enable") raise web.HTTPError(404) return abs_path diff --git a/notebook/files/handlers.py b/notebook/files/handlers.py index 3f9fea1fb..b94214984 100644 --- a/notebook/files/handlers.py +++ b/notebook/files/handlers.py @@ -34,7 +34,7 @@ class FilesHandler(IPythonHandler): def get(self, path, include_body=True): cm = self.contents_manager - if cm.is_hidden(path): + if cm.is_hidden(path) and not cm.allow_hidden: 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 7d0c7be5d..b8cfbed61 100644 --- a/notebook/services/contents/filemanager.py +++ b/notebook/services/contents/filemanager.py @@ -78,7 +78,6 @@ class FileContentsManager(FileManagerMixin, ContentsManager): return getcwd() save_script = Bool(False, config=True, help='DEPRECATED, use post_save_hook. Will be removed in Notebook 5.0') - @observe('save_script') def _update_save_script(self, change): if not change['new']: @@ -288,7 +287,7 @@ class FileContentsManager(FileManagerMixin, ContentsManager): if not os.path.isdir(os_path): raise web.HTTPError(404, four_o_four) - elif is_hidden(os_path, self.root_dir): + elif is_hidden(os_path, self.root_dir) and not self.allow_hidden: self.log.info("Refusing to serve hidden directory %r, via 404 Error", os_path ) @@ -426,7 +425,7 @@ class FileContentsManager(FileManagerMixin, ContentsManager): def _save_directory(self, os_path, model, path=''): """create a directory""" - if is_hidden(os_path, self.root_dir): + if is_hidden(os_path, self.root_dir) and not self.allow_hidden: raise web.HTTPError(400, u'Cannot create hidden directory %r' % os_path) if not os.path.exists(os_path): with self.perm_to_403(): diff --git a/notebook/services/contents/manager.py b/notebook/services/contents/manager.py index 3cbcbb147..3f1a67241 100644 --- a/notebook/services/contents/manager.py +++ b/notebook/services/contents/manager.py @@ -19,6 +19,7 @@ from nbformat.v4 import new_notebook from ipython_genutils.importstring import import_item from traitlets import ( Any, + Bool, Dict, Instance, List, @@ -56,6 +57,8 @@ class ContentsManager(LoggingConfigurable): root_dir = Unicode('/', config=True) + allow_hidden = Bool(False, config=True, help="Allow access to hidden files") + notary = Instance(sign.NotebookNotary) def _notary_default(self): return sign.NotebookNotary(parent=self) diff --git a/notebook/tests/test_files.py b/notebook/tests/test_files.py index 14aa55965..2a3f02a74 100644 --- a/notebook/tests/test_files.py +++ b/notebook/tests/test_files.py @@ -41,7 +41,7 @@ class FilesTest(NotebookTestBase): f.write('foo') with open(pjoin(path, '.foo'), 'w') as f: f.write('.foo') - + for d in not_hidden: path = pjoin(nbdir, d.replace('/', os.sep)) r = self.request('GET', url_path_join('files', d, 'foo')) @@ -49,13 +49,33 @@ class FilesTest(NotebookTestBase): self.assertEqual(r.text, 'foo') r = self.request('GET', url_path_join('files', d, '.foo')) self.assertEqual(r.status_code, 404) - + for d in hidden: path = pjoin(nbdir, d.replace('/', os.sep)) for foo in ('foo', '.foo'): r = self.request('GET', url_path_join('files', d, foo)) self.assertEqual(r.status_code, 404) - + + self.notebook.contents_manager.allow_hidden = True + try: + for d in not_hidden: + path = pjoin(nbdir, d.replace('/', os.sep)) + r = self.request('GET', url_path_join('files', d, 'foo')) + r.raise_for_status() + self.assertEqual(r.text, 'foo') + r = self.request('GET', url_path_join('files', d, '.foo')) + r.raise_for_status() + self.assertEqual(r.text, '.foo') + + for d in hidden: + path = pjoin(nbdir, d.replace('/', os.sep)) + for foo in ('foo', '.foo'): + r = self.request('GET', url_path_join('files', d, foo)) + r.raise_for_status() + self.assertEqual(r.text, foo) + finally: + self.notebook.contents_manager.allow_hidden = False + def test_contents_manager(self): "make sure ContentsManager returns right files (ipynb, bin, txt)." diff --git a/notebook/tree/handlers.py b/notebook/tree/handlers.py index 4d7a6f191..ef4252761 100644 --- a/notebook/tree/handlers.py +++ b/notebook/tree/handlers.py @@ -40,7 +40,7 @@ class TreeHandler(IPythonHandler): cm = self.contents_manager if cm.dir_exists(path=path): - if cm.is_hidden(path): + if cm.is_hidden(path) and not cm.allow_hidden: self.log.info("Refusing to serve hidden directory, via 404 Error") raise web.HTTPError(404) breadcrumbs = self.generate_breadcrumbs(path)