update
This commit is contained in:
Binary file not shown.
Binary file not shown.
@@ -1,9 +1,10 @@
|
||||
from __future__ import annotations
|
||||
import logging
|
||||
import unicodedata
|
||||
from pathlib import Path
|
||||
|
||||
from flask import Blueprint, render_template, request, redirect, url_for, flash
|
||||
from flask_login import login_required
|
||||
from werkzeug.utils import secure_filename
|
||||
from config import Config
|
||||
|
||||
xml_bp = Blueprint("xml", __name__)
|
||||
@@ -17,12 +18,26 @@ def allowed_file(filename: str) -> bool:
|
||||
return "." in filename and filename.rsplit(".", 1)[1].lower() in Config.ALLOWED_EXTENSIONS
|
||||
|
||||
|
||||
def sanitize_preserve_unicode(filename: str) -> str:
|
||||
"""
|
||||
디렉터리 탐색/제어 문자를 차단하면서, 한글/유니코드 파일명은 그대로 보존합니다.
|
||||
- 경로 요소 제거 (Path(...).name)
|
||||
- 유니코드 정규화(NFC)로 OS간 차이 최소화
|
||||
- 널문자/슬래시/역슬래시 차단
|
||||
"""
|
||||
name = Path(filename).name
|
||||
name = unicodedata.normalize("NFC", name)
|
||||
if not name or any(ch in name for ch in ["\x00", "/", "\\"]):
|
||||
raise ValueError("잘못된 파일명입니다.")
|
||||
return name
|
||||
|
||||
|
||||
@xml_bp.route("/xml_management")
|
||||
@login_required
|
||||
def xml_management():
|
||||
xml_dir = Path(Config.XML_FOLDER)
|
||||
try:
|
||||
files = [f.name for f in xml_dir.iterdir() if f.is_file()]
|
||||
files = sorted([f.name for f in xml_dir.iterdir() if f.is_file()])
|
||||
except FileNotFoundError:
|
||||
files = []
|
||||
flash("XML 폴더가 존재하지 않습니다.", "danger")
|
||||
@@ -37,58 +52,83 @@ def upload_xml():
|
||||
flash("업로드할 파일을 선택하세요.", "warning")
|
||||
return redirect(url_for("xml.xml_management"))
|
||||
|
||||
if allowed_file(file.filename):
|
||||
filename = secure_filename(file.filename)
|
||||
save_path = Path(Config.XML_FOLDER) / filename
|
||||
try:
|
||||
save_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
file.save(str(save_path))
|
||||
# 텍스트 파일이므로 0644 권장
|
||||
try:
|
||||
save_path.chmod(0o644)
|
||||
except Exception:
|
||||
pass # Windows 등에서 무시
|
||||
logging.info(f"XML 업로드됨: {filename}")
|
||||
flash("파일이 성공적으로 업로드되었습니다.", "success")
|
||||
except Exception as e:
|
||||
logging.error(f"파일 업로드 오류: {e}")
|
||||
flash("파일 저장 중 오류가 발생했습니다.", "danger")
|
||||
else:
|
||||
if not allowed_file(file.filename):
|
||||
flash("XML 확장자만 업로드할 수 있습니다.", "warning")
|
||||
return redirect(url_for("xml.xml_management"))
|
||||
|
||||
try:
|
||||
filename = sanitize_preserve_unicode(file.filename) # 한글/유니코드 보존
|
||||
except ValueError:
|
||||
flash("파일명이 올바르지 않습니다.", "danger")
|
||||
return redirect(url_for("xml.xml_management"))
|
||||
|
||||
save_path = Path(Config.XML_FOLDER) / filename
|
||||
try:
|
||||
save_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
file.save(str(save_path))
|
||||
# 텍스트 파일 권장 권한 (Windows에서는 무시될 수 있음)
|
||||
try:
|
||||
save_path.chmod(0o644)
|
||||
except Exception:
|
||||
pass
|
||||
logging.info(f"XML 업로드됨: {filename}")
|
||||
flash("파일이 성공적으로 업로드되었습니다.", "success")
|
||||
except Exception as e:
|
||||
logging.error(f"파일 업로드 오류: {e}")
|
||||
flash("파일 저장 중 오류가 발생했습니다.", "danger")
|
||||
|
||||
return redirect(url_for("xml.xml_management"))
|
||||
|
||||
|
||||
@xml_bp.route("/delete_xml/<filename>", methods=["POST"])
|
||||
@xml_bp.route("/delete_xml/<path:filename>", methods=["POST"])
|
||||
@login_required
|
||||
def delete_xml(filename: str):
|
||||
path = Path(Config.XML_FOLDER) / secure_filename(filename)
|
||||
if path.exists():
|
||||
try:
|
||||
path.unlink()
|
||||
flash(f"{filename} 파일이 삭제되었습니다.", "success")
|
||||
logging.info(f"XML 삭제됨: {filename}")
|
||||
except Exception as e:
|
||||
logging.error(f"XML 삭제 오류: {e}")
|
||||
flash("파일 삭제 중 오류 발생", "danger")
|
||||
else:
|
||||
try:
|
||||
safe_name = sanitize_preserve_unicode(filename)
|
||||
except ValueError:
|
||||
flash("잘못된 파일명입니다.", "danger")
|
||||
return redirect(url_for("xml.xml_management"))
|
||||
|
||||
path = Path(Config.XML_FOLDER) / safe_name
|
||||
if not path.exists():
|
||||
flash("해당 파일이 존재하지 않습니다.", "warning")
|
||||
return redirect(url_for("xml.xml_management"))
|
||||
|
||||
try:
|
||||
path.unlink()
|
||||
flash(f"{safe_name} 파일이 삭제되었습니다.", "success")
|
||||
logging.info(f"XML 삭제됨: {safe_name}")
|
||||
except Exception as e:
|
||||
logging.error(f"XML 삭제 오류: {e}")
|
||||
flash("파일 삭제 중 오류 발생", "danger")
|
||||
|
||||
return redirect(url_for("xml.xml_management"))
|
||||
|
||||
|
||||
@xml_bp.route("/edit_xml/<filename>", methods=["GET", "POST"])
|
||||
@xml_bp.route("/edit_xml/<path:filename>", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def edit_xml(filename: str):
|
||||
path = Path(Config.XML_FOLDER) / secure_filename(filename)
|
||||
try:
|
||||
safe_name = sanitize_preserve_unicode(filename)
|
||||
except ValueError:
|
||||
flash("잘못된 파일명입니다.", "danger")
|
||||
return redirect(url_for("xml.xml_management"))
|
||||
|
||||
path = Path(Config.XML_FOLDER) / safe_name
|
||||
if not path.exists():
|
||||
flash("파일을 찾을 수 없습니다.", "danger")
|
||||
return redirect(url_for("xml.xml_management"))
|
||||
|
||||
if request.method == "POST":
|
||||
new_content = request.form.get("content", "")
|
||||
raw = request.form.get("content", "")
|
||||
|
||||
# 1) 개행 통일: CRLF/CR → LF
|
||||
normalized = raw.replace("\r\n", "\n").replace("\r", "\n")
|
||||
|
||||
try:
|
||||
path.write_text(new_content, encoding="utf-8")
|
||||
logging.info(f"XML 수정됨: {filename}")
|
||||
# 2) 항상 LF로 저장 (Windows에서도 강제)
|
||||
path.write_text(normalized, encoding="utf-8", newline="\n")
|
||||
logging.info(f"XML 수정됨: {safe_name}")
|
||||
flash("파일이 성공적으로 수정되었습니다.", "success")
|
||||
return redirect(url_for("xml.xml_management"))
|
||||
except Exception as e:
|
||||
@@ -96,10 +136,11 @@ def edit_xml(filename: str):
|
||||
flash("파일 저장 중 오류가 발생했습니다.", "danger")
|
||||
|
||||
try:
|
||||
content = path.read_text(encoding="utf-8")
|
||||
# 보기/편집 일관성을 위해 읽을 때도 LF로 맞춰서 textarea에 넣음
|
||||
content = path.read_text(encoding="utf-8").replace("\r\n", "\n").replace("\r", "\n")
|
||||
except Exception as e:
|
||||
logging.error(f"XML 열기 실패: {e}")
|
||||
flash("파일 열기 중 오류가 발생했습니다.", "danger")
|
||||
content = ""
|
||||
|
||||
return render_template("edit_xml.html", filename=filename, content=content)
|
||||
return render_template("edit_xml.html", filename=safe_name, content=content)
|
||||
Reference in New Issue
Block a user