68 lines
2.5 KiB
Python
68 lines
2.5 KiB
Python
"""
|
||
Download Blueprint
|
||
Handles file download endpoints.
|
||
"""
|
||
|
||
from pathlib import Path
|
||
from flask import Blueprint, send_file, current_app
|
||
|
||
from ..shared import _format_response, log_performance, logger
|
||
|
||
# Create blueprint
|
||
download_bp = Blueprint('download', __name__, url_prefix='/download')
|
||
|
||
|
||
@download_bp.route('/<path:filename>')
|
||
@log_performance
|
||
def download_file(filename):
|
||
"""Download a processed file."""
|
||
from flask import request
|
||
|
||
logger.info(f"Download request for file: {filename} from IP {request.remote_addr}")
|
||
|
||
try:
|
||
# 支持两种路径格式:
|
||
# 1. 绝对路径(以 / 开头,如 /full/path/to/file)
|
||
# 2. 相对路径(task_id/filename)
|
||
if filename.startswith('/'):
|
||
# 绝对路径 - 直接使用
|
||
file_path = Path(filename)
|
||
else:
|
||
# 相对路径 - 相对于 OUTPUT_FOLDER
|
||
output_folder = Path(current_app.config.get('OUTPUT_FOLDER') or '')
|
||
if not output_folder:
|
||
logger.error("OUTPUT_FOLDER not configured")
|
||
return _format_response(500, "服务器配置错误")
|
||
|
||
# 解析 task_id/filename 格式
|
||
parts = filename.split('/', 1)
|
||
if len(parts) != 2:
|
||
logger.warning(f"Invalid relative path format: {filename}")
|
||
return _format_response(400, "无效的文件路径")
|
||
|
||
task_id, filename_part = parts
|
||
file_path = output_folder / task_id / filename_part
|
||
|
||
# Security check - ensure file is within output folder
|
||
file_path = file_path.resolve()
|
||
output_folder = Path(current_app.config.get('OUTPUT_FOLDER') or '').resolve()
|
||
if output_folder and not str(file_path).startswith(str(output_folder)):
|
||
logger.warning(f"Security violation: Attempted to access file outside output folder: {filename}")
|
||
return _format_response(403, "访问被拒绝")
|
||
|
||
if not file_path.exists():
|
||
logger.warning(f"File not found: {filename}")
|
||
return _format_response(404, "文件未找到")
|
||
|
||
if not file_path.is_file():
|
||
logger.warning(f"Path is not a file: {filename}")
|
||
return _format_response(400, "不是文件")
|
||
|
||
file_size = file_path.stat().st_size
|
||
logger.info(f"Serving file: {filename} ({file_size} bytes)")
|
||
|
||
return send_file(file_path)
|
||
|
||
except Exception as e:
|
||
logger.error(f"Error serving file {filename}: {str(e)}", exc_info=True)
|
||
return _format_response(500, "内部服务器错误") |