我喜欢在Python中编写一个模板系统,它允许包含文件。
e.g。
This is a template You can safely include files with safe_include`othertemplate.rst`
如您所知,包含文件可能很危险。例如,如果我在允许用户创建自己的模板的Web应用程序中使用模板系统,他们可能会执行类似
的操作。I want your passwords: safe_include`/etc/password`
因此,我必须限制将文件包含在文件中,例如在某个子目录中(例如/home/user/templates
)
现在的问题是:我如何检查/home/user/templates/includes/inc1.rst
是否在/home/user/templates
的子目录中?
以下代码是否有效且安全?
import os.path
def in_directory(file, directory, allow_symlink = False):
#make both absolute
directory = os.path.abspath(directory)
file = os.path.abspath(file)
#check whether file is a symbolic link, if yes, return false if they are not allowed
if not allow_symlink and os.path.islink(file):
return False
#return true, if the common prefix of both is equal to directory
#e.g. /a/b/c/d.rst and directory is /a/b, the common prefix is /a/b
return os.path.commonprefix([file, directory]) == directory
只要allow_symlink
为假,我认为它应该是安全的。如果用户能够创建这样的链接,那么允许符号链接会使其不安全。
更新 - 解决方案
如果中间目录是符号链接,则上述代码不起作用。
为防止出现这种情况,您必须使用realpath
代替abspath
。
UPDATE:添加一个尾随/目录以解决commonprefix()问题,Reorx指出。
由于符号链接扩展到其真实目的地
,这也使allow_symlink
变得不必要
import os.path
def in_directory(file, directory):
#make both absolute
directory = os.path.join(os.path.realpath(directory), '')
file = os.path.realpath(file)
#return true, if the common prefix of both is equal to directory
#e.g. /a/b/c/d.rst and directory is /a/b, the common prefix is /a/b
return os.path.commonprefix([file, directory]) == directory
答案 0 :(得分:28)
Python 3' pathlib
模块使用Path.parents属性使其变得简单明了。例如:
from pathlib import Path
root = Path('/path/to/root')
child = root / 'some' / 'child' / 'dir'
other = Path('/some/other/path')
然后:
>>> root in child.parents
True
>>> other in child.parents
False
答案 1 :(得分:13)
def is_subdir(path, directory):
path = os.path.realpath(path)
directory = os.path.realpath(directory)
relative = os.path.relpath(path, directory)
return not relative.startswith(os.pardir + os.sep)
答案 2 :(得分:11)
如果您要使用字符串比较或os.path.commonprefix
方法测试目录父级,则这些方法容易出现类似命名路径或相对路径的错误。例如:
/path/to/files/myfile
将使用许多方法显示为/path/to/file
的子路径。/path/to/files/../../myfiles
显示为/path/myfiles/myfile
的父级。事实上,它是。 Rob Dennis的previous answer提供了一种比较路径父母而不会遇到这些问题的好方法。 Python 3.4添加了pathlib
模块,它可以以更复杂的方式执行这些路径操作,可选择不引用底层操作系统。 jme在another previous answer中描述了如何使用pathlib
来准确确定一条路径是否是另一条路径的子路径。如果您不想使用pathlib
(不确定原因,它非常棒),那么Python 3.5在os.path
中引入了一个基于操作系统的新方法,允许您执行路径父子使用更少的代码以类似的准确和无差错的方式进行检查。
Python 3.5引入了函数os.path.commonpath
。这是一种特定于运行代码的操作系统的方法。您可以通过以下方式使用commonpath
来准确确定路径父级:
def path_is_parent(parent_path, child_path):
# Smooth out relative path names, note: if you are concerned about symbolic links, you should use os.path.realpath too
parent_path = os.path.abspath(parent_path)
child_path = os.path.abspath(child_path)
# Compare the common path of the parent and child path with the common path of just the parent path. Using the commonpath method on just the parent path will regularise the path name in the same way as the comparison that deals with both paths, removing any trailing path separator
return os.path.commonpath([parent_path]) == os.path.commonpath([parent_path, child_path])
您可以将整个批次合并到Python 3.5中的单行if语句中。这很丑陋,它包括对os.path.abspath
的不必要的重复调用,它绝对不符合PEP 8 79字符行长指南,但是如果你喜欢这样的话,那就去了:
if os.path.commonpath([os.path.abspath(parent_path_to_test)]) == os.path.commonpath([os.path.abspath(parent_path_to_test), os.path.abspath(child_path_to_test)]):
# Yes, the child path is under the parent path
答案 3 :(得分:10)
os.path.realpath(path):返回指定文件名的规范路径,消除路径中遇到的任何符号链接(如果操作系统支持它们)。
在目录和子目录名称上使用它,然后检查后者以前者开始。
答案 4 :(得分:6)
所以,我需要这个,并且由于对commonprefx的批评,我采取了不同的方式:
def os_path_split_asunder(path, debug=False):
"""
http://stackoverflow.com/a/4580931/171094
"""
parts = []
while True:
newpath, tail = os.path.split(path)
if debug: print repr(path), (newpath, tail)
if newpath == path:
assert not tail
if path: parts.append(path)
break
parts.append(tail)
path = newpath
parts.reverse()
return parts
def is_subdirectory(potential_subdirectory, expected_parent_directory):
"""
Is the first argument a sub-directory of the second argument?
:param potential_subdirectory:
:param expected_parent_directory:
:return: True if the potential_subdirectory is a child of the expected parent directory
>>> is_subdirectory('/var/test2', '/var/test')
False
>>> is_subdirectory('/var/test', '/var/test2')
False
>>> is_subdirectory('var/test2', 'var/test')
False
>>> is_subdirectory('var/test', 'var/test2')
False
>>> is_subdirectory('/var/test/sub', '/var/test')
True
>>> is_subdirectory('/var/test', '/var/test/sub')
False
>>> is_subdirectory('var/test/sub', 'var/test')
True
>>> is_subdirectory('var/test', 'var/test')
True
>>> is_subdirectory('var/test', 'var/test/fake_sub/..')
True
>>> is_subdirectory('var/test/sub/sub2/sub3/../..', 'var/test')
True
>>> is_subdirectory('var/test/sub', 'var/test/fake_sub/..')
True
>>> is_subdirectory('var/test', 'var/test/sub')
False
"""
def _get_normalized_parts(path):
return os_path_split_asunder(os.path.realpath(os.path.abspath(os.path.normpath(path))))
# make absolute and handle symbolic links, split into components
sub_parts = _get_normalized_parts(potential_subdirectory)
parent_parts = _get_normalized_parts(expected_parent_directory)
if len(parent_parts) > len(sub_parts):
# a parent directory never has more path segments than its child
return False
# we expect the zip to end with the short path, which we know to be the parent
return all(part1==part2 for part1, part2 in zip(sub_parts, parent_parts))
答案 5 :(得分:4)
def is_in_directory(filepath, directory):
return os.path.realpath(filepath).startswith(
os.path.realpath(directory) + os.sep)
答案 6 :(得分:3)
pathlib
在 PurePath
上有一个名为 is_relative_to
的新方法,它直接执行此功能。您可以阅读the python documentation on how is_relative_to
works,或使用以下示例:
from pathlib import Path
child_path = Path("/path/to/file")
if child_path.is_relative_to("/path"):
print("/path/to/file is a child of /path") # This prints
if child_path.is_relative_to("/anotherpath"):
print("/path/to/file is a child of /anotherpath") # This does not print
答案 7 :(得分:2)
我喜欢在另一个答案中提及的“other_path.parents中的路径”,因为我是pathlib的忠实粉丝,但我觉得这个方法有点重(它为每个父节点创建一个Path实例到路径的根目录)。此外,path == other_path将使用该方法失败,而os.commonpath将在该情况下成功。
以下是一种不同的方法,与各种答案中确定的其他方法相比,它们各有利弊:
try:
other_path.relative_to(path)
except ValueError:
...no common path...
else:
...common path...
有点冗长,但可以很容易地在应用程序的常用实用程序模块中添加为函数,甚至可以在启动时将该方法添加到Path。
答案 8 :(得分:0)
我使用下面的函数来解决类似的问题:
def is_subdir(p1, p2):
"""returns true if p1 is p2 or its subdirectory"""
p1, p2 = os.path.realpath(p1), os.path.realpath(p2)
return p1 == p2 or p1.startswith(p2+os.sep)
遇到符号链接问题后,我修改了该函数。现在,它会检查两个路径是否均为目录。
def is_subdir(p1, p2):
"""check if p1 is p2 or its subdirectory
:param str p1: subdirectory candidate
:param str p2: parent directory
:returns True if p1,p2 are directories and p1 is p2 or its subdirectory"""
if os.path.isdir(p1) and os.path.isdir(p2):
p1, p2 = os.path.realpath(p1), os.path.realpath(p2)
return p1 == p2 or p1.startswith(p2+os.sep)
else:
return False
答案 9 :(得分:0)
在您的启发下,此方法已添加到我的实用程序中:
def is_in_basefolder(path_to_check: PosixPath, basefolder: PosixPath):
"""
check if a given path is in base folder
parameters:
path_to_check: a path to match with base folder
basefolder: the base folder
"""
path = path_to_check.resolve()
base = basefolder.resolve()
if path == base:
return True
if base.stem in path.parts:
return True
else:
return False
答案 10 :(得分:-1)
我会测试commonprefix对文件名的结果,以获得更好的答案,如下所示:
def is_in_folder(filename, folder='/tmp/'):
# normalize both parameters
fn = os.path.normpath(filename)
fd = os.path.normpath(folder)
# get common prefix
commonprefix = os.path.commonprefix([fn, fd])
if commonprefix == fd:
# in case they have common prefix, check more:
sufix_part = fn.replace(fd, '')
sufix_part = sufix_part.lstrip('/')
new_file_name = os.path.join(fd, sufix_part)
if new_file_name == fn:
return True
pass
# for all other, it's False
return False
答案 11 :(得分:-1)
基于此处的另一个答案,带有更正,并且用户友好名称:
def isA_subdirOfB_orAisB(A, B):
"""It is assumed that A is a directory."""
relative = os.path.relpath(os.path.realpath(A),
os.path.realpath(B))
return not (relative == os.pardir
or relative.startswith(os.pardir + os.sep))