Spaces:
Running
Running
File size: 1,867 Bytes
37f9abc c706455 37f9abc c706455 37f9abc c706455 37f9abc c706455 37f9abc c706455 37f9abc | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | from __future__ import annotations
from pathlib import Path, PurePosixPath
def normalise_relative_path(path: str) -> PurePosixPath:
"""Normalise and validate a project-relative path."""
cleaned = path.strip().replace("\\", "/")
if not cleaned:
raise ValueError("Path cannot be empty.")
candidate = PurePosixPath(cleaned)
if candidate.is_absolute():
raise PermissionError(f"Absolute paths are not allowed: {path}")
if ".." in candidate.parts:
raise PermissionError(f"Parent-directory traversal is not allowed: {path}")
return candidate
def reject_repeated_project_prefix(
project_directory: Path,
path: str,
) -> None:
"""Reject paths such as sandbox/project-name/src/file.py."""
candidate = normalise_relative_path(path)
parts = candidate.parts
project_name = project_directory.name
if len(parts) >= 2 and parts[0].lower() == "sandbox" and parts[1] == project_name:
remaining_parts = parts[2:]
suggested_path = (
PurePosixPath(*remaining_parts).as_posix() if remaining_parts else "."
)
raise PermissionError(
"Do not include the sandbox or project-directory prefix. "
f"Use `{suggested_path}` instead of `{path}`."
)
def resolve_project_path(
project_directory: Path,
path: str,
) -> Path:
"""Resolve a project-relative path and ensure it stays inside the project."""
cleaned_path = path.strip()
reject_repeated_project_prefix(
project_directory,
cleaned_path,
)
relative_path = normalise_relative_path(cleaned_path)
root = project_directory.resolve()
resolved = (root / relative_path).resolve()
if not resolved.is_relative_to(root):
raise PermissionError(f"Path escapes the project directory: {path!r}")
return resolved
|