如何检查一个目录是否是另一个目录的子目录

2024-06-28 19:40:50 发布

您现在位置:Python中文网/ 问答频道 /正文

我喜欢用Python编写一个模板系统,它允许包含文件。

例如

    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

更新:添加一个trailing/to目录来解决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

Tags: thepathprefixreturnifisos链接
3条回答

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

realpath(path):返回指定文件名的规范路径,消除路径中遇到的任何符号链接(如果操作系统支持它们)。

在目录和子目录名上使用它,然后检查后者以前者开头。

许多建议方法的问题

如果要使用字符串比较或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中引入了一个新的基于OS的方法,它允许您以类似的精确和无错误的方式执行路径父子检查,代码要少得多。

Python 3.5的新特性

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])

精确一行

在Python3.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

相关问题 更多 >