fix(B05): 54-22 H1 · H2 경로 넘기 막음 — 구조물 id 안전 이름 · 지면 필터 · 표현 허용 목록
- H1: 구조물 저장 structure_id 를 안전 이름 규칙(FOLDER_PATTERN 과 한 벌)으로 검사 · 어기면 422 - H1: 복사본 맞춤에서 한 번 더 — 규칙 밖 · base 밖 폴더는 지우기 · 쓰기 건너뜀(못만듦) - H2: filter_key · method 를 허용 목록(Literal)으로 · 파일 이름 만드는 곳(표고 sampler · 비용면 · 스켈레톤)에서 한 번 더 - 시험: test_54_22_security 26 통과 · 관 간격 시험 가짜 필터 이름을 허용 값으로 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EYJJRCfHfdCGNhmXTjQKV7
This commit is contained in:
@@ -26,6 +26,7 @@ from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from B05_Profile.B05_Profile_Structures_Repository import load_structures
|
||||
from B05_Profile.B05_Profile_Structures_Schema import STRUCTURE_ID_RULE
|
||||
from common_util.common_util_drainage_pipes import pipe_points_path_in, read_pipe_points_file
|
||||
from common_util.common_util_json import atomic_write_json
|
||||
from common_util.common_util_summary_item import GROUP_JOIN, snapshot_folder_name
|
||||
@@ -51,7 +52,7 @@ LOCAL_FILES = (VALUES_FILE, PICKS_FILE)
|
||||
#: 복사본 폴더 이름 — 구조물 id(영문 · 숫자 · `_` `-`) 또는 `pipe@측점` · 옵션 그룹(PLAN 46)은
|
||||
#: 뒤에 `__{그룹 id}`
|
||||
FOLDER_PATTERN = re.compile(
|
||||
r"^(?:pipe@\d+(?:\.\d+)?(?:__g[0-9a-z]{1,16})?|[0-9A-Za-z][0-9A-Za-z_-]*)$"
|
||||
rf"^(?:pipe@\d+(?:\.\d+)?(?:__g[0-9a-z]{{1,16}})?|{STRUCTURE_ID_RULE})$"
|
||||
)
|
||||
|
||||
|
||||
@@ -69,6 +70,14 @@ def folder_name(owner_id: str) -> str:
|
||||
return owner_id.replace("#", "n")
|
||||
|
||||
|
||||
def safe_folder(base: Path, name: str) -> Path | None:
|
||||
"""`base` 바로 아래 복사본 폴더 — 이름이 규칙 밖 · `base` 밖이면 None(지우기 · 쓰기 막음)."""
|
||||
target = base / name
|
||||
if not FOLDER_PATTERN.match(name) or target.resolve().parent != base.resolve():
|
||||
return None
|
||||
return target
|
||||
|
||||
|
||||
def now_text() -> str:
|
||||
return datetime.now().isoformat(timespec="seconds")
|
||||
|
||||
@@ -181,8 +190,8 @@ def migrate_items(project_root: str | Path) -> list[str]:
|
||||
continue
|
||||
users = [o for o, item in wanted.items() if snapshot_folder_name(item) == child.name]
|
||||
for owner in users:
|
||||
target = base / folder_name(owner)
|
||||
if (target / MANIFEST_NAME).is_file():
|
||||
target = safe_folder(base, folder_name(owner))
|
||||
if target is None or (target / MANIFEST_NAME).is_file():
|
||||
continue # 이미 새 복사본이 있음
|
||||
shutil.copytree(child, target, dirs_exist_ok=True)
|
||||
origin = manifest.get("원본") or {}
|
||||
@@ -212,7 +221,11 @@ def sync_snapshots(project_root: str | Path) -> dict[str, list[str]]:
|
||||
failed: list[str] = []
|
||||
removed: list[str] = []
|
||||
for folder, (owner, item) in sorted(wanted.items()):
|
||||
target = base / folder
|
||||
target = safe_folder(base, folder)
|
||||
if target is None:
|
||||
logger.warning("복사본 폴더 이름이 규칙 밖 — 건너뜀: %r", folder)
|
||||
failed.append(folder)
|
||||
continue
|
||||
manifest = read_json(target / MANIFEST_NAME)
|
||||
if isinstance(manifest, dict) and same_item(manifest, item):
|
||||
continue
|
||||
|
||||
Reference in New Issue
Block a user