So prüfen Sie, ob ein Verzeichnis ein Unterverzeichnis eines anderen Verzeichnisses ist

Ich schreibe gern ein Template-System in Python, das es erlaubt, Dateien einzuschließen.

z.B

     Dies ist eine Vorlage
     Sie können sicher Dateien mit safe_include "othertemplate.rst "enthalten

Wie Sie wissen, können auch Dateien gefährlich sein. Zum Beispiel, wenn ich das Template-System in einer Web-Anwendung, die es Benutzern ermöglicht, ihre eigenen Vorlagen zu erstellen, können sie so etwas wie

 Ich möchte deine Passwörter: safe_include` / etc / password`

Deshalb muss ich also die Einbindung von Dateien in Dateien einschränken, die zB in einem bestimmten Unterverzeichnis (zB /home/user/templates )

Die Frage ist jetzt: Wie kann ich überprüfen, ob /home/user/templates/includes/inc1.rst in einem Unterverzeichnis von /home/user/templates ?

Würde der folgende Code funktionieren und sicher sein?

 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 #eg /a/b/c/d.rst and directory is /a/b, the common prefix is /a/b return os.path.commonprefix([file, directory]) == directory 

So lange, wie allow_symlink ist False, sollte es sicher sein, denke ich. Das Verlassen von Symlinks natürlich würde es unsicher machen, wenn der Benutzer in der Lage ist, solche Links zu erstellen.

UPDATE – Lösung Der obige Code funktioniert nicht, wenn Zwischenverzeichnisse symbolische Links sind. Um dies zu verhindern, musst du realpath anstelle von abspath .

UPDATE: Hinzufügen eines nachlaufenden / to-Verzeichnisses, um das Problem mit commonprefix zu lösen () Reorx wies darauf hin.

Dies macht auch allow_symlink unnötig, da Symlinks zu ihrem realen Ziel erweitert werden

 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 #eg /a/b/c/d.rst and directory is /a/b, the common prefix is /a/b return os.path.commonprefix([file, directory]) == directory 

7 Solutions collect form web for “So prüfen Sie, ob ein Verzeichnis ein Unterverzeichnis eines anderen Verzeichnisses ist”

Os.path.realpath (Pfad): Kehrt den kanonischen Pfad des angegebenen Dateinamens zurück und eliminiert alle symbolischen Links, die im Pfad angetroffen werden (falls sie vom Betriebssystem unterstützt werden).

Verwenden Sie es auf Verzeichnis und Unterverzeichnis, dann überprüfen Sie letztere beginnt mit früheren.

 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) 

Das pathlib Modul von Python 3 macht dies mit seinem Path.parents- Attribut einfach . Beispielsweise:

 from pathlib import Path root = Path('/path/to/root') child = root / 'some' / 'child' / 'dir' other = Path('/some/other/path') 

Dann:

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

Also, ich brauchte das, und wegen der Kritik an commonprefx ging ich anders:

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

Probleme mit vielen der vorgeschlagenen Methoden

Wenn Sie für Verzeichnis-Parentage mit String-Vergleich oder os.path.commonprefix Methoden testen os.path.commonprefix , sind diese anfällig für Fehler mit ähnlich benannten Pfaden oder relativen Pfaden. Beispielsweise:

  • /path/to/files/myfile würde als /path/to/files/myfile von /path/to/file mit vielen Methoden angezeigt.
  • /path/to/files/../../myfiles würde nicht als ein Elternteil von /path/myfiles/myfile von vielen Methoden /path/myfiles/myfile . In der Tat ist es.

Die vorherige Antwort von Rob Dennis bietet eine gute Möglichkeit, Pfad parentage ohne Begegnung mit diesen Problemen zu vergleichen. Python 3.4 fügte das pathlib Modul hinzu, das diese Art von pathlib in einer anspruchsvolleren Weise ausführen kann, wahlweise ohne Bezug auf das zugrunde liegende OS. Jme hat in einer anderen vorherigen Antwort beschrieben, wie man pathlib verwendet, um genau zu bestimmen, ob ein Pfad ein Kind eines anderen ist. Wenn Sie es vorziehen, nicht mit dem pathlib (nicht sicher, warum ist es ziemlich großartig), dann hat Python 3.5 eine neue OS-basierte Methode in os.path , die es Ihnen ermöglicht, Pfad Eltern-Kind-Checks in einer ähnlich genauen und fehlerfreien Weise durchzuführen Mit viel weniger Code.

Neu für Python 3.5

Python 3.5 hat die Funktion os.path.commonpath . Dies ist eine Methode, die für das Betriebssystem spezifisch ist, auf dem der Code läuft. Sie können commonpath in der folgenden Weise verwenden, um genau zu bestimmen Pfad Parentage:

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

Genaue Einzeiler

Du kannst das ganze Los in eine einzeilige if-Anweisung in Python 3.5 kombinieren. Es ist hässlich, es beinhaltet unnötige doppelte Anrufe an os.path.abspath und es wird definitiv nicht in die PEP 8 79-Zeichen Zeilen-Leitlinien passen, aber wenn Sie diese Art von Ding, hier geht:

 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 

Ich würde das Ergebnis von commonprefix gegen den Dateinamen testen, um eine bessere Antwort zu bekommen, so etwas wie dieses:

 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's False return False 

Basierend auf einer anderen Antwort hier, mit Korrektur, und mit einem benutzerfreundlichen Namen:

 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)) 
Python ist die beste Programmiersprache der Welt.