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

114voto

jme Points 694

Le module pathlib de Python 3 rend cela facile avec son attribut Path.parents. Par exemple :

from pathlib import Path

root = Path('/chemin/vers/répertoire')
child = root / 'quelque' / 'enfant' / 'dossier'
autre = Path('/quelque/autre/chemin')

Ensuite :

>>> root in child.parents
True
>>> autre in child.parents
False

48voto

Tom Bull Points 829

Problèmes avec de nombreuses méthodes suggérées

Si vous allez tester la parenté du répertoire avec la comparaison de chaînes ou les méthodes os.path.commonprefix, celles-ci sont sujettes à des erreurs avec des chemins similaires ou des chemins relatifs. Par exemple:

  • /chemin/vers/fichiers/monfichier serait montré comme un chemin enfant de /chemin/vers/fichier en utilisant de nombreuses méthodes.
  • /chemin/vers/fichiers/../../mesfichiers ne serait pas montré comme un parent de /chemin/mesfichiers/monfichier par de nombreuses méthodes. En fait, il l'est.

La réponse précédente de Rob Dennis propose une bonne façon de comparer la parenté des chemins sans rencontrer ces problèmes. Python 3.4 a ajouté le module pathlib qui peut effectuer ce type d'opérations de chemin de manière plus sophistiquée, facultativement sans référence au système d'exploitation sous-jacent. jme a décrit dans uneautre réponse précédente comment utiliser pathlib pour déterminer avec précision si un chemin est un enfant d'un autre. Si vous préférez ne pas utiliser pathlib (je ne sais pas pourquoi, c'est plutôt génial) alors Python 3.5 a introduit une nouvelle méthode basée sur le système d'exploitation dans os.path qui vous permet d'effectuer des vérifications parent-enfant de chemin de manière tout aussi précise et sans erreur avec beaucoup moins de code.

Nouveauté pour Python 3.5

Python 3.5 a introduit la fonction os.path.commonpath. C'est une méthode spécifique à l'OS sur lequel le code s'exécute. Vous pouvez utiliser commonpath de la manière suivante pour déterminer avec précision la parenté des chemins:

def path_is_parent(parent_path, child_path):
    # Lissez les noms de chemins relatifs, note : si vous êtes préoccupé par les liens symboliques, vous devriez également utiliser os.path.realpath
    parent_path = os.path.abspath(parent_path)
    child_path = os.path.abspath(child_path)

    # Comparez le chemin commun du chemin parent et du chemin enfant avec le chemin commun du seul chemin parent. En utilisant la méthode commonpath seulement sur le chemin parent régularisera le nom du chemin de la même manière que la comparaison qui traite les deux chemins, en supprimant tout séparateur de chemin final
    return os.path.commonpath([parent_path]) == os.path.commonpath([parent_path, child_path])

Efficace en une ligne

Vous pouvez combiner le tout en une seule instruction en une ligne dans Python 3.5. C'est moche, ça inclut des appels redondants inutiles à os.path.abspath et ça ne rentrera certainement pas dans les directives de longueur de ligne PEP 8 à 79 caractères, mais si vous aimez ce genre de chose, le voici:

if os.path.commonpath([os.path.abspath(chemin_parent_a_tester)]) == os.path.commonpath([os.path.abspath(chemin_parent_a_tester), os.path.abspath(chemin_enfant_a_tester)]):
    # Oui, le chemin enfant est sous le chemin parent

Nouveauté pour Python 3.9

pathlib a une nouvelle méthode sur PurePath appelée is_relative_to qui effectue cette fonction directement. Vous pouvez lire la documentation python sur comment is_relative_to fonctionne si vous avez besoin de voir comment l'utiliser. Ou vous pouvez voir ma autre réponse pour une description plus détaillée de son utilisation.

14voto

jgoeders Points 381
def is_subdir(path, répertoire):
    path = os.path.realpath(path)
    répertoire = os.path.realpath(répertoire)
    relative = os.path.relpath(path, répertoire)
    return not relative.startswith(os.pardir + os.sep)

12voto

blaze Points 2930

Os.path.realpath(chemin): Retourne le chemin canonique du nom de fichier spécifié, éliminant tout lien symbolique rencontré dans le chemin (s'ils sont pris en charge par le système d'exploitation).

Utilisez-le sur le nom du répertoire et du sous-répertoire, puis vérifiez que ce dernier commence par le premier.

11voto

Tom Bull Points 829

Nouveau pour Python 3.9

pathlib possède une nouvelle méthode sur PurePath appelée is_relative_to qui effectue cette fonction directement. Vous pouvez lire la documentation Python sur le fonctionnement de is_relative_to, ou utiliser cet exemple :

from pathlib import Path

child_path = Path("/chemin/vers/fichier")
if child_path.is_relative_to("/chemin"):
    print("/chemin/vers/fichier est un enfant de /chemin") # Cela s'affiche
if child_path.is_relative_to("/autrechemin"):
    print("/chemin/vers/fichier est un enfant de /autrechemin") # Cela ne s'affiche pas

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