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