← 中文资源

简体中文版本

清洗用户上传的文件名

把不可信文件名转换为安全基名:去除路径、拦截保留名、规范化 Unicode 并限制长度。

阅读英文原文

功能说明

safe_filename 把用户提供的文件名转换成可以安全落盘的基名。它先做 NFKC Unicode 规范化,再把反斜杠统一成正斜杠并只保留最后一段,从而剥离 ../、..\ 和绝对路径;随后删除控制字符、替换 Windows 和 POSIX 的非法字符、压缩空白并去掉首尾的点和空格。对于 Windows 保留设备名(如 CON、NUL、COM1)会加下划线前缀,最后按最大长度截断并尽量保留扩展名。

它适合上传接口在生成存储路径之前做第一道防线。函数返回的只是文件名,不包含目录;调用方仍应把文件写入固定目录,并在拼接后再次校验解析后的真实路径,防止符号链接或编码绕过。

关键代码

先取最后一段,再逐字符过滤 Unicode 控制类和格式类字符:

normalized = unicodedata.normalize("NFKC", name)
normalized = normalized.replace("\\", "/").rsplit("/", 1)[-1]
normalized = "".join(
    character
    for character in normalized
    if unicodedata.category(character)[0] != "C"
)
base = _INVALID.sub("_", normalized)
base = _WHITESPACE.sub(" ", base).strip(" .")

扩展名截断时只为较短的字母数字后缀保留位置,超长后缀会被丢弃。

使用注意

该函数不负责百分号解码或 URL 解码,调用方应先完成解码,否则 %2e%2e%2f 不会被视为路径分隔符。它也不处理文件内容、MIME 类型、双扩展名伪装(如 invoice.pdf.exe)或同名覆盖,这些需要额外的校验和随机化存储名。Unicode 规范化可能改变某些字符的视觉形式,但不会消除同形异义攻击。

max_length 限制在 8 到 255 之间,长度按 Python 字符数而不是字节数计算,某些多字节字符仍可能超过文件系统限制。保留名检查只看第一个点之前的部分,并忽略大小写。空文件名、只有点和空格的文件名会返回 fallback。函数不会修改目录分隔符之外的内容,也不会保证结果在所有操作系统上都合法。

验证方式

示例处理路径穿越、Windows 绝对路径、保留名、隐藏文件名、带重音的文件名、超长名称和包含 NUL 的名称,输出应分别变为 passwd、cmd.exe、_CON.txt、hidden、保留重音的 résumé 2026.pdf、截断后保留 .gz 的名称以及去除 NUL 的名称。传入过小的 max_length 应输出 max_length must be between 8 and 255。