Files
MajoorWaldi-ComfyUI-Majoor-…/mjr_am_backend/path_utils.py
T

80 lines
2.5 KiB
Python

"""
Shared path normalization and safety helpers.
"""
from __future__ import annotations
import os
from pathlib import Path, PureWindowsPath
# Cap on the number of path segments accepted in a subfolder argument.
# Override via ``MJR_MAX_SUBFOLDER_DEPTH`` if you legitimately have deeply
# nested asset libraries.
try:
_MAX_SUBFOLDER_DEPTH = int(os.environ.get("MJR_MAX_SUBFOLDER_DEPTH", "10"))
except Exception:
_MAX_SUBFOLDER_DEPTH = 10
if _MAX_SUBFOLDER_DEPTH < 1:
_MAX_SUBFOLDER_DEPTH = 1
def normalize_path(value: str) -> Path | None:
if not value:
return None
if "\x00" in value:
return None
try:
return Path(value).expanduser().resolve(strict=False)
except (OSError, ValueError):
return None
def _is_path_absolute_or_rooted(raw: str, rel: Path) -> bool:
win_path = PureWindowsPath(raw)
if win_path.drive or win_path.root in ("\\", "/") or win_path.is_absolute():
return True
return bool(getattr(rel, "drive", "")) or rel.is_absolute()
def safe_rel_path(value: str) -> Path | None:
if value is None:
return Path("")
raw = str(value).strip()
if raw == "":
return Path("")
if "\x00" in raw:
return None
# Normalize Windows-style backslashes so traversal checks work on Linux too
raw = raw.replace("\\", "/")
try:
rel = Path(raw)
except (OSError, ValueError):
return None
if _is_path_absolute_or_rooted(raw, rel):
return None
if any(part == ".." for part in rel.parts):
return None
# Defence-in-depth: reject pathologically deep client-supplied subfolders.
# Real custom roots stay well under 10 nested directories; deeper paths
# are typically traversal attempts or malformed input that would force
# the filesystem walker into long sequential stat() chains.
if len(rel.parts) > _MAX_SUBFOLDER_DEPTH:
return None
return rel
def is_within_root(candidate: Path, root: Path) -> bool:
try:
root_resolved = root.resolve(strict=True)
cand_resolved = candidate.resolve(strict=True)
except (OSError, RuntimeError, ValueError):
return False
try:
return cand_resolved == root_resolved or cand_resolved.is_relative_to(root_resolved)
except AttributeError:
try:
common = os.path.commonpath([str(cand_resolved), str(root_resolved)])
return os.path.normcase(common) == os.path.normcase(str(root_resolved))
except ValueError:
return False