Watch at Ease看见世界,读懂生活。

技术 · 实用指南

zipfile.Path.is_symlink()总返回False?核对运行版本与成员类型

20项本地测试区分Python 3.12的方法占位实现、ZIP中的Unix类型标记、CRC完整性,以及标准解压器实际生成的对象。

原创示意图:3.12中alias的存在、文件和链接谓词与Unix类型位。
原创示意图:实际3.12.14结果;方法返回False与存储链接标记可以同时出现。 Watch at Ease

在CPython 3.12.14中,一个标记为Unix符号链接的合成ZIP成员确实存在,zipfile.Path.is_file()也返回True,但is_symlink()返回False。查看存储的模式位才能识别链接标记。False并不能证明压缩包只含普通文件。

先检查运行版本和压缩包采用的元数据约定,再考虑改动文件。我们在Linux上用一个751字节、含7个成员的合成ZIP完成20项断言测试,也证实CRC通过、解压成功和成员类型检查回答的是不同问题。

1. 检查实现,不只检查方法是否存在

输出sys.version,并确认对象是zipfile.Path,而不是指向已解压文件的pathlib.Path。在实际3.12.14解释器中,查看zipfile.Path.is_symlink源码可见它直接返回False。方法可以调用,因此仅检测属性是否存在无法发现这一限制。

Python分版本文档记录了3.13的行为变化:此前is_symlink无条件返回False。我们阅读了文档,但没有执行3.13。应在实际部署版本上用已知且无害的链接标记样本检查,不要仅凭最新文档或方法名称推断行为。

2. 在明确的元数据约定下逐项检查

遍历已打开压缩包的infolist(),保留每个ZipInfo。记录filename、create_system和模式表达式info.external_attr >>16。在本测试采用的Unix主机约定中,先要求create_system==3,再用stat.S_ISLNK、stat.S_ISREG或stat.S_ISDIR解释模式。它们检查存储的类型位,不是在检查当前文件系统。

PKWARE规定外部属性的含义依赖主机系统。我们的有限分类器把非Unix主机和没有可识别类型的模式标为unknown。unknown不等于普通文件,也不代表没有问题。其他生成程序及其他主机约定需要独立核实,不能强行当作否定结果。

3. 文件名、目标文本和类型位分别判断

样本中的alias与dangling都带链接标记,尽管dangling指向不存在的目标。读取成员得到的只是存储的target.txt和absent.txt文本。名为ordinary.link的普通成员则凭明确Unix类型位归为普通文件,文件扩展名不能证明链接类型。

folder/成员同时具有目录式名称和目录模式。ZipInfo.is_dir依据名称末尾斜杠判断,不等于检查所有Unix类型位。仅带权限位的成员没有可识别类型,因此保持unknown;权限位本身不能证明它是普通文件。

4. 单独测量解压结果

testzip()对整个样本返回None,表示所测成员通过该CRC及头部检查,链接元数据却仍然存在。完整性检查并未回答成员是否为符号链接,也没有证明另一解压器会生成什么对象。

我们只用标准ZipFile.extract把无害的alias解压到全新的临时目录。在本次3.12.14/Linux测试中,结果是内容为target.txt的普通文件,而不是操作系统符号链接。没有运行外部解压程序。不要把这一结果推广到其他工具,也不要为了让方法返回预期值而改写元数据。

5. 按实际交付目标定义完成

20项测试检查方法可用性、exists/is_file/is_symlink、7成员类型矩阵、悬空目标和扩展名对照、未知元数据、CRC通过、目标文本、缺失成员,以及解压文件的类型和精确内容。报告保留运行版本、方法源码、脚本哈希和压缩包哈希。

自己的流程也应保留未改动压缩包和小型已知样本。核对成员清单,只按规则实际支持的约定分类;根据规则调查或拒绝未知类型。再检查计划使用的解压器所生成的对象类型与内容。这是成员类型诊断,并非完整的不可信压缩包解压验证器,也不是跨平台兼容保证。

原创示意图:7个成员的类型分类、未知项、CRC和解压结果。
原创示意图:类型、完整性和解压是三种结果;unknown不等于普通文件。 Watch at Ease