SandboxedRelativePath resolves userPath relative to root, ensuring the result stays within root. Absolute paths (including Windows drive-letter paths) are treated as relative to root. Returns an error if the resolved path would escape root via "..". Both root and the returned path use forward slash
(userPath, root string)
| 73 | // are normalized to forward slashes so that Windows-style paths are handled |
| 74 | // correctly even though the engine always runs on Linux. |
| 75 | func SandboxedRelativePath(userPath, root string) (string, error) { |
| 76 | // Normalize backslashes (Windows clients) to forward slashes. |
| 77 | clean := strings.ReplaceAll(userPath, "\\", "/") |
| 78 | clean = normalizePath(clean) |
| 79 | |
| 80 | // Strip drive letter or leading "/" so absolute paths are treated as |
| 81 | // relative to root. |
| 82 | if drive := GetDrive(clean); drive != "" { |
| 83 | clean = strings.TrimPrefix(clean, drive) |
| 84 | clean = strings.TrimPrefix(clean, "/") |
| 85 | } else if len(clean) > 0 && clean[0] == '/' { |
| 86 | clean = clean[1:] |
| 87 | } |
| 88 | |
| 89 | // Re-clean after stripping to collapse any leading separators or dot segments. |
| 90 | if clean == "" { |
| 91 | clean = "." |
| 92 | } |
| 93 | clean = normalizePath(clean) |
| 94 | |
| 95 | cleanRoot := normalizePath(root) |
| 96 | resolved := cleanRoot + "/" + clean |
| 97 | resolved = normalizePath(resolved) |
| 98 | |
| 99 | // Ensure the resolved path stays inside root. |
| 100 | // Special-case "/" so every absolute child path remains inside it. |
| 101 | if cleanRoot != "/" && resolved != cleanRoot && !strings.HasPrefix(resolved, cleanRoot+"/") { |
| 102 | return "", fmt.Errorf("path %q resolves outside root %q", userPath, root) |
| 103 | } |
| 104 | return resolved, nil |
| 105 | } |
| 106 | |
| 107 | // ExpandHomeDir expands a given path to its absolute form, handling home directory |
| 108 | func ExpandHomeDir(homeDir string, path string) (string, error) { |