摘要

  • RFC 1952 规定 gzip 文件由连续成员组成,每个成员都有自己的头部、压缩块以及 CRC32/ISIZE 尾部;追加新成员即可延长最终输出。
  • 文件名、注释、时间和扩展字段是可跳过的描述信息,不是经过认证的来源证据;成员序列也不等于随机访问索引。
  • CRC32 检查单个成员的还原数据,ISIZE 只保存该成员长度对 2^32 取模的结果,两者都不能证明追加者身份或整个文件的绝对长度。

不动旧字节,结果仍然变长

设想一份已压缩归档的运行记录。昨天的 gzip 流已经完整写完,今天的程序只在末尾再写一个完整 gzip 流。昨天那部分的哈希没有变化,存储位置也没有变化,可从头解压所得的逻辑内容已经多出一天。

这不是解压器的宽容习惯,而是 RFC 1952 写入格式的结构:gzip 文件是一系列首尾相接的成员,成员之间没有额外总目录。每个成员都有可识别的开头和独立尾部。尾部表达的是“这一成员闭合”,而不是“文件名之下再也不能有有效内容”。

RFC Editor 的记录显示,P. Deutsch 的这份文档发布于 1996 年 5 月,类别为 Informational。它不是标准轨互联网标准,也不是所有实现行为一致的证明。它提供的是格式与合规边界。

规范追求跨处理器、操作系统、文件系统与字符集的可移植性,要求能够用有限中间存储进行流式处理,并保持无专利实现与既有 gzip 兼容。随机访问明确不在目标之内。可追加意味着可以继续顺序读取,不意味着可以直接跳到任意成员。

头部里的字段并不拥有同等权力

两个固定字节识别 gzip,CM 表示压缩方法,数值 8 对应 DEFLATE。FLG 声明可选字段,保留位必须为零。这些是独立实现必须共同理解的硬边界。

MTIME、FNAME、FCOMMENT、FEXTRA 与 FTEXT 属于另一层。它们可以携带时间、原名、人工注释、扩展数据或文本提示;解压器即使不理解,也必须能按长度或结束符跳过。MTIME 为零表示没有可用时间,OS 字段也允许忽略。

所以,头部里的原文件名只是对象自述,不是身份。注释可能解释生产过程,却没有签名。时间不证明当前容器何时追加了该成员。扩展字段之所以有边界,是为了让未知信息可以安全越过,而不是迫使所有程序接受同一种解释。

若设置 FHCRC,头部后会保存先前头部字节 CRC32 的低 16 位。成员尾部的 CRC32 则覆盖解压后的数据;旁边的 ISIZE 保存原输入长度对 2^32 取模的值。三者距离很近,证据范围却不同。

校验成功不等于来源可信

CRC32 能有效发现许多意外损坏。解压器还原成员后重新计算并比较,就能检查结果是否与尾部一致。但有能力生成恶意成员的人也能计算新的 CRC32。它不是作者证明、监管链或抗恶意篡改承诺。

ISIZE 也不能被提升为任意大对象的绝对长度。超过 2^32 后会回绕;多成员文件中,每个 ISIZE 只描述自己的输入。总长度必须由读取者逐段还原并累计。

容器与算法仍然分层。RFC 1951 定义 DEFLATE 的块与编码;RFC 1950 则用另一套头部和 Adler-32 把 DEFLATE 包在 zlib 流中。后来 RFC 6713 注册 application/gzip 和 application/zlib 时,明确区分了 gzip 的文件型头尾与 zlib 的流格式。

这一分层避免把所有决定塞进压缩算法。DEFLATE 不负责文件名;gzip 成员也不重新定义哈夫曼编码。边界明确后,兼容性才可以局部判断。

运行实现让成员序列真正可见

GNU gzip 手册把连接多个压缩文件列为实际用法:gunzip 会依次还原所有成员。它同时说明,把原输入先合并再压缩通常效率更高;对于多成员文件,--list 展示的未压缩长度和 CRC 只属于最后一个成员。

于是,同一对象可以被完整解压,却被检查命令局部概括。一个安全扫描器若在首个尾部停止,另一个消费者若继续读取,它们面对的“文件内容”就不同。这里不一定有人违反 RFC,问题是组织没有说明检查覆盖了哪些成员。

RFC 9110继续把 gzip 列为 HTTP 内容编码,并引用 RFC 1952。这能证明协议词义,不能证明每个代理、客户端或扫描产品都以相同方式处理多成员序列。实现行为仍需独立测试。

最小共同层可以非常严格

Running-Code Primacy 提醒我们分开文档、实现与运行证据。RFC 证明成员结构被发布;GNU 手册证明某一持续维护的实现明确支持连接。两者相加也不是全行业普查。

Minimum Initial Specification 解释了为何“最小”并非“模糊”。识别字节、方法、长度、保留位和尾部校验必须严格一致;是否展示注释、采用时间或解释 OS 提示,可以留给本地决定。

Reality Layers 则把“一个 gzip 文件”的符号层与成员序列的运行层分开。一个名称之下可以有多次开始、多次闭合和多组自述。文件没有撒谎,只是名称比内部现实更粗。