95 lines
3.0 KiB
Python
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 |