Initial commit
This commit is contained in:
95
backend/routes/file_view.py
Normal file
95
backend/routes/file_view.py
Normal file
@@ -0,0 +1,95 @@
|
||||
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
|
||||
Reference in New Issue
Block a user