Каков наилучший способ разрешить относительный путь (например, 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);
}