Files
iDRAC_Info/backend/routes/file_view.py
2025-10-05 17:37:51 +09:00

95 lines
3.0 KiB
Python

from __future__ import annotations
import logging
from pathlib import Path
from flask import Blueprint, request, jsonify, Response
from flask_login import login_required
from config import Config
import chardet
file_view_bp = Blueprint("file_view", __name__)
def register_file_view(app):
"""블루프린트 등록"""
app.register_blueprint(file_view_bp)
def _safe_within(base: Path, target: Path) -> bool:
"""
target 이 base 디렉터리 내부인지 검사 (경로 탈출 방지)
"""
try:
target.resolve().relative_to(base.resolve())
return True
except Exception:
return False
def _decode_bytes(raw: bytes) -> str:
"""
파일 바이트 → 문자열 디코딩 (감지 → utf-8 → cp949 순서로 시도)
"""
enc = (chardet.detect(raw).get("encoding") or "utf-8").strip().lower()
for cand in (enc, "utf-8", "cp949"):
try:
return raw.decode(cand)
except Exception:
continue
# 최후의 수단: 손실 허용 디코딩
return raw.decode("utf-8", errors="replace")
@file_view_bp.route("/view_file", methods=["GET"])
@login_required
def view_file():
"""
파일 내용을 읽어 반환.
- /view_file?folder=idrac_info&filename=abc.txt
- /view_file?folder=backup&date=<백업폴더명>&filename=abc.txt
- ?raw=1 을 붙이면 text/plain 으로 원문을 반환 (모달 표시용)
"""
folder = request.args.get("folder", "").strip()
date = request.args.get("date", "").strip()
filename = request.args.get("filename", "").strip()
want_raw = request.args.get("raw")
if not filename:
return jsonify({"error": "파일 이름이 없습니다."}), 400
# 파일명/폴더명은 유니코드 보존. 상위 경로만 제거하여 보안 유지
safe_name = Path(filename).name
if folder == "backup":
base = Path(Config.BACKUP_FOLDER)
safe_date = Path(date).name if date else ""
target = (base / safe_date / safe_name).resolve()
else:
base = Path(Config.IDRAC_INFO_FOLDER)
target = (base / safe_name).resolve()
logging.info(
"file_view: folder=%s date=%s filename=%s | base=%s | target=%s",
folder, date, filename, str(base), str(target)
)
if not _safe_within(base, target) or not target.is_file():
logging.warning("file_view: 파일 없음: %s", str(target))
return jsonify({"error": "파일을 찾을 수 없습니다."}), 404
try:
raw = target.read_bytes()
content = _decode_bytes(raw)
if want_raw:
# 텍스트 원문 반환 (모달에서 fetch().text()로 사용)
return Response(content, mimetype="text/plain; charset=utf-8")
# JSON으로 감싸서 반환 (기존 사용처 호환)
return jsonify({"content": content})
except Exception as e:
logging.error("file_view: 파일 읽기 실패: %s%s", str(target), e)
return jsonify({"error": "파일 열기 중 오류 발생"}), 500