21
21
import aiofiles
22
22
import aiofiles .os
23
23
from fastapi import APIRouter , Request
24
+ from fastapi .exceptions import RequestValidationError
24
25
from fastapi .responses import FileResponse
25
26
26
27
from covalent ._shared_files import logger
@@ -46,18 +47,23 @@ async def _transfer_data(req: Request, dest_path: str):
46
47
await aiofiles .os .replace (tmp_path , dest_path )
47
48
48
49
50
+ # Resolve path to an absolute path and check that it
51
+ # doesn't escape the data directory root
52
+ def _sanitize_path (path : str ) -> str :
53
+ abs_path = os .path .realpath (path )
54
+ if not abs_path .startswith (BASE_PATH ) or len (abs_path ) <= len (BASE_PATH ):
55
+ raise RequestValidationError (f"Invalid object key { path } " )
56
+ return abs_path
57
+
58
+
49
59
@router .get ("/files/{object_key:path}" )
50
60
async def download_file (object_key : str ):
51
- # TODO: reject relative path components
52
-
53
- path = os .path .join (BASE_PATH , object_key )
61
+ path = _sanitize_path (os .path .join (BASE_PATH , object_key ))
54
62
return FileResponse (path )
55
63
56
64
57
65
@router .put ("/files/{object_key:path}" )
58
66
async def upload_file (req : Request , object_key : str ):
59
- # TODO: reject relative path components
60
-
61
- path = os .path .join (BASE_PATH , object_key )
67
+ path = _sanitize_path (os .path .join (BASE_PATH , object_key ))
62
68
os .makedirs (os .path .dirname (path ), exist_ok = True )
63
69
await _transfer_data (req , path )
0 commit comments