私はPythonでテンプレートシステムを書くのが好きです。これにより、ファイルを含めることができます。
例えば.
これはテンプレートです safe_include`othertemplate.rst` でファイルを安全に含めることができます
ご存知のように、ファイルを含めることは危険な場合があります。たとえば、ユーザーが独自のテンプレートを作成できるWebアプリケーションでテンプレートシステムを使用する場合、ユーザーは次のようなことを行う可能性があります
パスワードが必要です: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である限り、安全だと思います。もちろん、シンボリックリンクを許可すると、ユーザーがそのようなリンクを作成できる場合は安全でなくなります。
PDATE-Solution中間ディレクトリがシンボリックリンクの場合、上記のコードは機能しません。これを防ぐには、realpath
の代わりにabspath
を使用する必要があります。
PDATE: 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
os.path.realpath(path):指定されたファイル名の正規パスを返し、パスで検出されたシンボリックリンクを削除します(オペレーティングシステムでサポートされている場合)。
それをディレクトリとサブディレクトリ名で使用し、後者が前者で始まることを確認します。
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
文字列比較またはos.path.commonprefix
メソッドを使用してディレクトリの親子関係をテストする場合、これらは同様の名前のパスまたは相対パスでエラーが発生しやすくなります。例えば:
/path/to/files/myfile
は、多くのメソッドを使用して/path/to/file
の子パスとして表示されます。/path/to/files/../../myfiles
は、多くのメソッドで/path/myfiles/myfile
の親として表示されません。実際、そうです。Rob Dennisによる 前の回答 は、これらの問題に遭遇することなくパスの親子を比較するための優れた方法を提供します。 Python 3.4は、これらの種類のパス操作をより高度な方法で実行できるpathlib
モジュールを追加しました。オプションで、基盤となるOSを参照する必要はありません。jmeは another前の回答 あるパスが別のパスの子であるかどうかを正確に判断するためにpathlib
を使用する方法。pathlib
を使用したくない場合(理由はわからないが、かなり素晴らしい)次にPython 3.5がos.path
に新しいOSベースのメソッドを導入しました。これにより、はるかに少ないコードで、正確でエラーのない方法でパスの親子チェックを実行できます。 。
Python 3.5では、関数os.path.commonpath
が導入されました。これは、コードが実行されているOSに固有のメソッドです。次のように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])
Python 3.5。のifステートメントにロット全体を組み合わせることができます。醜いですが、os.path.abspath
への不要な重複呼び出しが含まれており、PEPに確実に適合しません8 79文字の行長のガイドラインですが、そのようなことが好きな場合は、次のようになります。
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)
それで、私はこれを必要としました、そして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))
def is_in_directory(filepath, directory):
return os.path.realpath(filepath).startswith(
os.path.realpath(directory) + os.sep)
私はpathlibの大ファンなので、別の回答で述べた「other_path.parentsのパス」アプローチが好きですが、そのアプローチは少し重いと感じます(パスのルートへの親ごとに1つのPathインスタンスを作成します)。また、path == other_pathがそのアプローチで失敗するのに対して、os.commonpathはそのケースで成功します。
以下は異なるアプローチであり、さまざまな回答で特定された他の方法と比較した独自の長所と短所があります。
try:
other_path.relative_to(path)
except ValueError:
...no common path...
else:
...common path...
これはもう少し冗長ですが、アプリケーションの共通ユーティリティモジュールの関数として簡単に追加できます。また、起動時にメソッドをPathに追加することもできます。
私は同様の問題のために以下の関数を使用しました:
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)
シンボリックリンクで問題が発生した後、関数を変更しました。現在は、両方のパスがディレクトリであるかどうかをチェックします。
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