J'aime écrire un système de template en Python, qui permet d'inclure des fichiers.
par exemple.
Ceci est un modèle Vous pouvez inclure en toute sécurité des fichiers 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èles 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 restreindre l'inclusion de fichiers à des 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):
#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
Aussi longtemps que allow_symlink
est faux, il devrait être sécurisé, je pense. Autoriser les liens symboliques, bien sûr, rendrait non sûr si l'utilisateur est capable de créer de tels liens.
PDATE - Solution Le code ci-dessus ne fonctionne pas, si les répertoires intermédiaires sont des liens symboliques. Pour éviter cela, vous devez utiliser realpath
au lieu de abspath
.
PDATE: ajout d'un répertoire de fin/vers pour résoudre le problème avec commonprefix () Reorx l'a souligné.
Cela fait également allow_symlink
inutile car les liens symboliques sont étendus à leur destination réelle
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
os.path.realpath (path): renvoie le chemin canonique du nom de fichier spécifié, en éliminant tous les liens symboliques rencontrés 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 ancien.
Le module pathlib
de Python 3 rend cela simple avec son attribut Path.parents . Par exemple:
from pathlib import Path
root = Path('/path/to/root')
child = root / 'some' / 'child' / 'dir'
other = Path('/some/other/path')
Ensuite:
>>> root in child.parents
True
>>> other in child.parents
False
Si vous voulez tester la filiation d'un répertoire avec une comparaison de chaînes ou os.path.commonprefix
méthodes, celles-ci sont sujettes à des erreurs avec des chemins de nom similaire ou des chemins relatifs. Par exemple:
/path/to/files/myfile
serait affiché comme un chemin enfant de /path/to/file
en utilisant de nombreuses méthodes./path/to/files/../../myfiles
ne serait pas affiché en tant que parent de /path/myfiles/myfile
par de nombreuses méthodes. En fait, ça l'est.Le réponse précédente de Rob Dennis fournit un bon moyen de comparer la filiation du chemin sans rencontrer ces problèmes. Python 3.4 a ajouté le module pathlib
qui peut effectuer ce type d'opérations de chemin d'une manière plus sophistiquée, éventuellement sans référencer le système d'exploitation sous-jacent. Jme a décrit dans n autre réponse précédente comment utiliser pathlib
dans le but de déterminer avec précision si un chemin est un enfant d'un autre. Si vous préférez ne pas utiliser pathlib
(vous ne savez pas pourquoi, c'est plutôt bien) puis 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 de chemin d'accès parent-enfant d'une manière similaire précise et sans erreur avec beaucoup moins de code.
Python 3.5 a introduit la fonction os.path.commonpath
. Il s'agit d'une méthode spécifique au système d'exploitation sur lequel le code s'exécute. Vous pouvez utiliser commonpath
de la manière suivante pour déterminer avec précision la filiation du chemin:
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])
Vous pouvez combiner le tout en une instruction if d'une ligne dans Python 3.5. C'est moche, cela inclut des appels inutiles en double à os.path.abspath
et il ne rentrera certainement pas dans les directives de longueur de ligne de 79 caractères du PEP 8, mais si vous aimez ce genre de chose, voici:
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
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)
donc, j'en avais besoin, et en raison des critiques à propos de commonprefx, je suis allé d'une manière différente:
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))
def is_in_directory(filepath, directory):
return os.path.realpath(filepath).startswith(
os.path.realpath(directory) + os.sep)
J'aime le "chemin dans other_path.parents" abordé mentionné dans une autre réponse parce que je suis un grand fan de pathlib, MAIS je pense que cette approche est un peu lourde (elle crée une instance de chemin pour chaque parent à la racine du chemin). Également le cas où path == other_path échouera avec cette approche, alors que os.commonpath réussirait dans ce cas.
Ce qui suit est une approche différente, avec son propre ensemble d'avantages et d'inconvénients par rapport à d'autres méthodes identifiées dans les différentes réponses:
try:
other_path.relative_to(path)
except ValueError:
...no common path...
else:
...common path...
ce qui est un peu plus détaillé 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.
J'ai utilisé la fonction ci-dessous pour un problème similaire:
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)
Après avoir rencontré des problèmes avec le lien symbolique, j'ai modifié la fonction. Maintenant, il vérifie si les deux chemins sont des répertoires.
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