为不存在的文件解析相对路径(如 realpath)的最佳方法是啥?
Posted
技术标签:
【中文标题】为不存在的文件解析相对路径(如 realpath)的最佳方法是啥?【英文标题】:What is the best way to resolve a relative path (like realpath) for non-existing files?为不存在的文件解析相对路径(如 realpath)的最佳方法是什么? 【发布时间】:2013-12-29 15:08:13 【问题描述】:我正在尝试在文件系统抽象中强制执行根目录。我遇到的问题如下:
API 允许您读取和写入文件,不仅可以到本地存储,还可以到远程存储。因此,引擎盖下正在进行各种标准化。目前它不支持相对路径,所以这样的事情是不可能的:
$filesystem->write('path/to/some/../relative/file.txt', 'file contents');
我希望能够安全地解析路径,因此输出将是:path/to/relative/file.txt
。
正如为此错误/增强 (https://github.com/FrenkyNet/Flysystem/issues/36#issuecomment-30319406) 创建的 github 问题中所述,它需要做的不仅仅是拆分段并相应地删除它们。
此外,由于该软件包处理远程文件系统和不存在的文件,因此 realpath 是不可能的。
那么,在处理这些路径时应该如何处理呢?
【问题讨论】:
realpath(dirname($path))
怎么样?
realpath 需要一个存在于本地文件系统上的路径,而写入的情况并非如此,并且在远程文件系统上完全不可用
我不明白如何确定不存在的相对路径的绝对路径。您至少需要包含点的子路径存在
不完全是,你也可以用 [empty-string] 替换所有有另一个租赁段的../
,但这有安全风险,正如我在 github 问题中提到的那样。
【参考方案1】:
引用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);
【讨论】:
几个cmets:(1) 使用empty()
是危险的,因为它会跳过名称为0
或0.0
的目录。 (2) 你应该使用DIRECTORY_SEPARATOR
而不是/
。 (3) 这在不是文件名的任意路径上也可以正常工作,因此应该将其命名为getAbsolutePath
。【参考方案2】:
我已经解决了如何做到这一点,这是我的解决方案:
/**
* 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('#\pC+|^\./#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);
【讨论】:
"路径删除自引用路径 ("/./")" 不适用于以..
结尾的路径,例如a/b/..
。此外,“用于解析相对路径的正则表达式”不适用于带有点前缀的目录,例如a/.b/../c
.
我在发布后改进了这个初始实现,它也处理这些情况,代码可以在这里找到:github.com/thephpleague/flysystem/blob/master/src/Util.php#L80
如果您编辑答案,我可以删除我的 -1。顺便说一句,为什么您使用正则表达式而不是在 dir 分隔符上拆分并在路径部分上循环,保留一堆路径部分,当您遇到 ..
时弹出最后一个路径部分?
能否根据***.com/a/4205278/2970321 替换realpath
来防止目录遍历攻击?【参考方案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);
【讨论】:
代码旁边的一些解释会很有帮助。 你是什么意思?例子?或者只是强调第一条注释行“删除'。”和 '..' 路径部分并在不解析符号链接的情况下使路径成为绝对路径。”? 看看 SO,你会看到很多这样的例子。这不仅仅是一个代码工厂,而是一个人们来学习的地方。系统将您的回答标记为长期用户并要求社区来帮助向您介绍这里的工作方式是有原因的。 看,我试图提供帮助。我不想浪费时间拐弯抹角,所以要具体回答我的问题。 “在 SO 上搜索答案”类型的答案不是答案,如果不知道要搜索什么,它甚至没有帮助。我在代码注释中解释了代码,此代码是对原始问题/问题的回答。所以我又问了。你还有什么“解释”?例子?更多解释?我是一个看代码的专业人士,一切都很清楚。因此,如果您是新手,请告诉我哪些部分不清楚,我会尝试更新并提供更多解释。 真的男人吗?那次旅行说我做的一切都很好。对不起,但我们在绕圈子。你没有解释的抱怨只是在浪费我的时间。通过引用的***.com/tour 并坚持第一条规则“这个网站就是为了得到答案。它不是一个讨论论坛。没有闲聊。”。至少感谢您尝试改进我的第一个贡献,但这没有帮助。祝你有美好的一天。以上是关于为不存在的文件解析相对路径(如 realpath)的最佳方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章