paths.py 1.5 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647
  1. """DMS存储目录创建和安全路径解析。"""
  2. from __future__ import annotations
  3. from pathlib import Path, PurePosixPath
  4. STORAGE_SUBDIRECTORIES = (
  5. "original",
  6. "preview",
  7. "extracted",
  8. "temporary",
  9. "quarantine",
  10. "recycle",
  11. )
  12. class UnsafeStoragePathError(ValueError):
  13. """相对路径试图逃逸DMS存储根目录。"""
  14. def ensure_storage_directories(storage_root: str | Path) -> Path:
  15. root = Path(storage_root).expanduser().resolve()
  16. root.mkdir(parents=True, exist_ok=True)
  17. for directory in STORAGE_SUBDIRECTORIES:
  18. (root / directory).mkdir(parents=True, exist_ok=True)
  19. return root
  20. def resolve_storage_path(
  21. relative_path: str,
  22. storage_root: str | Path,
  23. ) -> Path:
  24. """安全解析数据库相对路径并保证结果仍位于存储根目录。"""
  25. if not relative_path or "\x00" in relative_path:
  26. raise UnsafeStoragePathError("存储相对路径不能为空或包含空字符")
  27. normalized = PurePosixPath(relative_path.replace("\\", "/"))
  28. has_drive_prefix = bool(normalized.parts and ":" in normalized.parts[0])
  29. if normalized.is_absolute() or has_drive_prefix or ".." in normalized.parts:
  30. raise UnsafeStoragePathError("存储路径必须是根目录内的安全相对路径")
  31. root = Path(storage_root).expanduser().resolve()
  32. candidate = root.joinpath(*normalized.parts).resolve()
  33. if not candidate.is_relative_to(root):
  34. raise UnsafeStoragePathError("存储路径超出DMS存储根目录")
  35. return candidate