Files
2026-10-18 15:55:34 +02:00

47 lines
1.7 KiB
PHP

<?php
/** SFTP paths use POSIX separators, regardless of the OS running PHP. */
function normalizePath(string $path): string {
$parts = array_filter(explode('/', $path), fn($part) => $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);
}