Files
Ewald 8efd481008 fix: resolve node patching bugs, label generation issues, and backend stability
- Fix save node patching: early exit on matching targets, subgraph traversal, tighten isSaveLikeNode filters
- Fix project target prefix handling: dual prefix and filename_prefix support across preview and create_custom_out
- Optimize UI: 300ms debounce on target name input, patch nodes preview caching, clean asset name extraction
- Backend stability: audit logger fd cleanup, double close fix, registry locks, rate limiting cache eviction
- Model fixer: properly clear node errors and refresh combo lists in ComfyUI
2026-09-17 10:43:07 +02:00

78 lines
2.6 KiB
Python

"""
Centralized input validation helpers.
"""
from __future__ import annotations
import re
from dataclasses import dataclass
from typing import Iterable
class ValidationError(ValueError):
pass
_RESERVED_WINDOWS_NAMES = {
"CON",
"PRN",
"AUX",
"NUL",
*(f"COM{i}" for i in range(1, 10)),
*(f"LPT{i}" for i in range(1, 10)),
}
@dataclass(frozen=True)
class InputValidator:
SAFE_PROJECT_ID = re.compile(r"^[a-zA-Z0-9_-]+$")
SAFE_FILENAME = re.compile(r"^[a-zA-Z0-9._ -]+$")
@staticmethod
def validate_project_id(value: str, *, max_len: int = 255) -> str:
v = str(value or "").strip()
if not v:
raise ValidationError("project_id is required")
if len(v) > max_len:
raise ValidationError(f"project_id is too long (max {max_len})")
if not InputValidator.SAFE_PROJECT_ID.match(v):
raise ValidationError("project_id contains invalid characters")
if v.upper() in _RESERVED_WINDOWS_NAMES:
raise ValidationError(f"'{v}' is a reserved name")
return v
@staticmethod
def validate_basename(value: str, *, max_len: int = 255, allowed_exts: Iterable[str] | None = None) -> str:
v = str(value or "").strip()
if not v:
raise ValidationError("filename is required")
if len(v) > max_len:
raise ValidationError(f"filename is too long (max {max_len})")
if "/" in v or "\\" in v or ":" in v or ".." in v:
raise ValidationError("filename must be a basename")
if not InputValidator.SAFE_FILENAME.match(v):
raise ValidationError("filename contains invalid characters")
if allowed_exts is not None:
lower = v.lower()
if not any(lower.endswith(ext.lower()) for ext in allowed_exts):
raise ValidationError("unsupported file extension")
if v.upper() in _RESERVED_WINDOWS_NAMES:
raise ValidationError(f"'{v}' is a reserved name")
return v
@staticmethod
def validate_relpath(value: str, *, max_len: int = 1024) -> str:
v = str(value or "").replace("\\", "/").strip()
if not v:
raise ValidationError("path is required")
if len(v) > max_len:
raise ValidationError(f"path is too long (max {max_len})")
if v.startswith("/"):
raise ValidationError("absolute paths are not allowed")
if ":" in v:
raise ValidationError("drive paths are not allowed")
if ".." in v:
raise ValidationError("path traversal detected")
return v