1515from dotenv import load_dotenv
1616from celery .result import AsyncResult
1717from celery_worker import celery , process_tts_request
18+ from werkzeug .utils import secure_filename
19+ import os .path
1820
1921# Configure logging
2022logging .basicConfig (
@@ -75,9 +77,20 @@ def serve_static_files(path):
7577 if path == '' :
7678 return send_from_directory ('static' , 'index.html' )
7779
78- full_path = Path ('static' ) / path
80+ # Secure the filename and prevent path traversal
81+ secure_path = secure_filename (path )
82+ if not secure_path or secure_path != path :
83+ return "Invalid path" , 400
7984
80- if full_path .exists ():
85+ # Normalize and validate the path
86+ base_path = os .path .abspath ('static' )
87+ full_path = os .path .normpath (os .path .join (base_path , secure_path ))
88+
89+ # Ensure the path is within the static directory
90+ if not full_path .startswith (base_path ):
91+ return "Invalid path" , 400
92+
93+ if os .path .exists (full_path ):
8194 # Determine content type
8295 content_type = {
8396 '.html' : 'text/html' ,
@@ -87,7 +100,7 @@ def serve_static_files(path):
87100 '.jpg' : 'image/jpeg' ,
88101 '.gif' : 'image/gif' ,
89102 '.ico' : 'image/x-icon'
90- }.get (full_path . suffix , 'application/octet-stream' )
103+ }.get (os . path . splitext ( full_path )[ 1 ] , 'application/octet-stream' )
91104
92105 return send_file (full_path , mimetype = content_type )
93106
@@ -236,8 +249,24 @@ def voice_sample(voice):
236249 "error" : "Voice parameter is required"
237250 }), 400
238251
239- sample_path = VOICE_SAMPLES_DIR / f"{ voice } _sample.mp3"
240- if not sample_path .exists ():
252+ # Secure the voice parameter and prevent path traversal
253+ secure_voice = secure_filename (voice )
254+ if not secure_voice or secure_voice != voice :
255+ return jsonify ({
256+ "error" : "Invalid voice parameter"
257+ }), 400
258+
259+ # Normalize and validate the path
260+ base_path = os .path .abspath (VOICE_SAMPLES_DIR )
261+ sample_path = os .path .normpath (os .path .join (base_path , f"{ secure_voice } _sample.mp3" ))
262+
263+ # Ensure the path is within the voice samples directory
264+ if not sample_path .startswith (base_path ):
265+ return jsonify ({
266+ "error" : "Invalid path"
267+ }), 400
268+
269+ if not os .path .exists (sample_path ):
241270 return jsonify ({
242271 "error" : f"Sample not found for voice: { voice } "
243272 }), 404
@@ -246,7 +275,7 @@ def voice_sample(voice):
246275 sample_path ,
247276 mimetype = "audio/mpeg" ,
248277 as_attachment = False ,
249- download_name = f"{ voice } _sample.mp3"
278+ download_name = f"{ secure_voice } _sample.mp3"
250279 )
251280
252281 except Exception as e :
0 commit comments