在python中清理文件路径

jan*_*jan 9 python

我有一个文件浏览器应用程序,它将目录及其内容公开给用户.

我想清理用户输入,这是一个文件路径,因此它不允许绝对路径,如'/ tmp /'和相对路径,如'../../etc'

是否有跨平台执行此操作的python函数?

Ert*_*yui 13

也适用于寻找摆脱"A /./ B" - >"A/B和"A/B /../ C" - >"A/C"路径的方法的人们.你可以使用os.path.normpath它.


Tom*_*ull 6

适用于python的全面文件路径清理工具

我对任何可用的方法都不太满意,因此我写了自己的,比较全面的方法。这适合*从公共端点(http上传,REST端点等)获取输入,并确保如果将数据保存在生成​​的文件路径中,则不会损坏系统**。(注意:此代码针对Python 3+,您可能需要进行一些更改才能使其在2.x上运行)

*没有保证!请不要依赖此代码,除非自己仔细检查。

**同样,没有保证!您仍然可以做一些疯狂的事情,并将* nix系统上的根路径设置为/dev/or /bin/或类似的东西。不要那样做 也有在Windows上一些边缘情况下可能造成损害(设备文件名,例如),您可以检查secure_filename从法werkzeugutils有关处理这些,如果你针对Windows一个良好的开端。

怎么运行的

  • 您需要指定一个根路径,卫生检查人员将确保所有返回的路径都在该根目录下。检查此get_root_path功能在何处执行。确保根路径的值来自您自己的配置,而不是来自用户的输入!
  • 有一个文件名为sanitiser的文件:
    • 将Unicode转换为ASCII
    • 将路径分隔符转换为下划线
    • 仅允许文件名中白名单中的某些字符。白名单包括所有大小写字母,所有数字,连字符,下划线,空格,左括号和右括号以及句号(句号)。您可以根据需要自定义此白名单。
    • 确保所有名称至少包含一个字母或数字(避免使用诸如“ ..”之类的名称)
  • 要获取有效的文件路径,请致电make_valid_file_path。您可以选择在path参数中为其传递子目录路径。这是根路径下的路径,可以来自用户输入。您可以选择在filename参数中为其传递文件名,也可以来自用户输入。您传递的文件名中的任何路径信息都不会用于确定文件的路径,而是将其平整为文件名的有效,安全的组成部分。
    • 如果没有路径或文件名,它将返回根路径,该路径为主机文件系统正确格式化,并带有尾随路径分隔符(/)。
    • 如果存在子目录路径,它将把它分成几个组成部分,使用文件名sanitiser清理每个组成部分,并在没有前导路径分隔符的情况下重建路径。
    • 如果有文件名,它将使用清理工具对名称进行清理。
    • 它将使os.path.join路径组件获得文件的最终路径。
    • 最后要仔细检查生成的路径是否有效和安全,它会检查生成的路径是否在根路径下。可以通过拆分并比较路径的组成部分来正确完成此检查,而不仅仅是确保一个字符串以另一字符串开头。

OK,足够的警告和说明,下面是代码:

import os

def ensure_directory_exists(path_directory):
    if not os.path.exists(path_directory):
        os.makedirs(path_directory)

def os_path_separators():
    seps = []
    for sep in os.path.sep, os.path.altsep:
        if sep:
            seps.append(sep)
    return seps

def sanitise_filesystem_name(potential_file_path_name):
    # Sort out unicode characters
    valid_filename = normalize('NFKD', potential_file_path_name).encode('ascii', 'ignore').decode('ascii')
    # Replace path separators with underscores
    for sep in os_path_separators():
        valid_filename = valid_filename.replace(sep, '_')
    # Ensure only valid characters
    valid_chars = "-_.() {0}{1}".format(string.ascii_letters, string.digits)
    valid_filename = "".join(ch for ch in valid_filename if ch in valid_chars)
    # Ensure at least one letter or number to ignore names such as '..'
    valid_chars = "{0}{1}".format(string.ascii_letters, string.digits)
    test_filename = "".join(ch for ch in potential_file_path_name if ch in valid_chars)
    if len(test_filename) == 0:
        # Replace empty file name or file path part with the following
        valid_filename = "(Empty Name)"
    return valid_filename

def get_root_path():
    # Replace with your own root file path, e.g. '/place/to/save/files/'
    filepath = get_file_root_from_config()
    filepath = os.path.abspath(filepath)
    # ensure trailing path separator (/)
    if not any(filepath[-1] == sep for sep in os_path_separators()):
        filepath = '{0}{1}'.format(filepath, os.path.sep)
    ensure_directory_exists(filepath)
    return filepath

def path_split_into_list(path):
    # Gets all parts of the path as a list, excluding path separators
    parts = []
    while True:
        newpath, tail = os.path.split(path)
        if newpath == path:
            assert not tail
            if path and path not in os_path_separators():
                parts.append(path)
            break
        if tail and tail not in os_path_separators():
            parts.append(tail)
        path = newpath
    parts.reverse()
    return parts

def sanitise_filesystem_path(potential_file_path):
    # Splits up a path and sanitises the name of each part separately
    path_parts_list = path_split_into_list(potential_file_path)
    sanitised_path = ''
    for path_component in path_parts_list:
        sanitised_path = '{0}{1}{2}'.format(sanitised_path, sanitise_filesystem_name(path_component), os.path.sep)
    return sanitised_path

def check_if_path_is_under(parent_path, child_path):
    # Using the function to split paths into lists of component parts, check that one path is underneath another
    child_parts = path_split_into_list(child_path)
    parent_parts = path_split_into_list(parent_path)
    if len(parent_parts) > len(child_parts):
        return False
    return all(part1==part2 for part1, part2 in zip(child_parts, parent_parts))

def make_valid_file_path(path=None, filename=None):
    root_path = get_root_path()
    if path:
        sanitised_path = sanitise_filesystem_path(path)
        if filename:
            sanitised_filename = sanitise_filesystem_name(filename)
            complete_path = os.path.join(root_path, sanitised_path, sanitised_filename)
        else:
            complete_path = os.path.join(root_path, sanitised_path)
    else:
        if filename:
            sanitised_filename = sanitise_filesystem_name(filename)
            complete_path = os.path.join(root_path, sanitised_filename)
        else:
            complete_path = complete_path
    complete_path = os.path.abspath(complete_path)
    if check_if_path_is_under(root_path, complete_path):
        return complete_path
    else:
        return None
Run Code Online (Sandbox Code Playgroud)