Як я можу читати з саморозпаковується (exe) zip-файл WinZip в Java?

Чи існує існуючий метод, чи мені доведеться вручну проаналізувати та пропустити блок exe, перш ніж передавати дані в ZipInputStream?

7

4 Відповіді

Переглянувши формат файлу EXE та Формат ZIP-файлу та тестування різних варіантів. Найпростіше це просто ігнорувати будь-яку преамбулу до першого заголовка місцевого файлу zip.

Zip file layout

Zip local file header

Я написав фільтр вхідного потоку, щоб обійти преамбулу, і він ідеально працює:

ZipInputStream zis = new ZipInputStream(
    new WinZipInputStream(
    new FileInputStream("test.exe")));
while ((ze = zis.getNextEntry()) != null) {
    . . .
    zis.closeEntry();
}
zis.close();

WinZipInputStream.java

import java.io.FilterInputStream;
import java.io.InputStream;
import java.io.IOException;

public class WinZipInputStream extends FilterInputStream {
    public static final byte[] ZIP_LOCAL = { 0x50, 0x4b, 0x03, 0x04 };
    protected int ip;
    protected int op;

    public WinZipInputStream(InputStream is) {
        super(is);
    }

    public int read() throws IOException {
        while(ip < ZIP_LOCAL.length) {
            int c = super.read();
            if (c == ZIP_LOCAL[ip]) {
                ip++;
            }
            else ip = 0;
        }

        if (op < ZIP_LOCAL.length)
            return ZIP_LOCAL[op++];
        else
            return super.read();
    }

    public int read(byte[] b, int off, int len) throws IOException {
        if (op == ZIP_LOCAL.length) return super.read(b, off, len);
        int l = 0;
        while (l < Math.min(len, ZIP_LOCAL.length)) {
            b[l++] = (byte)read();
        }
        return l;
    }
}
12
додано
Дуже дякую, це мені дуже допомогло.
додано Автор Nagaraj N, джерело

Гарна річ про ZIP-файли - це їх послідовна структура: кожен запис - це самостійна група байтів, а в кінці - це Центральний каталог каталогів , де перераховані всі записи та їхні відхилення у файлі.

Погано те, що класи java.util.zip. * ігнорують цей індекс і просто починають читати в файл і сподіваються, що перший запис буде блокуванням локального заголовка файлу , що не стосується саморозпаковуються архівів ZIP (вони починаються з частини EXE).

Кілька років тому я написав спеціальний синтаксичний аналізатор ZIP для вилучення окремих записів ZIP (LFH + data), які покладалися на CDI, щоб знайти ці записи в файлі. Я просто перевірив, і він може фактично перерахувати записи саморозпаючого ZIP архіву без додаткового викривлення та давати вам компенсації - так що ви могли б:

  1. use that code to find the first LFH after the EXE part, and copy everything after that offset to a different File, then feed that new File to java.util.zip.ZipFile:

    Edit: Just skipping the EXE part doesn't seem to work, ZipFile still won't read it and my native ZIP program complains that the new ZIP file is damaged and exactly the number of bytes I skipped are given as "missing" (so it actually reads the CDI). I guess some headers would need to be rewritten, so the second approach given below looks more promising -- or

  2. use that code for the full ZIP extraction (it's similar to java.util.zip); this would require some additional plumbing because the code originally wasn't intended as replacement ZIP library but had a very specific use case (differential updating of ZIP files over HTTP)

Код розміщений на SourceForge ( сторінка проекту , веб-сайт ) і ліцензовано відповідно до Apache License 2.0, отже комерційне використання - AFAIK є комерційна гра, яка використовує її як оновлювач для своїх ігрових активів.

The interesting parts to get the offsets from a ZIP file are in Indexer.parseZipFile which returns a LinkedHashMap (so the first map entry has the lowest offset in the file). Here's the code I used to list the entries of a self-extracting ZIP archive (created with the WinZIP SE creator with Wine on Ubuntu from an acra release file):

public static void main(String[] args) throws Exception {
    File archive = new File("/home/phil/downloads", "acra-4.2.3.exe");
    Map resources = parseZipFile(archive);
    for (Entry resource : resources.entrySet()) {
        System.out.println(resource.getKey() + ": " + resource.getValue());
    }
}

Ви, ймовірно, можете вирвати більшу частину коду, крім класу Indexer та пакета zip , що містить всі класи розбору заголовків.

7
додано
Дякую за інформацію, яка мені на правильному шляху. Я закінчив написання простим вхідним фільтром, щоб ігнорувати щось до першого локального блоку заголовка.
додано Автор jamesallman, джерело

TrueZip найкраще працює у цьому випадку. (Принаймні у моєму випадку)

Самідвідний ZIP-файл має такий формат: code1 header1 file1 (у той час як звичайний ZIP-файл має формат header1 file1) ... Код розповідає про те, як витягнути zip

Хоча програма для вилучення Truezip скаржиться на додаткові байти і викидає виняток

Ось код

 private void Extract(String src, String dst, String incPath) {
    TFile srcFile = new TFile(src, incPath);
    TFile dstFile = new TFile(dst);
    try {
        TFile.cp_rp(srcFile, dstFile, TArchiveDetector.NULL);
        } 
    catch (IOException e) {
       //Handle Exception
        }
}

Ви можете викликати цей метод, як Extract (новий рядок ("C: \ 2006Production.exe"), новий String ("c: \"), "");

Файл витягується з диска c ... Ви можете виконувати власну операцію у своєму файлі. Я сподіваюсь, це допоможе.

Дякую.

1
додано
Чому ви нічого не робите, за винятком? І чому повернути логічний, коли це може бути тільки правдою?
додано Автор Robin Salih, джерело

У деяких саморозпаковуються ZIP-файлах є фальшивими марками локального заголовка файлів . Я думаю, що найкраще сканувати файл назад, щоб знайти запис End Of Central Directory . Запис EOCD містить зсув Центрального каталогу , а CD містить зміщення першого локального заголовка файлу . Якщо ви почнете читання з першого байта локального заголовка файлу ZipInputStream працює нормально.

Очевидно, що код нижче не є найшвидшим рішенням. Якщо ви збираєтеся обробляти великі файли, вам слід застосувати якісь буферизацію або використовувати файли, накладені на пам'ять.

import org.apache.commons.io.EndianUtils;
...

public class ZipHandler {
    private static final byte[] EOCD_MARKER = { 0x06, 0x05, 0x4b, 0x50 };

    public InputStream openExecutableZipFile(Path zipFilePath) throws IOException {
        try (RandomAccessFile raf = new RandomAccessFile(zipFilePath.toFile(), "r")) {
            long position = raf.length() - 1;
            int markerIndex = 0;
            byte[] buffer = new byte[4];
            while (position > EOCD_MARKER.length) {
                raf.seek(position);
                raf.read(buffer, 0 ,1);
                if (buffer[0] == EOCD_MARKER[markerIndex]) {
                    markerIndex++;
                } else {
                    markerIndex = 0;
                }
                if (markerIndex == EOCD_MARKER.length) {
                    raf.skipBytes(15);
                    raf.read(buffer, 0, 4);
                    int centralDirectoryOffset = EndianUtils.readSwappedInteger(buffer, 0);
                    raf.seek(centralDirectoryOffset);
                    raf.skipBytes(42);
                    raf.read(buffer, 0, 4);
                    int localFileHeaderOffset = EndianUtils.readSwappedInteger(buffer, 0);
                    return new SkippingInputStream(Files.newInputStream(zipFilePath), localFileHeaderOffset);
                }
                position--;
            }
            throw new IOException("No EOCD marker found");
        }
    }
}

public class SkippingInputStream extends FilterInputStream {
    private int bytesToSkip;
    private int bytesAlreadySkipped;

    public SkippingInputStream(InputStream inputStream, int bytesToSkip) {
        super(inputStream);
        this.bytesToSkip = bytesToSkip;
        this.bytesAlreadySkipped = 0;
    }

    @Override
    public int read() throws IOException {
        while (bytesAlreadySkipped < bytesToSkip) {
            int c = super.read();
            if (c == -1) {
                return -1;
            }
            bytesAlreadySkipped++;
        }
        return super.read();
    }

    @Override
    public int read(byte[] b, int off, int len) throws IOException {
        if (bytesAlreadySkipped == bytesToSkip) {
            return super.read(b, off, len);
        }
        int count = 0;
        while (count < len) {
            int c = read();
            if (c == -1) {
                break;
            }
            b[count++] = (byte) c;
        }
        return count;
    }
}
1
додано
ІТ КПІ - Java
ІТ КПІ - Java
436 учасників