103 votes

Comment vérifier si un répertoire est un sous-répertoire d'un autre répertoire

J'aime écrire un système de modèle en Python, qui permet d'inclure des fichiers.

par exemple.

    C'est un modèle
    Vous pouvez inclure des fichiers en toute sécurité avec safe\_include\`othertemplate.rst\`

Comme vous le savez, l'inclusion de fichiers peut être dangereuse. Par exemple, si j'utilise le système de modèle dans une application web qui permet aux utilisateurs de créer leurs propres modèles, ils pourraient faire quelque chose comme

Je veux vos mots de passe: safe\_include\`/etc/password\`

Par conséquent, je dois limiter l'inclusion de fichiers aux fichiers qui se trouvent par exemple dans un certain sous-répertoire (par exemple /home/user/templates)

La question est maintenant : Comment puis-je vérifier si /home/user/templates/includes/inc1.rst se trouve dans un sous-répertoire de /home/user/templates?

Le code suivant fonctionnerait-il et serait-il sécurisé?

import os.path

def in_directory(file, directory, allow_symlink = False):
    #rendre tout deux en absolu    
    directory = os.path.abspath(directory)
    file = os.path.abspath(file)

    #vérifier si le fichier est un lien symbolique, et dans ce cas, retourner faux s'ils ne sont pas autorisés
    if not allow_symlink and os.path.islink(file):
        return False

    #retourner vrai, si le préfixe commun des deux est égal au répertoire
    #par exemple, /a/b/c/d.rst et le répertoire est /a/b, le préfixe commun est /a/b
    return os.path.commonprefix([file, directory]) == directory

Tant que allow_symlink est False, cela devrait être sécurisé, je pense. Autoriser les liens symboliques bien sûr le rendrait insecure si l'utilisateur peut créer de tels liens.

MISE À JOUR - Solution Le code ci-dessus ne fonctionne pas si des répertoires intermédiaires sont des liens symboliques. Pour prévenir cela, vous devez utiliser realpath au lieu de abspath.

MISE À JOUR: ajouter un / final au répertoire pour résoudre le problème avec commonprefix() souligné par Reorx.

Cela rend également allow_symlink inutile car les liens symboliques sont étendus vers leur destination réelle

import os.path

def in_directory(file, directory):
    #rendre tout deux en absolu    
    directory = os.path.join(os.path.realpath(directory), '')
    file = os.path.realpath(file)

    #retourner vrai, si le préfixe commun des deux est égal au répertoire
    #par exemple, /a/b/c/d.rst et le répertoire est /a/b, le préfixe commun est /a/b
    return os.path.commonprefix([file, directory]) == directory

7voto

Rob Dennis Points 326

Alors, j'avais besoin de cela et en raison des critiques sur commonprefx, j'ai opté pour une autre solution :

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):
    """
    Le premier argument est-il un sous-répertoire du deuxième argument?

    :param potential_subdirectory :
    :param expected_parent_directory :
    :return: True si potential_subdirectory est un enfant du répertoire parent attendu

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

7voto

Juan A. Navarro Points 1768
def is_in_directory(filepath, directory):
    return os.path.realpath(filepath).startswith(
        os.path.realpath(directory) + os.sep)

3voto

Schollii Points 8020

J'aime l'approche "chemin dans d'autres_chemins.parents" mentionnée dans une autre réponse car je suis un grand fan de pathlib, MAIS je trouve que cette approche est un peu lourde (elle crée une instance Path pour chaque parent à la source du chemin). De plus, dans le cas où le chemin == autre_chemin échouera avec cette approche, alors que os.commonpath réussirait dans ce cas.

Voici une approche différente, avec ses propres avantages et inconvénients par rapport aux autres méthodes identifiées dans les différentes réponses :

try:
   autre_chemin.relative_to(chemin)
except ValueError:
   ...aucun chemin commun...
else:
   ...chemin commun...

qui est un peu plus verbeux mais peut facilement être ajouté en tant que fonction dans le module des utilitaires communs de votre application ou même ajouter la méthode à Path au démarrage.

0voto

Jacek Błocki Points 130

J'ai utilisé la fonction ci-dessous pour un problème similaire :

def is_subdir(p1, p2):
    """retourne vrai si p1 est p2 ou son sous-répertoire"""
    p1, p2 = os.path.realpath(p1), os.path.realpath(p2)
    return p1 == p2 or p1.startswith(p2+os.sep)

Après avoir rencontré des problèmes avec les liens symboliques, j'ai modifié la fonction. Maintenant, elle vérifie si les deux chemins sont des répertoires.

def is_subdir(p1, p2):
    """vérifie si p1 est p2 ou un de ses sous-répertoires
    :param str p1: candidat sous-répertoire
    :param str p2: répertoire parent
    :returns True si p1, p2 sont des répertoires et si p1 est p2 ou l'un de ses sous-répertoires"""
    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

0voto

OzInClouds Points 1

Avec vos inspirations, cette méthode a été ajoutée à mes utils:

def is_in_basefolder(path_to_check: PosixPath, basefolder: PosixPath):
        """
        vérifie si un chemin donné se trouve dans le dossier de base

        paramètres:
            path_to_check: un chemin à comparer avec le dossier de base
            basefolder: le dossier de base
        """
        path = path_to_check.resolve()
        base = basefolder.resolve()

        if path == base:
            return True

        if base.stem in path.parts:
            return True
        else:
            return False

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X