Подтвердить что ты не робот

Как проверить, является ли каталог вспомогательной директорией другого каталога

Мне нравится писать систему шаблонов в Python, которая позволяет включать файлы.

например.

    This is a template
    You can safely include files with safe_include`othertemplate.rst`

Как вы знаете, файлы могут быть опасными. Например, если я использую систему шаблонов в веб-приложении, которое позволяет пользователям создавать свои собственные шаблоны, они могут сделать что-то вроде

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 является False, он должен быть безопасным, я думаю. Конечно, разрешение символических ссылок сделало бы его небезопасным, если пользователь сможет создавать такие ссылки.

ОБНОВЛЕНИЕ - Решение Приведенный выше код не работает, если промежуточные каталоги являются символическими ссылками. Чтобы предотвратить это, вы должны использовать realpath вместо abspath.

UPDATE: добавление каталога 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
4b9b3361

Ответ 1

os.path.realpath(path): возвращает канонический путь указанного имени файла, исключая любые символические ссылки, встречающиеся в пути (если они поддерживаются операционной системой).

Используйте его в имени каталога и подкаталога, затем проверьте, что последний начинается с прежнего.

Ответ 2

Модуль 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

Ответ 3

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)

Ответ 4

Проблемы со многими из предложенных методов

Если вы собираетесь тестировать исходное происхождение каталога с помощью метода сравнения строк или os.path.commonprefix, они подвержены ошибкам с одинаково названными путями или относительными путями. Например:

  • /path/to/files/myfile будет отображаться как дочерний путь /path/to/file с использованием многих методов.
  • /path/to/files/../../myfiles не будет отображаться в качестве родительского элемента /path/myfiles/myfile многими способами. На самом деле, это так.

предыдущий ответ Роб Деннис дает хороший способ сравнить происхождение пути, не сталкиваясь с этими проблемами. В Python 3.4 добавлен модуль pathlib, который может выполнять эти операции с более сложным способом, необязательно, без ссылки на базовую ОС. jme описал в еще один предыдущий ответ, как использовать pathlib с целью точного определения того, является ли один путь дочерним по отношению к другому. Если вы предпочитаете не использовать pathlib (не уверен, почему, это довольно здорово), то Python 3.5 представил новый метод на основе ОС в os.path, который позволяет выполнять проверки родительского родительского пути в аналогичных точках и ошибках, свободный способ с гораздо меньшим количеством кода.

Новое для 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])

Точный однострочный

Вы можете объединить всю партию в однострочный оператор if в Python 3.5. Он уродливый, он включает ненужные повторяющиеся вызовы на os.path.abspath, и он определенно не поместится в руководящих принципах длины строки в 80 символов PEP 8, но если вам нравится такая штука, вот что:

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

Ответ 5

так, мне это нужно, и из-за критики о 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))

Ответ 6

def is_in_directory(filepath, directory):
    return os.path.realpath(filepath).startswith(
        os.path.realpath(directory) + os.sep)

Ответ 7

Мне нравится "path in other_path.parents", упомянутый в другом ответе, потому что я большой поклонник pathlib, НО я считаю, что этот подход немного тяжелый (он создает один экземпляр Path для каждого родителя до корня пути). Также случай, когда путь == other_path терпит неудачу с этим подходом, тогда как os.commonpath будет успешным в этом случае.

Ниже представлен другой подход с собственным набором плюсов и минусов по сравнению с другими методами, идентифицированными в различных ответах:

try:
   other_path.relative_to(path)
except ValueError:
   ...no common path...
else:
   ...common path...

который является немного более подробным, но может быть легко добавлен как функция в вашем приложении общего модуля утилит или даже добавить метод Path в момент запуска.

Ответ 8

Ответ Оливера, кажется, работает, но .resolve() прежнему необходимо вызывать на обоих путях перед запуском .relative_to(), если речь идет о потенциально относительных путях.

Ответ 9

Я бы проверил результат от 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 False
    return False

Ответ 10

Основываясь на другом ответе здесь, с исправлением и с дружественным пользователем именем:

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