Show More
@@ -22,7 +22,6 b' import json' | |||||
22 | import logging |
|
22 | import logging | |
23 | import os |
|
23 | import os | |
24 | import re |
|
24 | import re | |
25 | import stat |
|
|||
26 | import sys |
|
25 | import sys | |
27 | import traceback |
|
26 | import traceback | |
28 | try: |
|
27 | try: | |
@@ -42,10 +41,7 b' except ImportError:' | |||||
42 | from IPython.config import Application |
|
41 | from IPython.config import Application | |
43 | from IPython.utils.path import filefind |
|
42 | from IPython.utils.path import filefind | |
44 | from IPython.utils.py3compat import string_types |
|
43 | from IPython.utils.py3compat import string_types | |
45 |
|
44 | from IPython.html.utils import is_hidden | ||
46 | # UF_HIDDEN is a stat flag not defined in the stat module. |
|
|||
47 | # It is used by BSD to indicate hidden files. |
|
|||
48 | UF_HIDDEN = getattr(stat, 'UF_HIDDEN', 32768) |
|
|||
49 |
|
45 | |||
50 | #----------------------------------------------------------------------------- |
|
46 | #----------------------------------------------------------------------------- | |
51 | # Top-level handlers |
|
47 | # Top-level handlers | |
@@ -269,28 +265,9 b' class AuthenticatedFileHandler(IPythonHandler, web.StaticFileHandler):' | |||||
269 | """ |
|
265 | """ | |
270 | abs_path = super(AuthenticatedFileHandler, self).validate_absolute_path(root, absolute_path) |
|
266 | abs_path = super(AuthenticatedFileHandler, self).validate_absolute_path(root, absolute_path) | |
271 | abs_root = os.path.abspath(root) |
|
267 | abs_root = os.path.abspath(root) | |
272 |
|
|
268 | if is_hidden(abs_root, abs_path): | |
273 | return abs_path |
|
|||
274 |
|
||||
275 | def forbid_hidden(self, absolute_root, absolute_path): |
|
|||
276 | """Raise 403 if a file is hidden or contained in a hidden directory. |
|
|||
277 |
|
||||
278 | Hidden is determined by either name starting with '.' |
|
|||
279 | or the UF_HIDDEN flag as reported by stat |
|
|||
280 | """ |
|
|||
281 | inside_root = absolute_path[len(absolute_root):] |
|
|||
282 | if any(part.startswith('.') for part in inside_root.split(os.sep)): |
|
|||
283 | raise web.HTTPError(403) |
|
269 | raise web.HTTPError(403) | |
284 |
|
270 | return abs_path | ||
285 | # check UF_HIDDEN on any location up to root |
|
|||
286 | path = absolute_path |
|
|||
287 | while path and path.startswith(absolute_root) and path != absolute_root: |
|
|||
288 | st = os.stat(path) |
|
|||
289 | if getattr(st, 'st_flags', 0) & UF_HIDDEN: |
|
|||
290 | raise web.HTTPError(403) |
|
|||
291 | path = os.path.dirname(path) |
|
|||
292 |
|
||||
293 | return absolute_path |
|
|||
294 |
|
271 | |||
295 |
|
272 | |||
296 | def json_errors(method): |
|
273 | def json_errors(method): |
@@ -29,6 +29,7 b' from .nbmanager import NotebookManager' | |||||
29 | from IPython.nbformat import current |
|
29 | from IPython.nbformat import current | |
30 | from IPython.utils.traitlets import Unicode, Dict, Bool, TraitError |
|
30 | from IPython.utils.traitlets import Unicode, Dict, Bool, TraitError | |
31 | from IPython.utils import tz |
|
31 | from IPython.utils import tz | |
|
32 | from IPython.html.utils import is_hidden | |||
32 |
|
33 | |||
33 | #----------------------------------------------------------------------------- |
|
34 | #----------------------------------------------------------------------------- | |
34 | # Classes |
|
35 | # Classes | |
@@ -108,7 +109,26 b' class FileNotebookManager(NotebookManager):' | |||||
108 | path = path.strip('/') |
|
109 | path = path.strip('/') | |
109 | os_path = self.get_os_path(path=path) |
|
110 | os_path = self.get_os_path(path=path) | |
110 | return os.path.isdir(os_path) |
|
111 | return os.path.isdir(os_path) | |
111 |
|
112 | |||
|
113 | def is_hidden(self, path): | |||
|
114 | """Does the API style path correspond to a hidden directory or file? | |||
|
115 | ||||
|
116 | Parameters | |||
|
117 | ---------- | |||
|
118 | path : string | |||
|
119 | The path to check. This is an API path (`/` separated, | |||
|
120 | relative to base notebook-dir). | |||
|
121 | ||||
|
122 | Returns | |||
|
123 | ------- | |||
|
124 | exists : bool | |||
|
125 | Whether the path is hidden. | |||
|
126 | ||||
|
127 | """ | |||
|
128 | path = path.strip('/') | |||
|
129 | os_path = self.get_os_path(path=path) | |||
|
130 | return is_hidden(self.notebook_dir, os_path) | |||
|
131 | ||||
112 | def get_os_path(self, name=None, path=''): |
|
132 | def get_os_path(self, name=None, path=''): | |
113 | """Given a notebook name and a URL path, return its file system |
|
133 | """Given a notebook name and a URL path, return its file system | |
114 | path. |
|
134 | path. | |
@@ -159,13 +179,13 b' class FileNotebookManager(NotebookManager):' | |||||
159 | """List the directories for a given API style path.""" |
|
179 | """List the directories for a given API style path.""" | |
160 | path = path.strip('/') |
|
180 | path = path.strip('/') | |
161 | os_path = self.get_os_path('', path) |
|
181 | os_path = self.get_os_path('', path) | |
162 | if not os.path.isdir(os_path): |
|
182 | if not os.path.isdir(os_path) or is_hidden(self.notebook_dir, os_path): | |
163 | raise web.HTTPError(404, u'diretory does not exist: %r' % os_path) |
|
183 | raise web.HTTPError(404, u'directory does not exist: %r' % os_path) | |
164 | dir_names = os.listdir(os_path) |
|
184 | dir_names = os.listdir(os_path) | |
165 | dirs = [] |
|
185 | dirs = [] | |
166 | for name in dir_names: |
|
186 | for name in dir_names: | |
167 | os_path = self.get_os_path(name, path) |
|
187 | os_path = self.get_os_path(name, path) | |
168 |
if os.path.isdir(os_path) and not |
|
188 | if os.path.isdir(os_path) and not is_hidden(self.notebook_dir, os_path): | |
169 | try: |
|
189 | try: | |
170 | model = self.get_dir_model(name, path) |
|
190 | model = self.get_dir_model(name, path) | |
171 | except IOError: |
|
191 | except IOError: |
@@ -82,7 +82,24 b' class NotebookManager(LoggingConfigurable):' | |||||
82 | Whether the path does indeed exist. |
|
82 | Whether the path does indeed exist. | |
83 | """ |
|
83 | """ | |
84 | raise NotImplementedError |
|
84 | raise NotImplementedError | |
85 |
|
85 | |||
|
86 | def is_hidden(self, path): | |||
|
87 | """Does the API style path correspond to a hidden directory or file? | |||
|
88 | ||||
|
89 | Parameters | |||
|
90 | ---------- | |||
|
91 | path : string | |||
|
92 | The path to check. This is an API path (`/` separated, | |||
|
93 | relative to base notebook-dir). | |||
|
94 | ||||
|
95 | Returns | |||
|
96 | ------- | |||
|
97 | exists : bool | |||
|
98 | Whether the path is hidden. | |||
|
99 | ||||
|
100 | """ | |||
|
101 | raise NotImplementedError | |||
|
102 | ||||
86 | def _notebook_dir_changed(self, name, old, new): |
|
103 | def _notebook_dir_changed(self, name, old, new): | |
87 | """Do a bit of validation of the notebook dir.""" |
|
104 | """Do a bit of validation of the notebook dir.""" | |
88 | if not os.path.isabs(new): |
|
105 | if not os.path.isabs(new): |
@@ -11,10 +11,13 b'' | |||||
11 | # Imports |
|
11 | # Imports | |
12 | #----------------------------------------------------------------------------- |
|
12 | #----------------------------------------------------------------------------- | |
13 |
|
13 | |||
|
14 | import os | |||
|
15 | ||||
14 | import nose.tools as nt |
|
16 | import nose.tools as nt | |
15 |
|
17 | |||
16 | import IPython.testing.tools as tt |
|
18 | import IPython.testing.tools as tt | |
17 | from IPython.html.utils import url_escape, url_unescape |
|
19 | from IPython.html.utils import url_escape, url_unescape, is_hidden | |
|
20 | from IPython.utils.tempdir import TemporaryDirectory | |||
18 |
|
21 | |||
19 | #----------------------------------------------------------------------------- |
|
22 | #----------------------------------------------------------------------------- | |
20 | # Test functions |
|
23 | # Test functions | |
@@ -59,3 +62,14 b' def test_url_unescape():' | |||||
59 | '/%20%21%40%24%23%25%5E%26%2A%20/%20test%20%25%5E%20notebook%20%40%23%24%20name.ipynb') |
|
62 | '/%20%21%40%24%23%25%5E%26%2A%20/%20test%20%25%5E%20notebook%20%40%23%24%20name.ipynb') | |
60 | nt.assert_equal(path, '/ !@$#%^&* / test %^ notebook @#$ name.ipynb') |
|
63 | nt.assert_equal(path, '/ !@$#%^&* / test %^ notebook @#$ name.ipynb') | |
61 |
|
64 | |||
|
65 | def test_is_hidden(): | |||
|
66 | with TemporaryDirectory() as root: | |||
|
67 | subdir1 = os.path.join(root, 'subdir') | |||
|
68 | os.makedirs(subdir1) | |||
|
69 | nt.assert_equal(is_hidden(root, subdir1), False) | |||
|
70 | subdir2 = os.path.join(root, '.subdir2') | |||
|
71 | os.makedirs(subdir2) | |||
|
72 | nt.assert_equal(is_hidden(root, subdir2), True) | |||
|
73 | subdir34 = os.path.join(root, 'subdir3', '.subdir4') | |||
|
74 | os.makedirs(subdir34) | |||
|
75 | nt.assert_equal(is_hidden(root, subdir34), True) |
@@ -19,7 +19,7 b' import os' | |||||
19 |
|
19 | |||
20 | from tornado import web |
|
20 | from tornado import web | |
21 | from ..base.handlers import IPythonHandler, notebook_path_regex, path_regex |
|
21 | from ..base.handlers import IPythonHandler, notebook_path_regex, path_regex | |
22 | from ..utils import url_path_join, path2url, url2path, url_escape |
|
22 | from ..utils import url_path_join, path2url, url2path, url_escape, is_hidden | |
23 |
|
23 | |||
24 | #----------------------------------------------------------------------------- |
|
24 | #----------------------------------------------------------------------------- | |
25 | # Handlers |
|
25 | # Handlers | |
@@ -62,8 +62,8 b' class TreeHandler(IPythonHandler):' | |||||
62 | self.log.debug("Redirecting %s to %s", self.request.path, url) |
|
62 | self.log.debug("Redirecting %s to %s", self.request.path, url) | |
63 | self.redirect(url) |
|
63 | self.redirect(url) | |
64 | else: |
|
64 | else: | |
65 | if not nbm.path_exists(path=path): |
|
65 | if not nbm.path_exists(path=path) or nbm.is_hidden(path): | |
66 | # no such directory, 404 |
|
66 | # Directory is hidden or does not exist. | |
67 | raise web.HTTPError(404) |
|
67 | raise web.HTTPError(404) | |
68 | breadcrumbs = self.generate_breadcrumbs(path) |
|
68 | breadcrumbs = self.generate_breadcrumbs(path) | |
69 | page_title = self.generate_page_title(path) |
|
69 | page_title = self.generate_page_title(path) |
@@ -12,7 +12,11 b' Authors:' | |||||
12 | # the file COPYING, distributed as part of this software. |
|
12 | # the file COPYING, distributed as part of this software. | |
13 | #----------------------------------------------------------------------------- |
|
13 | #----------------------------------------------------------------------------- | |
14 |
|
14 | |||
|
15 | from __future__ import print_function | |||
|
16 | ||||
15 | import os |
|
17 | import os | |
|
18 | import stat | |||
|
19 | ||||
16 | try: |
|
20 | try: | |
17 | from urllib.parse import quote, unquote |
|
21 | from urllib.parse import quote, unquote | |
18 | except ImportError: |
|
22 | except ImportError: | |
@@ -20,6 +24,10 b' except ImportError:' | |||||
20 |
|
24 | |||
21 | from IPython.utils import py3compat |
|
25 | from IPython.utils import py3compat | |
22 |
|
26 | |||
|
27 | # UF_HIDDEN is a stat flag not defined in the stat module. | |||
|
28 | # It is used by BSD to indicate hidden files. | |||
|
29 | UF_HIDDEN = getattr(stat, 'UF_HIDDEN', 32768) | |||
|
30 | ||||
23 | #----------------------------------------------------------------------------- |
|
31 | #----------------------------------------------------------------------------- | |
24 | # Imports |
|
32 | # Imports | |
25 | #----------------------------------------------------------------------------- |
|
33 | #----------------------------------------------------------------------------- | |
@@ -72,3 +80,28 b' def url_unescape(path):' | |||||
72 | for p in py3compat.unicode_to_str(path).split('/') |
|
80 | for p in py3compat.unicode_to_str(path).split('/') | |
73 | ]) |
|
81 | ]) | |
74 |
|
82 | |||
|
83 | def is_hidden(absolute_root, absolute_path): | |||
|
84 | """Is a file is hidden or contained in a hidden directory. | |||
|
85 | ||||
|
86 | Hidden is determined by either name starting with '.' or the UF_HIDDEN | |||
|
87 | flag as reported by stat. | |||
|
88 | ||||
|
89 | Parameters | |||
|
90 | ---------- | |||
|
91 | absolute_root : unicode | |||
|
92 | absolute_path : unicode | |||
|
93 | """ | |||
|
94 | inside_root = absolute_path[len(absolute_root):] | |||
|
95 | if any(part.startswith('.') for part in inside_root.split(os.sep)): | |||
|
96 | return True | |||
|
97 | ||||
|
98 | # check UF_HIDDEN on any location up to root | |||
|
99 | path = absolute_path | |||
|
100 | while path and path.startswith(absolute_root) and path != absolute_root: | |||
|
101 | st = os.stat(path) | |||
|
102 | if getattr(st, 'st_flags', 0) & UF_HIDDEN: | |||
|
103 | return True | |||
|
104 | path = os.path.dirname(path) | |||
|
105 | ||||
|
106 | return False | |||
|
107 |
General Comments 0
You need to be logged in to leave comments.
Login now