$part !== '' && $part !== '.'); $stack = []; foreach ($parts as $part) { if ($part === '..') { array_pop($stack); } else { $stack[] = $part; } } return '/' . implode('/', $stack); } /** Like dirname(), but only '/' is a separator and the root stays '/'. */ function remoteDirname(string $path): string { $path = rtrim($path, '/'); if ($path === '') return '/'; $separator = strrpos($path, '/'); if ($separator === false) return '.'; $parent = rtrim(substr($path, 0, $separator), '/'); return $parent === '' ? '/' : $parent; } /** Compare directory boundaries, so /data-other is not considered part of /data. */ function isRemotePathWithin(string $path, string $basePath): bool { $path = normalizePath($path); $basePath = normalizePath($basePath); return $path === $basePath || strpos($path, rtrim($basePath, '/') . '/') === 0; } /** Validate a single POSIX filename before joining it to a directory. */ function isRemoteNameValid(string $name): bool { return $name !== '' && $name !== '.' && $name !== '..' && strpos($name, '/') === false && strpos($name, "\0") === false; } /** Resolve a request path inside the user's configured workspace. */ function requireRemotePath(string $path, string $basePath): string { if (strpos($path, "\0") !== false || !isRemotePathWithin($path, $basePath)) { http_response_code(403); exit('Path is outside your workspace.'); } return normalizePath($path); }