66from __future__ import print_function
77
88import asyncio
9- import concurrent .futures
10- import ctypes
119import errno
1210import inspect
1311import os
14- import stat
1512import sys
1613from distutils .version import LooseVersion
17- from contextlib import contextmanager
18-
1914
2015from urllib .parse import quote , unquote , urlparse , urljoin
2116from urllib .request import pathname2url
2217
23-
24- # tornado.concurrent.Future is asyncio.Future
25- # in tornado >=5 with Python 3
26- from tornado .concurrent import Future as TornadoFuture
27- from tornado import gen
2818from ipython_genutils import py3compat
2919
30- # UF_HIDDEN is a stat flag not defined in the stat module.
31- # It is used by BSD to indicate hidden files.
32- UF_HIDDEN = getattr (stat , 'UF_HIDDEN' , 32768 )
33-
34-
35- def exists (path ):
36- """Replacement for `os.path.exists` which works for host mapped volumes
37- on Windows containers
38- """
39- try :
40- os .lstat (path )
41- except OSError :
42- return False
43- return True
44-
4520
4621def url_path_join (* pieces ):
4722 """Join components of url into a relative url
@@ -58,10 +33,12 @@ def url_path_join(*pieces):
5833 if result == '//' : result = '/'
5934 return result
6035
36+
6137def url_is_absolute (url ):
6238 """Determine whether a given URL is absolute"""
6339 return urlparse (url ).path .startswith ("/" )
6440
41+
6542def path2url (path ):
6643 """Convert a local file path to a URL"""
6744 pieces = [ quote (p ) for p in path .split (os .sep ) ]
@@ -71,12 +48,14 @@ def path2url(path):
7148 url = url_path_join (* pieces )
7249 return url
7350
51+
7452def url2path (url ):
7553 """Convert a URL to a local file path"""
7654 pieces = [ unquote (p ) for p in url .split ('/' ) ]
7755 path = os .path .join (* pieces )
7856 return path
7957
58+
8059def url_escape (path ):
8160 """Escape special characters in a URL path
8261
@@ -85,6 +64,7 @@ def url_escape(path):
8564 parts = py3compat .unicode_to_str (path , encoding = 'utf8' ).split ('/' )
8665 return u'/' .join ([quote (p ) for p in parts ])
8766
67+
8868def url_unescape (path ):
8969 """Unescape special characters in a URL path
9070
@@ -96,201 +76,6 @@ def url_unescape(path):
9676 ])
9777
9878
99- def is_file_hidden_win (abs_path , stat_res = None ):
100- """Is a file hidden?
101-
102- This only checks the file itself; it should be called in combination with
103- checking the directory containing the file.
104-
105- Use is_hidden() instead to check the file and its parent directories.
106-
107- Parameters
108- ----------
109- abs_path : unicode
110- The absolute path to check.
111- stat_res : os.stat_result, optional
112- Ignored on Windows, exists for compatibility with POSIX version of the
113- function.
114- """
115- if os .path .basename (abs_path ).startswith ('.' ):
116- return True
117-
118- win32_FILE_ATTRIBUTE_HIDDEN = 0x02
119- try :
120- attrs = ctypes .windll .kernel32 .GetFileAttributesW (
121- py3compat .cast_unicode (abs_path )
122- )
123- except AttributeError :
124- pass
125- else :
126- if attrs > 0 and attrs & win32_FILE_ATTRIBUTE_HIDDEN :
127- return True
128-
129- return False
130-
131- def is_file_hidden_posix (abs_path , stat_res = None ):
132- """Is a file hidden?
133-
134- This only checks the file itself; it should be called in combination with
135- checking the directory containing the file.
136-
137- Use is_hidden() instead to check the file and its parent directories.
138-
139- Parameters
140- ----------
141- abs_path : unicode
142- The absolute path to check.
143- stat_res : os.stat_result, optional
144- The result of calling stat() on abs_path. If not passed, this function
145- will call stat() internally.
146- """
147- if os .path .basename (abs_path ).startswith ('.' ):
148- return True
149-
150- if stat_res is None or stat .S_ISLNK (stat_res .st_mode ):
151- try :
152- stat_res = os .stat (abs_path )
153- except OSError as e :
154- if e .errno == errno .ENOENT :
155- return False
156- raise
157-
158- # check that dirs can be listed
159- if stat .S_ISDIR (stat_res .st_mode ):
160- # use x-access, not actual listing, in case of slow/large listings
161- if not os .access (abs_path , os .X_OK | os .R_OK ):
162- return True
163-
164- # check UF_HIDDEN
165- if getattr (stat_res , 'st_flags' , 0 ) & UF_HIDDEN :
166- return True
167-
168- return False
169-
170- if sys .platform == 'win32' :
171- is_file_hidden = is_file_hidden_win
172- else :
173- is_file_hidden = is_file_hidden_posix
174-
175- def is_hidden (abs_path , abs_root = '' ):
176- """Is a file hidden or contained in a hidden directory?
177-
178- This will start with the rightmost path element and work backwards to the
179- given root to see if a path is hidden or in a hidden directory. Hidden is
180- determined by either name starting with '.' or the UF_HIDDEN flag as
181- reported by stat.
182-
183- If abs_path is the same directory as abs_root, it will be visible even if
184- that is a hidden folder. This only checks the visibility of files
185- and directories *within* abs_root.
186-
187- Parameters
188- ----------
189- abs_path : unicode
190- The absolute path to check for hidden directories.
191- abs_root : unicode
192- The absolute path of the root directory in which hidden directories
193- should be checked for.
194- """
195- if os .path .normpath (abs_path ) == os .path .normpath (abs_root ):
196- return False
197-
198- if is_file_hidden (abs_path ):
199- return True
200-
201- if not abs_root :
202- abs_root = abs_path .split (os .sep , 1 )[0 ] + os .sep
203- inside_root = abs_path [len (abs_root ):]
204- if any (part .startswith ('.' ) for part in inside_root .split (os .sep )):
205- return True
206-
207- # check UF_HIDDEN on any location up to root.
208- # is_file_hidden() already checked the file, so start from its parent dir
209- path = os .path .dirname (abs_path )
210- while path and path .startswith (abs_root ) and path != abs_root :
211- if not exists (path ):
212- path = os .path .dirname (path )
213- continue
214- try :
215- # may fail on Windows junctions
216- st = os .lstat (path )
217- except OSError :
218- return True
219- if getattr (st , 'st_flags' , 0 ) & UF_HIDDEN :
220- return True
221- path = os .path .dirname (path )
222-
223- return False
224-
225- # TODO: Move to jupyter_core
226- def win32_restrict_file_to_user (fname ):
227- """Secure a windows file to read-only access for the user.
228- Follows guidance from win32 library creator:
229- http://timgolden.me.uk/python/win32_how_do_i/add-security-to-a-file.html
230-
231- This method should be executed against an already generated file which
232- has no secrets written to it yet.
233-
234- Parameters
235- ----------
236-
237- fname : unicode
238- The path to the file to secure
239- """
240- import win32api
241- import win32security
242- import ntsecuritycon as con
243-
244- # everyone, _domain, _type = win32security.LookupAccountName("", "Everyone")
245- admins = win32security .CreateWellKnownSid (win32security .WinBuiltinAdministratorsSid )
246- user , _domain , _type = win32security .LookupAccountName ("" , win32api .GetUserName ())
247-
248- sd = win32security .GetFileSecurity (fname , win32security .DACL_SECURITY_INFORMATION )
249-
250- dacl = win32security .ACL ()
251- # dacl.AddAccessAllowedAce(win32security.ACL_REVISION, con.FILE_ALL_ACCESS, everyone)
252- dacl .AddAccessAllowedAce (win32security .ACL_REVISION , con .FILE_GENERIC_READ | con .FILE_GENERIC_WRITE , user )
253- dacl .AddAccessAllowedAce (win32security .ACL_REVISION , con .FILE_ALL_ACCESS , admins )
254-
255- sd .SetSecurityDescriptorDacl (1 , dacl , 0 )
256- win32security .SetFileSecurity (fname , win32security .DACL_SECURITY_INFORMATION , sd )
257-
258- # TODO: Move to jupyter_core
259- @contextmanager
260- def secure_write (fname , binary = False ):
261- """Opens a file in the most restricted pattern available for
262- writing content. This limits the file mode to `600` and yields
263- the resulting opened filed handle.
264-
265- Parameters
266- ----------
267-
268- fname : unicode
269- The path to the file to write
270- """
271- mode = 'wb' if binary else 'w'
272- open_flag = os .O_CREAT | os .O_WRONLY | os .O_TRUNC
273- try :
274- os .remove (fname )
275- except (IOError , OSError ):
276- # Skip any issues with the file not existing
277- pass
278-
279- if os .name == 'nt' :
280- # Python on windows does not respect the group and public bits for chmod, so we need
281- # to take additional steps to secure the contents.
282- # Touch file pre-emptively to avoid editing permissions in open files in Windows
283- fd = os .open (fname , os .O_CREAT | os .O_WRONLY | os .O_TRUNC , 0o600 )
284- os .close (fd )
285- open_flag = os .O_WRONLY | os .O_TRUNC
286- win32_restrict_file_to_user (fname )
287-
288- with os .fdopen (os .open (fname , open_flag , 0o600 ), mode ) as f :
289- if os .name != 'nt' :
290- # Enforce that the file got the requested permissions before writing
291- assert '0600' == oct (stat .S_IMODE (os .stat (fname ).st_mode )).replace ('0o' , '0' )
292- yield f
293-
29479def samefile_simple (path , other_path ):
29580 """
29681 Fill in for os.path.samefile when it is unavailable (Windows+py2).
@@ -328,6 +113,7 @@ def to_os_path(path, root=''):
328113 path = os .path .join (root , * parts )
329114 return path
330115
116+
331117def to_api_path (os_path , root = '' ):
332118 """Convert a filesystem path to an API path
333119
0 commit comments