@@ -445,46 +445,53 @@ async def download_scenario(form: DownloadScenario, project_root: CEAProjectRoot
445
445
446
446
# Use compresslevel=1 for faster zipping, at the cost of compression ratio
447
447
with zipfile .ZipFile (temp_file , 'w' , zipfile .ZIP_DEFLATED , compresslevel = 1 ) as zip_file :
448
- base_path = Path (project_root ) / project
448
+ base_path = Path (secure_path ( Path ( project_root , project ). resolve ()))
449
449
450
450
# Collect all files first for batch processing
451
451
files_to_zip = []
452
452
for scenario in scenarios :
453
- scenario_path = base_path / scenario
453
+ # sanitize scenario for fs ops and zip arcnames
454
+ scenario_name = Path (scenario ).name
455
+ scenario_path = Path (secure_path ((base_path / scenario_name ).resolve ()))
456
+
454
457
if not scenario_path .exists ():
455
458
continue
456
459
457
- if input_files :
458
- input_paths = ( scenario_path / "inputs" )
460
+ input_paths = ( scenario_path / "inputs" )
461
+ if input_files and input_paths . exists ():
459
462
for root , dirs , files in os .walk (input_paths ):
460
463
root_path = Path (root )
461
464
for file in files :
462
465
if Path (file ).suffix in VALID_EXTENSIONS :
463
466
item_path = root_path / file
464
- relative_path = str (Path (scenario ) / "inputs" / item_path .relative_to (input_paths ))
467
+ relative_path = str (Path (scenario_name ) / "inputs" / item_path .relative_to (input_paths ))
465
468
files_to_zip .append ((item_path , relative_path ))
466
469
467
- if output_files_level == "detailed" :
468
- output_paths = ( scenario_path / "outputs" )
470
+ output_paths = ( scenario_path / "outputs" )
471
+ if output_files_level == "detailed" and output_paths . exists ():
469
472
for root , dirs , files in os .walk (output_paths ):
470
473
root_path = Path (root )
471
474
for file in files :
472
475
if Path (file ).suffix in VALID_EXTENSIONS :
473
476
item_path = root_path / file
474
- relative_path = str (Path (scenario ) / "outputs" / item_path .relative_to (output_paths ))
477
+ relative_path = str (Path (scenario_name ) / "outputs" / item_path .relative_to (output_paths ))
475
478
files_to_zip .append ((item_path , relative_path ))
479
+
476
480
elif output_files_level == "summary" :
477
481
# create summary files first
478
- await run_in_threadpool (run_summary , str (base_path ), scenario )
482
+ await run_in_threadpool (run_summary , str (base_path ), scenario_name )
479
483
480
484
export_paths = (scenario_path / "export" / "results" )
485
+ if not export_paths .exists ():
486
+ raise ValueError (f"Export results path does not exist for scenario { scenario_name } " )
487
+
481
488
for root , dirs , files in os .walk (export_paths ):
482
489
root_path = Path (root )
483
490
for file in files :
484
491
if Path (file ).suffix in VALID_EXTENSIONS :
485
492
item_path = root_path / file
486
493
relative_path = str (
487
- Path (scenario ) / "export" / "results" / item_path .relative_to (export_paths ))
494
+ Path (scenario_name ) / "export" / "results" / item_path .relative_to (export_paths ))
488
495
files_to_zip .append ((item_path , relative_path ))
489
496
490
497
# Batch write all files to zip
0 commit comments