簡體   English   中英

Stream Zip 文件:java.util.zip.ZipException:條目大小無效(預期為 0 但得到 419 字節)

[英]Stream Zip files: java.util.zip.ZipException: invalid entry size (expected 0 but got 419 bytes)

我有一個 web 服務處理 ZIP 文件的內容,我從 a.network 源和 stream 收到動態返回 a.network 目標。 這對我大約 60% 的測試文件非常有用,但其中 40% 無法處理,因為zipEntry.getSize()返回-1作為所有 zip 條目的文件大小。

下面您可以看到兩個 java 測試將內容從源 zip 流式傳輸到目標 zip。第一個接受任何InputStream作為源(這是我需要的,因為我直接從 .network 獲取數據)並且無法處理 zip 條目大小未知 ( -1 )。

第二個測試知道如何處理未知 ( -1 ) 大小的條目,但只能處理源自本地文件的流(這不是我需要的 - 它只是為了證明,有問題的 zip 文件沒有損壞) .

網上有很多處理本地 zip 文件的示例 - 但很少有關於處理 .network 流的示例,這就是為什么我很難找到解決方案的原因。

第一個例子拋出的錯誤是Stream Zip files: java.util.zip.ZipException: invalid entry size (expected 0 but got 419 bytes)

這是我的代碼:

package de.ftk.threemf.mesh;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.junit.jupiter.api.Test;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Date;
import java.util.Enumeration;
import java.util.zip.*;

@Slf4j
public class ZipStreamTests {

    @Test
    public void generalizedStreamZipTest() throws IOException {
        Path path = Path.of("testdata/brokenzip/trex.3mf");
        InputStream in = Files.newInputStream(path);
        OutputStream out = Files.newOutputStream(Path.of("testoutput/ziptest.3mf"));

        ZipInputStream zipInputStream = new ZipInputStream(in);
        ZipEntry zipEntry;

        CheckedOutputStream checkedOutputStream = new CheckedOutputStream(out, new Adler32());
        ZipOutputStream zipOutputStream = new ZipOutputStream(checkedOutputStream);

        while ((zipEntry = zipInputStream.getNextEntry()) != null) {
            log.info("zip file contains: {} modified on {}", zipEntry.getName(), new Date(zipEntry.getTime()));
            zipOutputStream.putNextEntry(zipEntry);
            log.info("expecting " + zipEntry.getSize() + " bytes");
            IOUtils.copy(zipInputStream, zipOutputStream);
            zipOutputStream.closeEntry();
            zipInputStream.closeEntry();
        }
        zipInputStream.close();
        zipOutputStream.finish();
        zipOutputStream.close();
        in.close();
        out.close();
    }

    @Test
    public void fileStreamZipTest() throws IOException {
        ZipFile zipFile = new ZipFile("testdata/brokenzip/trex.3mf");
        final ZipOutputStream zos = new ZipOutputStream(new FileOutputStream("testoutput/ziptest.3mf"));
        for (Enumeration<? extends ZipEntry> e = zipFile.entries(); e.hasMoreElements(); ) {
            ZipEntry entryIn = e.nextElement();
            log.info("zip file contains: {} modified on {}", entryIn.getName(), new Date(entryIn.getTime()));
            ZipEntry zipEntry = new ZipEntry(entryIn.getName());
            log.info("expecting " + zipEntry.getSize() + " bytes");
            zos.putNextEntry(zipEntry);
            InputStream is = zipFile.getInputStream(entryIn);
            byte[] buf = new byte[1024];
            int len;
            while ((len = (is.read(buf))) > 0) {
                zos.write(buf);
            }
            zos.closeEntry();
        }
        zos.close();
    }

}

提示: 3MF文件是一個包含 3D 模型的ZIP文件。

這與 ZIP64 子格式https://www.ibm.com/support/pages/zip-file-unreadable-cause-javautilzipzipexception-invalid-entry-size有關

較新的 java7 和 java8 版本已修復此問題 - jdk-1.8.0_91 不正常,openjdk-1.8.0.212.b04 正常。

即使是最近的 jdk-1.8.0_341, ZipInputStream中似乎仍然存在與 ZIP64相關的錯誤。

它在這些條件下觸發:

  • 文件大小和壓縮大小低於 4 GiB
  • 本地文件 header 是 ZIP64 編碼的
  • 實際文件大小包含在尾隨數據描述符中

ZipInputStream將數據描述符解釋為 32 位,盡管本地文件 header 是 ZIP64,因此讀取文件大小不正確。 罪魁禍首在readEnd()方法中:

 if ((flag & 8) == 8) {
    /* "Data Descriptor" present */
    if (inf.getBytesWritten() > ZIP64_MAGICVAL ||
        inf.getBytesRead() > ZIP64_MAGICVAL) {
        // ZIP64 format
        ...

錯誤修復應如下所示:

 if ((flag & 8) == 8) {
    /* "Data Descriptor" present */
    if (/* file header was ZIP64*/) {
        // ZIP64 format
        ...

只有在讀取所有文件數據並且ZipInputStream嘗試關閉並驗證ZipEntry大小和 CRC 時才會拋出異常。 因此 stream 可以使用反射手動推進以從錯誤中恢復。

ZipInputStream zipStream = ...;
zipStream.getNextEntry();
try {
    // Read file data
    zipStream.read(...);
} catch (ZipException e) {
    
    // Recover from error condition
    if (e.getMessage().startsWith("invalid entry size (expected 0 ")) {
        // Grab pushback stream
        Field inF = FilterInputStream.class.getDeclaredField("in"); inF.setAccessible(true);
        PushbackInputStream in = (PushbackInputStream) inF.get(zipStream);
        
        for (int i = 0; i < 8; i++) in.read(); // Read 8 extra bytes to compensate footer
        
        // Close the entry manually
        Field f = ZipInputStream.class.getDeclaredField("entryEOF");
        f.setAccessible(true);
        f.set(zipStream, true);
        f = ZipInputStream.class.getDeclaredField("entry");
        f.setAccessible(true);
        f.set(zipStream, null);
    } else {
        throw e;
    }
}
zipStream.getNextEntry(); // Continue as if exception hadn't occured

我在這個存儲庫中創建了一個完整的打包解決方案。 還有一些正在測試資源的示例 ZIP 會觸發該錯誤。

https://github.com/cjgriscom/ZipInputStreamPatch64

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM