ZipInputStream无法解析281TB zip炸弹 仅读取首个条目无报错原因查询
问题根因
你遇到的问题不是ZipInputStream的BUG,也不是未公开的使用限制,而是你使用的zblg.zip这类zip炸弹的特殊构造,刻意针对流式ZIP解析器的设计特征做了规避:
- 该zip文件的所有后续条目的本地文件头,都被嵌入到了第一个条目的压缩数据段内
- 你代码中会完整读取第一个条目的所有压缩数据,读完后流指针已经跳过了后续所有条目的本地文件头,直接到了文件末尾区域,自然
getNextEntry()就返回null,表现为只有1个条目。
原理说明
Java提供了两种读取ZIP文件的实现,二者逻辑差异极大:
ZipInputStream:属于流式解析器,设计目标是处理不可随机寻址的流式数据(比如网络传输的ZIP流),它不会读取ZIP文件末尾的中央目录,完全按顺序读取每个条目前面的本地文件头来识别条目,只要读完当前条目数据后找不到下一个合法的本地文件头,就认为文件已经结束。ZipFile:属于本地文件专用解析器,依赖文件的随机寻址能力,会直接跳到文件末尾读取中央目录,获取所有条目的完整列表,不受本地文件头位置的影响。
普通桌面解压工具(WinRAR、7-Zip等)均采用类似ZipFile的逻辑,优先读取中央目录,所以会显示出该zip炸弹预设的数千个条目。
解决方案
如果你需要完整读取该zip文件的所有条目,改用ZipFile实现即可,Java版本示例代码如下:
import java.io.IOException; import java.util.Enumeration; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; public class ZipSize { public static void main(String[] args) throws IOException { for (String arg : args) { System.out.printf("Opening '%s'\n", arg); long totalSize = 0; try (ZipFile zipFile = new ZipFile(arg)) { Enumeration<? extends ZipEntry> entries = zipFile.entries(); while (entries.hasMoreElements()) { ZipEntry entry = entries.nextElement(); long entrySize = entry.getSize(); totalSize += entrySize; System.out.printf("Uncompressed size of '%s' is %d bytes\n", entry.getName(), entrySize); } } System.out.printf("Total uncompressed size of '%s' is %d bytes\n", arg, totalSize); } } }
Groovy版本可以按相同逻辑改写,直接使用java.util.zip.ZipFile类即可。
优化建议
你当前的实现需要完整读取每个条目的所有数据来统计大小,碰到这类zip炸弹会产生数TB的IO开销,完全没有必要。Zip条目元数据中本身就存储了未压缩大小的字段,直接调用ZipEntry.getSize()即可获取,不需要读取实际内容,仅在ZipEntry.getSize()返回-1(元数据未存储大小)的特殊场景下,再读取条目内容统计大小即可。
内容的提问来源于stack exchange,提问作者woggioni
相关产品推荐
相关产品推荐

