Skip to content

通用工具兼容性 ​

结论摘要

前置的 32 字节 Header 不影响通用工具打开;真正的风险在文件末尾 —— Footer 与恢复记录位于 EOCD 之后,二者合计超过 65535 字节时, bsdtar 与 Python zipfile 会因只回扫文件末尾 64 KB 而找不到 EOCD。 7-Zip 因搜索范围更宽而不受影响。

为什么要验证这件事 ​

规范的设计前提里有一条:

本格式基于标准 ZIP64 构建,通用压缩工具可以正常解压并读出全部条目名列表。

这是格式的核心承诺。若不成立,那么"可排障性""生态互操作性""零成本迁移"三条设计理由 全部落空,格式应重新设计。因此这里用可复现的实验验证该前提,并界定它的成立条件。

测试环境 ​

项目版本 / 说明
操作系统Windows 10/11 (x64)
7-Zip26.03 (x64)
bsdtarWindows 内置 tar.exe
Python3.13.12(zipfile 标准库)
测试文件按规范手工构造的等价容器

测试结果 ​

基准是一个含 2 个条目的标准 ZIP,在此之上叠加变量构造变体。

变体7-Zip l7-Zip xbsdtarPython zipfile
纯 ZIP(基准)通过通过通过通过
Header + 偏移已修正通过通过通过通过
Header + 偏移未修正通过通过通过通过
Header + ZIP + Footer 24B通过通过通过通过
Footer 计入 EOCD 注释长度通过通过通过通过
Header + ZIP + 64 KB 恢复记录 + Footer通过通过失败失败
Header + ZIP + 1 MB 恢复记录 + Footer通过通过失败失败
纯 ZIP + Footer(隔离 Header 变量)通过通过通过通过
强制 ZIP64 + Header(偏移已修正)通过通过通过通过
强制 ZIP64 + Header(偏移未修正)通过通过通过通过
强制 ZIP64(无 Header)通过通过通过通过

分界线非常干脆:只有"EOCD 之后的数据超过 64 KB"这一种情况会失败。

失败时的报错:

[tar]    tar: Error opening archive: Unrecognized archive format
[python] BadZipFile: File is not a zip file

而同一个文件 7-Zip 仍能正常列出全部条目。

原理分析 ​

前置数据为什么不影响 ​

在文件开头插入自定义数据,在 ZIP 领域有成熟先例 —— 自解压归档(SFX)。机制是:

  1. ZIP 的权威索引是文件末尾的 EOCD,工具从末尾向前搜索签名 PK\x05\x06,而不是从开头解析
  2. EOCD 中记录的中央目录偏移是相对文件起始的绝对偏移
  3. 因此只要把偏移字段整体后移前缀长度,工具就能正确定位中央目录

7-Zip 的输出直接印证了它按 SFX 语义处理该文件:

Type = zip
Embedded Stub Size = 32          <- 明确识别出 32 字节前缀

偏移修正:必要性来自规范,不是可用性 ​

这是实测中最反直觉的发现:三个工具在偏移未修正时全都能打开。

原因是它们都实现了同一套前缀补偿逻辑:

前缀长度 = EOCD 在文件中的实际位置 - 中央目录大小 - EOCD 中记录的中央目录偏移

7-Zip 甚至会把推算结果直接打印出来:

Type = zip
Offset = 32          <- 自行推算出的前缀长度

但这不构成可以不修正的理由:

  • 这是各实现的宽容行为,不是 ZIP 规范的要求
  • 依赖宽容行为会掩盖真正的偏移错误 —— 例如条目级偏移写错时,工具可能仍"看起来正常", 问题被推迟到特定条目解压时才暴露

规范中的"必须修正"依然正确;实现指南建议的打包后自检依然必要。

真正的风险:EOCD 之后的尾随数据 ​

ZIP 规范规定 EOCD 是文件的最后一个结构,其后只允许注释, 且注释长度记录在 EOCD 的 commentLength 字段(2 字节,最大 65535)。 因此主流实现的定位策略是只回扫文件末尾 64 KB。

而 .mfp 的 Footer 位于 EOCD 之后、恢复记录位于 Footer 之前,于是产生隐含上限:

recoveryLength + 24(Footer) <= 65535
  • 不超过:严格实现仍能在 64 KB 窗口内找到 EOCD,正常打开
  • 超过:EOCD 被推出搜索窗口,bsdtar 与 Python zipfile 报"不是压缩包"

7-Zip 因搜索范围更宽,不受此限制 —— 这也解释了为什么它能通过全部变体。

缺陷定级 ​

项目评估
缺陷Footer / 恢复记录位于 EOCD 之后,超出 ZIP 规范的注释长度上限
触发条件recoveryLength + 24 > 65535,即恢复记录超过约 64 KB
影响面bsdtar、Python zipfile 等按严格逻辑实现的工具与库;7-Zip 不受影响
严重性中。默认未启用恢复记录时不触发;但恢复记录是可选特性,一旦启用大尺寸即失效
文档问题原表述未标注此约束,属表述过于绝对

同时应指出:若把恢复记录计入 EOCD.commentLength,在 64 KB 以内是完全合规的 ZIP 注释,任何工具都不会拒绝。这提供了一个低成本的兼容区间。

修复方案 ​

方案 A:仅修正文档表述(成本最低) ​

把"可以直接打开并解压"改为带条件的表述,明确标注恢复记录带来的尺寸约束。

  • 优点:零改动成本
  • 缺点:把约束转嫁给使用者,格式的"可排障性"承诺在启用恢复记录后打折

方案 B:限制恢复记录尺寸(低成本折中) ​

规定 recoveryLength <= 65511(65535 − 24),并把恢复记录与 Footer 计入 EOCD.commentLength。

  • 优点:完全符合 ZIP 规范,所有工具都能打开,实现改动小
  • 缺点:恢复记录容量被限制在 64 KB,对需要覆盖中央目录与 manifest 的大容器可能不够

不再把 Footer 放在 EOCD 之后,而是作为普通 ZIP 条目存放:

[Local File Header] .mfp-footer      <- 承载 Footer Descriptor 与恢复记录
[Local File Header] manifest.json
[Local File Header] fonts/<sha256>...
[Central Directory][ZIP64 EOCD][Locator][EOCD]   <- 文件末尾,无任何尾随数据

读取端改为:从中央目录取出 .mfp-footer 条目并解析,而不是检查 EOCD 紧接着的 24 字节。

  • 优点:EOCD 恒为文件最后一个结构,彻底消除 64 KB 限制;恢复记录不再有尺寸上限; 结构更统一(所有扩展数据都走 ZIP 条目,与 manifest.json 一致)
  • 缺点:需修改规范的 Footer、恢复记录与格式识别流程,客户端读取逻辑需同步调整

推荐组合:以方案 C 为目标,配合方案 A 先行修正文档表述作为过渡。

未验证项与局限 ​

项说明
无真实 .mfp 样本全盘检索未发现任何 .mfp 文件;结论基于按规范手工构造的等价容器
客户端尚未实现实现指南的验收清单该条仍为未勾选状态
WinRAR 无命令行数据Rar.exe 无法用于测试 ZIP;WinRAR GUI 行为未取得实测证据,结论为推理
未覆盖加密容器规范提及 WinZip AES;加密后通用工具会提示输入密码,行为与"打不开"易混淆
未覆盖数据描述符场景流式写入(bit 3 标志 + data descriptor)的容器未构造测试,预计无影响
7-Zip 版本差异实测使用 26.03,较旧版本行为可能不同

在缺少失败样本的情况下,未能复现"7-Zip 打不开"的现象。若后续取得该文件,应优先做三件事:

  1. 用 7z l -slt 检查其 Type 与 Embedded Stub Size
  2. 用十六进制查看器确认 EOCD 距文件末尾的实际字节数
  3. 检查 EOCD 的 commentLength 字段与实际尾随数据长度是否一致

结论 ​

  1. 规范的核心设计前提成立:按规范修正偏移后,前置 32 字节 Header 的容器可被 7-Zip 正常打开与解压。
  2. 偏移修正的必要性是"规范性"而非"可用性":三者对未修正偏移的文件也能打开, 规范要求修正依然正确,但不应把工具的宽容当作格式正确性的依据。
  3. 存在一个真实缺陷:Footer 与恢复记录位于 EOCD 之后,超过 65535 字节时 bsdtar 与 Python zipfile 无法定位 EOCD。这是"通用工具可打开"承诺的唯一失效点。
  4. 文档表述需要修正,或采纳方案 C 从结构上消除该约束。

本仓库是协议层规范仓库,规范文本以 MIT 发布;包内字体的再分发受其自身许可证约束。