86 lines
3.4 KiB
Python
86 lines
3.4 KiB
Python
"""
|
|
StorageView 文件下载模块
|
|
提供文件下载功能(导出日志、导出配置等场景使用)。
|
|
"""
|
|
from app.views.ViewsBase import *
|
|
from app.utils.LanguageUtils import LANG_VIEWS_T
|
|
from django.core import signing
|
|
from django.utils.encoding import escape_uri_path
|
|
import os
|
|
|
|
|
|
DOWNLOAD_ID_SALT = "monitor.temp-download.v1"
|
|
DOWNLOAD_ID_MAX_AGE_SECONDS = 300
|
|
ALLOWED_DOWNLOAD_SUFFIXES = frozenset((
|
|
".mp4", ".wav", ".jpg", ".png", ".tar", ".xclogs", ".xcsettings", ".xcupdate", ".xcflow",
|
|
))
|
|
|
|
|
|
def _safe_temp_download_path(filename, require_exists=True):
|
|
if not filename or filename != os.path.basename(filename):
|
|
raise ValueError("invalid download file")
|
|
if os.path.splitext(filename)[1].lower() not in ALLOWED_DOWNLOAD_SUFFIXES:
|
|
raise ValueError("unsupported download file type")
|
|
base = os.path.realpath(g_config.storageTempDir)
|
|
path = os.path.realpath(os.path.join(base, filename))
|
|
if os.path.dirname(path) != base:
|
|
raise ValueError("invalid download path")
|
|
if require_exists and not os.path.isfile(path):
|
|
raise FileNotFoundError("download file not found")
|
|
return path
|
|
|
|
|
|
def issue_download_file_id(filename):
|
|
"""Return an opaque, short-lived identifier for a server-created temp file."""
|
|
_safe_temp_download_path(filename, require_exists=True)
|
|
return signing.dumps({"name": filename}, salt=DOWNLOAD_ID_SALT, compress=True)
|
|
|
|
|
|
def resolve_download_file_id(file_id):
|
|
data = signing.loads(file_id, salt=DOWNLOAD_ID_SALT, max_age=DOWNLOAD_ID_MAX_AGE_SECONDS)
|
|
if not isinstance(data, dict):
|
|
raise signing.BadSignature("invalid download id")
|
|
filename = str(data.get("name") or "")
|
|
return filename, _safe_temp_download_path(filename, require_exists=True)
|
|
|
|
|
|
def api_openInfo(request):
|
|
"""查询存储空间信息(原 Storage 模块,已移除)"""
|
|
if request.method != 'GET':
|
|
return f_responseJson({"code": 0, "msg": LANG_VIEWS_T(request, "msg_method_not_supported")})
|
|
|
|
__check_ret, __check_msg = f_checkRequestSafe(request)
|
|
if not __check_ret:
|
|
return f_responseJson({"code": 0, "msg": __check_msg})
|
|
|
|
g_logger.info("StorageView.api_openInfo() ip:%s" % f_parseRequestIp(request))
|
|
return f_responseJson({
|
|
"code": 1000,
|
|
"msg": "ok",
|
|
"info": {
|
|
"alarmFolderSize": 0,
|
|
"recordFolderSize": 0
|
|
}
|
|
})
|
|
|
|
|
|
def api_openDownload(request):
|
|
"""Download a server-created temp file by a short-lived signed identifier."""
|
|
__check_ret, __check_msg = f_checkRequestSafe(request)
|
|
if not __check_ret:
|
|
return f_responseJson({"code": 0, "msg": __check_msg})
|
|
params = f_parseGetParams(request)
|
|
file_id = params.get("file_id", "").strip()
|
|
try:
|
|
if not file_id:
|
|
raise signing.BadSignature("missing download id")
|
|
filename, filepath = resolve_download_file_id(file_id)
|
|
from django.http import FileResponse
|
|
response = FileResponse(open(filepath, mode="rb"), content_type="application/octet-stream")
|
|
response['Content-Disposition'] = "attachment; filename={};".format(escape_uri_path(filename))
|
|
response['Cache-Control'] = "no-store"
|
|
return response
|
|
except (signing.BadSignature, signing.SignatureExpired, ValueError, FileNotFoundError) as e:
|
|
g_logger.warning("StorageView.openDownload() rejected: %s" % str(e))
|
|
return f_responseJson({"code": 0, "msg": "invalid or expired download id"})
|