From 626a2ed50a9efed965bf4038bffd3849345de6c1 Mon Sep 17 00:00:00 2001 From: OoO Date: Fri, 15 May 2026 14:17:33 +0800 Subject: [PATCH] fix: guard ppt audit file integrity and disable preview on corrupted files --- routes/admin_observability_routes.py | 32 ++++++++++++++++++++++++++ templates/admin/ppt_audit_history.html | 27 ++++++++++++++++++++-- 2 files changed, 57 insertions(+), 2 deletions(-) diff --git a/routes/admin_observability_routes.py b/routes/admin_observability_routes.py index 69cbabb..2f49e56 100644 --- a/routes/admin_observability_routes.py +++ b/routes/admin_observability_routes.py @@ -1847,6 +1847,7 @@ def ppt_audit_file(filename: str): action = (request.args.get('action', 'view') or 'view').strip().lower() try: import os + import zipfile from utils.security import safe_join reports_dir = os.environ.get('REPORTS_DIR', '/app/data/reports') @@ -1858,6 +1859,17 @@ def ppt_audit_file(filename: str): if safe_path.suffix.lower() != '.pptx': return '不支援的檔案格式', 400 + if action == 'view': + try: + with zipfile.ZipFile(safe_path, 'r') as zf: + bad = zf.testzip() + if bad is not None: + return f'PPT 檔案損毀,無法預覽(損毀區段:{bad})', 409 + except zipfile.BadZipFile: + return 'PPT 檔案損毀,無法預覽(非有效 zip)', 409 + except Exception as e: + return f'預覽檢查失敗:{type(e).__name__}', 409 + return send_file( str(safe_path), mimetype='application/vnd.openxmlformats-officedocument.presentationml.presentation', @@ -2171,6 +2183,7 @@ def budget_update(budget_id: int): def ppt_audit_history(): """掃 reports/ 目錄列指定月份 daily 報表 + 從 ppt_audit_results 讀審核歷史(Phase 38)""" import os + import zipfile reports_dir = os.environ.get('REPORTS_DIR', '/app/data/reports') files = [] audit_records = [] @@ -2231,6 +2244,16 @@ def ppt_audit_history(): next_month_label = f"{next_year:04d}-{next_month:02d}" show_next_month = (next_year < now.year) or (next_year == now.year and next_month <= now.month) + def _inspect_ppt_file(file_path: str): + try: + with zipfile.ZipFile(file_path, 'r') as zf: + bad = zf.testzip() + return (bad is None, None if bad is None else f'壓縮檔異常:{bad}') + except zipfile.BadZipFile: + return False, 'PPTX 檔案損毀(非有效 zip)' + except Exception as e: + return False, f'檢查失敗:{str(e)[:60]}' + try: if not os.path.isdir(reports_dir): error = f'{reports_dir} 目錄不存在' @@ -2248,6 +2271,7 @@ def ppt_audit_history(): try: mtime = os.path.getmtime(full) if month_start_ts <= mtime < month_end_ts: + is_valid, check_msg = _inspect_ppt_file(full) files_by_name[f] = { 'source': 'filesystem', 'name': f, @@ -2256,6 +2280,8 @@ def ppt_audit_history(): 'mtime_ts': mtime, 'file_exists': True, 'file_path': full, + 'is_valid_ppt': is_valid, + 'file_error': check_msg, } except OSError: continue @@ -2294,6 +2320,10 @@ def ppt_audit_history(): continue candidate_path = os.path.join(reports_dir, name) exists = os.path.isfile(candidate_path) + is_valid = False + check_msg = '檔案未落盤' + if exists: + is_valid, check_msg = _inspect_ppt_file(candidate_path) files_by_name[name] = { 'source': 'database', 'name': name, @@ -2303,6 +2333,8 @@ def ppt_audit_history(): 'file_exists': exists, 'file_path': candidate_path if exists else file_path, 'report_type': rpt_type, + 'is_valid_ppt': is_valid, + 'file_error': None if exists else check_msg, } finally: session.close() diff --git a/templates/admin/ppt_audit_history.html b/templates/admin/ppt_audit_history.html index a2140bf..8779246 100644 --- a/templates/admin/ppt_audit_history.html +++ b/templates/admin/ppt_audit_history.html @@ -286,19 +286,42 @@ {{ f.mtime }} {% if f.source == 'database' %} - {% if f.file_exists %}資料庫快取 + 檔案可存取{% else %}資料庫快取(檔案未落盤){% endif %} + {% if f.file_exists %} + {% if f.is_valid_ppt %} + 資料庫快取 + 檔案可預覽 + {% else %} + 資料庫快取 + 檔案損毀,建議重跑 + {% endif %} + {% else %} + 資料庫快取(檔案未落盤) + {% endif %} {% elif f.source == 'both' %} + {% if f.is_valid_ppt %} 檔案 + 資料庫 + {% else %} + 檔案 + 資料庫,檔案損毀 + {% endif %} {% else %} - 22:00 掃描落盤 + {% if f.is_valid_ppt %} + 22:00 掃描落盤,可預覽 + {% else %} + 22:00 掃描落盤,檔案損毀 + {% endif %} + {% endif %} + {% if f.file_error %} +
{{ f.file_error }}
{% endif %}
{% if f.file_exists %} + {% if f.is_valid_ppt %} 開啟 + {% else %} + 檔案不可預覽 + {% endif %} 下載