10
10
import inspect
11
11
12
12
from collections .abc import Sized , Iterable , Container
13
+ from pathlib import Path
13
14
from urllib .parse import urlencode , unquote
14
15
from types import MappingProxyType
15
16
@@ -159,14 +160,17 @@ def __init__(self, name, prefix, directory, *,
159
160
'GET' , self .handle , name , expect_handler = expect_handler )
160
161
self ._prefix = prefix
161
162
self ._prefix_len = len (self ._prefix )
162
- self ._directory = os .path .abspath (directory ) + os .sep
163
+ try :
164
+ directory = Path (directory ).resolve ()
165
+ if not directory .is_dir ():
166
+ raise ValueError ('Not a directory' )
167
+ except (FileNotFoundError , ValueError ) as error :
168
+ raise ValueError (
169
+ "No directory exists at '{}'" .format (directory )) from error
170
+ self ._directory = directory
163
171
self ._chunk_size = chunk_size
164
172
self ._response_factory = response_factory
165
173
166
- if not os .path .isdir (self ._directory ):
167
- raise ValueError (
168
- "No directory exists at '{}'" .format (self ._directory ))
169
-
170
174
if bool (os .environ .get ("AIOHTTP_NOSENDFILE" )):
171
175
self ._sendfile = self ._sendfile_fallback
172
176
@@ -176,6 +180,8 @@ def match(self, path):
176
180
return {'filename' : path [self ._prefix_len :]}
177
181
178
182
def url (self , * , filename , query = None ):
183
+ if isinstance (filename , Path ):
184
+ filename = str (getattr (filename , 'path' , filename ))
179
185
while filename .startswith ('/' ):
180
186
filename = filename [1 :]
181
187
url = self ._prefix + filename
@@ -266,19 +272,25 @@ def _sendfile_fallback(self, req, resp, fobj, count):
266
272
@asyncio .coroutine
267
273
def handle (self , request ):
268
274
filename = request .match_info ['filename' ]
269
- filepath = os .path .abspath (os .path .join (self ._directory , filename ))
270
- if not filepath .startswith (self ._directory ):
271
- raise HTTPNotFound ()
272
- if not os .path .exists (filepath ) or not os .path .isfile (filepath ):
273
- raise HTTPNotFound ()
274
-
275
- st = os .stat (filepath )
275
+ try :
276
+ filepath = self ._directory .joinpath (filename ).resolve ()
277
+ filepath .relative_to (self ._directory )
278
+ except (ValueError , FileNotFoundError ) as error :
279
+ # relatively safe
280
+ raise HTTPNotFound () from error
281
+ except Exception as error :
282
+ # perm error or other kind!
283
+ request .logger .exception (error )
284
+ raise HTTPNotFound () from error
285
+
286
+ st = filepath .stat ()
276
287
277
288
modsince = request .if_modified_since
278
289
if modsince is not None and st .st_mtime <= modsince .timestamp ():
279
290
raise HTTPNotModified ()
280
291
281
- ct , encoding = mimetypes .guess_type (filepath )
292
+ path = str (getattr (filepath , 'path' , filename ))
293
+ ct , encoding = mimetypes .guess_type (path )
282
294
if not ct :
283
295
ct = 'application/octet-stream'
284
296
@@ -295,7 +307,7 @@ def handle(self, request):
295
307
try :
296
308
yield from resp .prepare (request )
297
309
298
- with open (filepath , 'rb' ) as f :
310
+ with filepath . open ('rb' ) as f :
299
311
yield from self ._sendfile (request , resp , f , file_size )
300
312
301
313
finally :
0 commit comments