18
18
import json
19
19
import mimetypes
20
20
import os
21
- import posixpath
22
21
import secrets
23
22
import time
24
23
import traceback
35
34
Optional ,
36
35
Type ,
37
36
Union ,
37
+ cast ,
38
38
)
39
39
40
40
import fastapi
73
73
ComponentServerBlobBody ,
74
74
ComponentServerJSONBody ,
75
75
DataWithFiles ,
76
+ DeveloperPath ,
76
77
PredictBody ,
77
78
ResetBody ,
78
79
SimplePredictBody ,
80
+ UserProvidedPath ,
79
81
)
82
+ from gradio .exceptions import InvalidPathError
80
83
from gradio .oauth import attach_oauth
81
84
from gradio .route_utils import ( # noqa: F401
82
85
CustomCORSMiddleware ,
109
112
110
113
mimetypes .init ()
111
114
112
- STATIC_TEMPLATE_LIB = files ("gradio" ).joinpath ("templates" ).as_posix () # type: ignore
113
- STATIC_PATH_LIB = files ("gradio" ).joinpath ("templates" , "frontend" , "static" ).as_posix () # type: ignore
114
- BUILD_PATH_LIB = files ("gradio" ).joinpath ("templates" , "frontend" , "assets" ).as_posix () # type: ignore
115
+ STATIC_TEMPLATE_LIB = cast (
116
+ DeveloperPath ,
117
+ files ("gradio" ).joinpath ("templates" ).as_posix (), # type: ignore
118
+ )
119
+ STATIC_PATH_LIB = cast (
120
+ DeveloperPath ,
121
+ files ("gradio" ).joinpath ("templates" , "frontend" , "static" ).as_posix (), # type: ignore
122
+ )
123
+ BUILD_PATH_LIB = cast (
124
+ DeveloperPath ,
125
+ files ("gradio" ).joinpath ("templates" , "frontend" , "assets" ).as_posix (), # type: ignore
126
+ )
115
127
VERSION = get_package_version ()
116
128
117
129
@@ -446,7 +458,7 @@ def get_config(request: fastapi.Request):
446
458
447
459
@app .get ("/static/{path:path}" )
448
460
def static_resource (path : str ):
449
- static_file = safe_join (STATIC_PATH_LIB , path )
461
+ static_file = routes_safe_join (STATIC_PATH_LIB , UserProvidedPath ( path ) )
450
462
return FileResponse (static_file )
451
463
452
464
@app .get ("/custom_component/{id}/{type}/{file_name}" )
@@ -458,7 +470,6 @@ def custom_component_path(
458
470
location = next (
459
471
(item for item in components if item ["component_class_id" ] == id ), None
460
472
)
461
-
462
473
if location is None :
463
474
raise HTTPException (status_code = 404 , detail = "Component not found." )
464
475
@@ -470,9 +481,14 @@ def custom_component_path(
470
481
if module_path is None or component_instance is None :
471
482
raise HTTPException (status_code = 404 , detail = "Component not found." )
472
483
473
- path = safe_join (
474
- str (Path (module_path ).parent ),
475
- f"{ component_instance .__class__ .TEMPLATE_DIR } /{ type } /{ file_name } " ,
484
+ requested_path = utils .safe_join (
485
+ component_instance .__class__ .TEMPLATE_DIR ,
486
+ UserProvidedPath (f"{ type } /{ file_name } " ),
487
+ )
488
+
489
+ path = routes_safe_join (
490
+ DeveloperPath (str (Path (module_path ).parent )),
491
+ UserProvidedPath (requested_path ),
476
492
)
477
493
478
494
key = f"{ id } -{ type } -{ file_name } "
@@ -494,7 +510,7 @@ def custom_component_path(
494
510
495
511
@app .get ("/assets/{path:path}" )
496
512
def build_resource (path : str ):
497
- build_file = safe_join (BUILD_PATH_LIB , path )
513
+ build_file = routes_safe_join (BUILD_PATH_LIB , UserProvidedPath ( path ) )
498
514
return FileResponse (build_file )
499
515
500
516
@app .get ("/favicon.ico" )
@@ -543,7 +559,7 @@ async def file(path_or_url: str, request: fastapi.Request):
543
559
544
560
is_dir = abs_path .is_dir ()
545
561
546
- if in_blocklist or is_dir :
562
+ if is_dir or in_blocklist :
547
563
raise HTTPException (403 , f"File not allowed: { path_or_url } ." )
548
564
549
565
created_by_app = False
@@ -1142,7 +1158,14 @@ async def upload_file(
1142
1158
name = f"tmp{ secrets .token_hex (5 )} "
1143
1159
directory = Path (app .uploaded_file_dir ) / temp_file .sha .hexdigest ()
1144
1160
directory .mkdir (exist_ok = True , parents = True )
1145
- dest = (directory / name ).resolve ()
1161
+ try :
1162
+ dest = utils .safe_join (
1163
+ DeveloperPath (str (directory )), UserProvidedPath (name )
1164
+ )
1165
+ except InvalidPathError as err :
1166
+ raise HTTPException (
1167
+ status_code = 400 , detail = f"Invalid file name: { name } "
1168
+ ) from err
1146
1169
temp_file .file .close ()
1147
1170
# we need to move the temp file to the cache directory
1148
1171
# but that's possibly blocking and we're in an async function
@@ -1153,9 +1176,9 @@ async def upload_file(
1153
1176
os .rename (temp_file .file .name , dest )
1154
1177
except OSError :
1155
1178
files_to_copy .append (temp_file .file .name )
1156
- locations .append (str ( dest ) )
1179
+ locations .append (dest )
1157
1180
output_files .append (dest )
1158
- blocks .upload_file_set .add (str ( dest ) )
1181
+ blocks .upload_file_set .add (dest )
1159
1182
if files_to_copy :
1160
1183
bg_tasks .add_task (
1161
1184
move_uploaded_files_to_cache , files_to_copy , locations
@@ -1218,32 +1241,22 @@ async def analytics_dashboard(key: str):
1218
1241
########
1219
1242
1220
1243
1221
- def safe_join (directory : str , path : str ) -> str :
1222
- """Safely path to a base directory to avoid escaping the base directory.
1223
- Borrowed from: werkzeug.security.safe_join"""
1224
- _os_alt_seps : List [str ] = [
1225
- sep for sep in [os .path .sep , os .path .altsep ] if sep is not None and sep != "/"
1226
- ]
1227
-
1244
+ def routes_safe_join (directory : DeveloperPath , path : UserProvidedPath ) -> str :
1245
+ """Safely join the user path to the directory while performing some additional http-related checks,
1246
+ e.g. ensuring that the full path exists on the local file system and is not a directory"""
1228
1247
if path == "" :
1229
- raise HTTPException (400 )
1248
+ raise fastapi . HTTPException (400 )
1230
1249
if route_utils .starts_with_protocol (path ):
1231
- raise HTTPException (403 )
1232
- filename = posixpath .normpath (path )
1233
- fullpath = os .path .join (directory , filename )
1234
- if (
1235
- any (sep in filename for sep in _os_alt_seps )
1236
- or os .path .isabs (filename )
1237
- or filename == ".."
1238
- or filename .startswith ("../" )
1239
- or os .path .isdir (fullpath )
1240
- ):
1241
- raise HTTPException (403 )
1242
-
1243
- if not os .path .exists (fullpath ):
1244
- raise HTTPException (404 , "File not found" )
1245
-
1246
- return fullpath
1250
+ raise fastapi .HTTPException (403 )
1251
+ try :
1252
+ fullpath = Path (utils .safe_join (directory , path ))
1253
+ except InvalidPathError as e :
1254
+ raise fastapi .HTTPException (403 ) from e
1255
+ if fullpath .is_dir ():
1256
+ raise fastapi .HTTPException (403 )
1257
+ if not fullpath .exists ():
1258
+ raise fastapi .HTTPException (404 )
1259
+ return str (fullpath )
1247
1260
1248
1261
1249
1262
def get_types (cls_set : List [Type ]):
0 commit comments