科技 · 实用指南
zlib循环到eof还不停?区分未处理输入和下一个gzip成员
28项本地检查区分结束后的尾部重放、只读首个成员,以及第二个成员已输出全部明文却仍未完整结束的情况。

解压对象eof=True时,gzip输入仍可能包含另一个成员。我们安装的CPython 3.12.14在eof之后还可能保留unconsumed_tail;把它反复交给同一个已结束对象,会让unused_data不断追加尾部字节,却没有新输出。
这是两种不同故障:循环在一个成员结束后继续运行,或者过早停止而漏掉后续成员。28项小型对照还发现更隐蔽的边界:第二个成员被截断时,两个成员合计仍输出全部9个预期明文字节,但第二个对象的eof保持False。
1. 记录解压状态,不只记录输出长度
记录sys.version、zlib.ZLIB_VERSION、zlib.ZLIB_RUNTIME_VERSION、wbits,以及应用允许单成员还是多成员的规则。本次实测为CPython 3.12.14/Linux,构建和运行时zlib均为1.3.2。查阅的C库上游手册标为1.3.1;它是参考版本,不是我们实际运行的版本。
每次小型诊断调用都记录输出长度、eof、unconsumed_tail长度和unused_data长度。eof之前,输出受max_length限制时可能需要再次处理未消费输入。到达eof后,应按格式规则解释尾部,不能把两个属性不加区分地交回同一对象。
2. 停止重放已经结束的对象
zlib封装的夹具包含1024个明文字节和五字节后缀extra。每次最多输出100字节,仅在not d.eof and d.unconsumed_tail时继续,得到完整准确的载荷。在本次安装版本中,结束时两个尾部属性都包含extra,不能把它们当成两段独立数据拼接。
我们随后明确只多调用三次,每次输入保留的尾部。三次输出都为零,unused_data从5增至20字节,unconsumed_tail仍为5字节;没有运行无限循环。上游问题已给出eof保护条件,并已关闭且关联修复。应测试自己的运行版本,不能把它说成所有当前Python版本尚未修复的新缺陷。
3. 一个gzip成员结束,只表示到达交接点
49字节gzip夹具由25字节的alpha成员和24字节的beta成员拼接。一个zlib.decompressobj(31)只返回alpha,eof=True,unused_data中保留完整24字节第二成员。这次未限制输出的调用中,unconsumed_tail为空。
将unused_data交给新对象,得到beta,随后eof=True且没有后缀。对完整的小夹具调用gzip.decompress直接得到alphabeta,与文档的多成员支持一致。这些API和社区的新对象方法不是新发现。处理大型或外部输入时,应选满足规则和限制的维护中读取器,不能把本次内存比较当作正式接入设计。
4. 空输出和预期明文都不是完成证明
有效的空gzip成员不输出明文,但会到达eof并保留下一成员。若循环把空输出块当作全部输入结束,就可能漏掉后续有效数据。应跟踪成员完成状态和待处理输入,而非只看最近输出是否非空。
从第二成员尾部删去四字节后,两个对象仍输出alphabeta,全部9个预期字节都出现,但第二对象eof=False。调用flush()也没有补足缺失尾部的完成状态。另一个对照改坏第二成员CRC,首对象仍返回alpha;直到第二对象处理第二成员才出现错误。
5. 明确允许哪些成员和尾部数据
协议可以只允许一个完整成员,并拒绝全部额外字节;也可以允许拼接成员,要求每个成员使用新的解码对象。不能默默丢弃unused_data、假定任何后缀都是有效新成员,或把它与可能重叠的unconsumed_tail相加。我们的garbage后缀被当成新gzip成员处理时失败。
允许多成员时,输出预算应跨成员累计,并按实际读取器设置输入、成员数、时间和上游缓冲控制。max_length限制单次调用,零表示不限制输出;flush(length)是初始缓冲大小,不是上限。实验中max_length=0产生1024字节,max_length=1产生一字节。没有测量进程内存、CPU限制或网络客户端。
6. 只提交完成检查能够支持的结果
诊断回归测试应使用小型已知夹具:单个有效成员、两个有效成员、空首成员、截断的第二成员尾部、错误的第二成员CRC,以及非成员后缀。核对准确输出、每个获准成员的完成状态和最终尾部处理规则;保留失败,不要把有用前缀作为完整结果发布。
全部28项断言在所述版本通过,报告保留版本、平台、脚本SHA及实际状态。本次没有实现通用流式读取器、运行发布修复版本,也没有证明HTTP完整性、来源真实性或资源使用安全。成员之间正确交接只是必要条件,应用仍须定义整个允许输入何时算完成。
