6
6
from __future__ import print_function
7
7
8
8
import asyncio
9
- import concurrent .futures
10
- import ctypes
11
9
import errno
12
10
import inspect
13
11
import os
14
- import stat
15
12
import sys
16
13
from distutils .version import LooseVersion
17
- from contextlib import contextmanager
18
-
19
14
20
15
from urllib .parse import quote , unquote , urlparse , urljoin
21
16
from urllib .request import pathname2url
22
17
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
28
18
from ipython_genutils import py3compat
29
19
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
-
45
20
46
21
def url_path_join (* pieces ):
47
22
"""Join components of url into a relative url
@@ -58,10 +33,12 @@ def url_path_join(*pieces):
58
33
if result == '//' : result = '/'
59
34
return result
60
35
36
+
61
37
def url_is_absolute (url ):
62
38
"""Determine whether a given URL is absolute"""
63
39
return urlparse (url ).path .startswith ("/" )
64
40
41
+
65
42
def path2url (path ):
66
43
"""Convert a local file path to a URL"""
67
44
pieces = [ quote (p ) for p in path .split (os .sep ) ]
@@ -71,12 +48,14 @@ def path2url(path):
71
48
url = url_path_join (* pieces )
72
49
return url
73
50
51
+
74
52
def url2path (url ):
75
53
"""Convert a URL to a local file path"""
76
54
pieces = [ unquote (p ) for p in url .split ('/' ) ]
77
55
path = os .path .join (* pieces )
78
56
return path
79
57
58
+
80
59
def url_escape (path ):
81
60
"""Escape special characters in a URL path
82
61
@@ -85,6 +64,7 @@ def url_escape(path):
85
64
parts = py3compat .unicode_to_str (path , encoding = 'utf8' ).split ('/' )
86
65
return u'/' .join ([quote (p ) for p in parts ])
87
66
67
+
88
68
def url_unescape (path ):
89
69
"""Unescape special characters in a URL path
90
70
@@ -96,201 +76,6 @@ def url_unescape(path):
96
76
])
97
77
98
78
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
-
294
79
def samefile_simple (path , other_path ):
295
80
"""
296
81
Fill in for os.path.samefile when it is unavailable (Windows+py2).
@@ -328,6 +113,7 @@ def to_os_path(path, root=''):
328
113
path = os .path .join (root , * parts )
329
114
return path
330
115
116
+
331
117
def to_api_path (os_path , root = '' ):
332
118
"""Convert a filesystem path to an API path
333
119
0 commit comments