Я пытаюсь обеспечить корневой каталог в абстракции файловой системы. Проблема, с которой я сталкиваюсь, заключается в следующем:
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 не может быть и речи.
Итак, как это следует делать при работе с этими путями?
Чтобы процитировать 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); }
Я решил, как это сделать, это мое решение:
/** * 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); }
/** * 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); }