Каков наилучший способ разрешить относительный путь (например, realpath) для несуществующих файлов?
Я пытаюсь внедрить корневой каталог в абстракции файловой системы. Проблема, с которой я сталкиваюсь, заключается в следующем:
API позволяет читать и записывать файлы не только на локальные, но и на удаленные хранилища. Так что под капотом происходят все виды нормализации. В настоящий момент это не поддерживает относительные пути, поэтому что-то вроде этого невозможно:
$filesystem->write('path/to/some/../relative/file.txt', 'file contents');
Я хочу иметь возможность безопасно разрешать путь, поэтому выход будет: path/to/relative/file.txt
.
Как указано в проблеме github, которая была создана для этой ошибки/улучшения (https://github.com/FrenkyNet/Flysystem/issues/36#issuecomment-30319406), ей нужно сделать больше, чем просто разделение сегментов и их удаление соответственно.
Кроме того, поскольку пакет обрабатывает удаленные файловые системы и несуществующие файлы, вопрос realpath не может быть и речи.
Итак, как это следует делать при работе с этими путями?
Ответы
Ответ 1
Я решил, как это сделать, это мое решение:
/**
* Normalize path
*
* @param string $path
* @param string $separator
* @return string normalized path
*/
public function normalizePath($path, $separator = '\\/')
{
// Remove any kind of funky unicode whitespace
$normalized = preg_replace('#\p{C}+|^\./#u', '', $path);
// Path remove self referring paths ("/./").
$normalized = preg_replace('#/\.(?=/)|^\./|\./$#', '', $normalized);
// Regex for resolving relative paths
$regex = '#\/*[^/\.]+/\.\.#Uu';
while (preg_match($regex, $normalized)) {
$normalized = preg_replace($regex, '', $normalized);
}
if (preg_match('#/\.{2}|\.{2}/#', $normalized)) {
throw new LogicException('Path is outside of the defined root, path: [' . $path . '], resolved: [' . $normalized . ']');
}
return trim($normalized, $separator);
}
Ответ 2
Чтобы процитировать Jame Zawinski:
Некоторые люди, столкнувшись с проблемой, думают: "Я знаю, я буду использовать регулярные выражения". Теперь у них есть две проблемы.
protected function getAbsoluteFilename($filename) {
$path = [];
foreach(explode('/', $filename) as $part) {
// ignore parts that have no value
if (empty($part) || $part === '.') continue;
if ($part !== '..') {
// cool, we found a new part
array_push($path, $part);
}
else if (count($path) > 0) {
// going back up? sure
array_pop($path);
} else {
// now, here we don't like
throw new \Exception('Climbing above the root is not permitted.');
}
}
// prepend my root directory
array_unshift($path, $this->getPath());
return join('/', $path);
}
Ответ 3
./Текущее местоположение
../на один уровень вверх
function normalize_path($str){
$N = 0;
$A =explode("/",preg_replace("/\/\.\//",'/',$str)); // remove current_location
$B=[];
for($i = sizeof($A)-1;$i>=0;--$i){
if(trim($A[$i]) ===".."){
$N++;
}else{
if($N>0){
$N--;
}
else{
$B[] = $A[$i];
}
}
}
return implode("/",array_reverse($B));
}
так:
"a/b/c/../../d" -> "a/d"
"a/./b" -> "a/b"
Ответ 4
/**
* Remove '.' and '..' path parts and make path absolute without
* resolving symlinks.
*
* Examples:
*
* resolvePath("test/./me/../now/", false);
* => test/now
*
* resolvePath("test///.///me///../now/", true);
* => /home/example/test/now
*
* resolvePath("test/./me/../now/", "/www/example.com");
* => /www/example.com/test/now
*
* resolvePath("/test/./me/../now/", "/www/example.com");
* => /test/now
*
* @access public
* @param string $path
* @param mixed $basePath resolve paths realtively to this path. Params:
* STRING: prefix with this path;
* TRUE: use current dir;
* FALSE: keep relative (default)
* @return string resolved path
*/
function resolvePath($path, $basePath=false) {
// Make absolute path
if (substr($path, 0, 1) !== DIRECTORY_SEPARATOR) {
if ($basePath === true) {
// Get PWD first to avoid getcwd() resolving symlinks if in symlinked folder
$path=(getenv('PWD') ?: getcwd()).DIRECTORY_SEPARATOR.$path;
} elseif (strlen($basePath)) {
$path=$basePath.DIRECTORY_SEPARATOR.$path;
}
}
// Resolve '.' and '..'
$components=array();
foreach(explode(DIRECTORY_SEPARATOR, rtrim($path, DIRECTORY_SEPARATOR)) as $name) {
if ($name === '..') {
array_pop($components);
} elseif ($name !== '.' && !(count($components) && $name === '')) {
// … && !(count($components) && $name === '') - we want to keep initial '/' for abs paths
$components[]=$name;
}
}
return implode(DIRECTORY_SEPARATOR, $components);
}