Java'da bir Winzip kendiliğinden açılan (exe) zip dosyasından nasıl okuyabilirim?

Mevcut bir yöntem var mı, yoksa verileri elle sıkıştırıp exe bloğunu ZipInputStream'e aktarmadan önce atlamalı mıyım?

7

4 cevap

EXE dosya biçimi ve ZIP dosya formatı ve çeşitli seçeneklerin test edilmesi en kolay çözümün, ilk zip yerel dosya başlığına kadar herhangi bir girişin yok sayılmasıdır.

Zip file layout

Zip local file header

Giriş kısmını atlamak için bir giriş akışı filtresi yazdım ve mükemmel çalışıyor:

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
katma
Çok teşekkür ederim, bu bana çok yardımcı oldu.
katma yazar Nagaraj N, kaynak

ZIP dosyaları ile ilgili güzel şey, sıralı yapısıdır: Her girdi bağımsız bir bayttır ve sonunda, tüm girişleri ve bunların ofsetlerini dosyada listeleyen bir Merkezi Dizin Dizini dir.

Kötü olan şey, java.util.zip. * sınıfları bu endeksi göz ardı eder ve sadece dosyayı okumaya başlar ve ilk girdinin bir Yerel Dosya Başlığı bloğu olmasını bekler. Kendiliğinden açılan ZIP arşivleri için durum böyle değildir (bunlar EXE bölümü ile başlar).

Birkaç yıl önce, bu girişlerin dosyada nerede olduğunu bulmak için CDI'ya dayanan bireysel ZIP girişlerini (LFH + verileri) ayıklamak için özel bir ZIP ayrıştırıcısı yazdım. Ben sadece kontrol ettim ve aslında kendinden uzak bir ZIP arşivinin girişlerini daha fazla ado olmadan listeleyebilir ve size ofsetleri verebilir.

  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)

Kod, SourceForge'da ( proje sayfası , web sitesi ) ve Apache Lisansı 2.0 kapsamında lisanslıdır, dolayısıyla ticari kullanım iyi - AFAIK, oyun varlıklarını güncelleyen bir ticari oyun.

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());
    }
}

Muhtemelen kodun çoğunu, tüm başlık ayrıştırma sınıflarını içeren Dizinleyici sınıfı ve zip paketi dışında kopyalayabilirsiniz.

7
katma
Beni doğru yola koyduğu bilgiler için teşekkürler. İlk yerel başlık bloğuna kadar hiçbir şeyi görmezden gelmek için basit bir giriş filtresi yazdım.
katma yazar jamesallman, kaynak

TrueZip bu durumda en iyi çalışır. (Benim durumumda en iyisi)

Kendi kendine ayıklanan zip, aşağıdaki kod1 header1 dosya1 biçimindedir (normal bir zip, header1 dosya1 biçimindedir). Kod, zip'in nasıl çıkarılacağını anlatır.

Truezip ayıklama yardımcı programı fazladan bayttan şikayet ediyor ve bir istisna atar

İşte kod

 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 (yeni Dize ("C: \ 2006Production.exe"), yeni Dize ("c: \"), "") gibi bu yöntemi çağırabilirsiniz;

Dosya c sürücüsünde ayıklanıyor ... dosyanızda kendi işleminizi gerçekleştirebilirsiniz. Umarım bu yardımcı olur.

Teşekkürler.

1
katma
Neden istisna dışında bir şey yapmıyorsunuz? Ve neden sadece doğru olabildiğinde bir boole döndürüyor?
katma yazar Robin Salih, kaynak

Bazı kendiliğinden açılan ZIP dosyalarında sahte Yerel Dosya Üstbilgisi işaretleri var. Merkez Dizini Sonu kaydını bulmak için bir dosyayı geriye doğru taramanın en iyi yol olduğunu düşünüyorum. EOCD kaydı, bir Merkez Dizini ofsetini içerir ve CD ilk Yerel Dosya Başlığı 'nın ofsetini içerir. Bir Yerel Dosya Başlığı ZipInputStream öğesinin ilk baytından okumaya başlarsanız iyi çalışır.

Açıkçası aşağıdaki kod en hızlı çözüm değildir. Büyük dosyaları işleyecekseniz, bir çeşit arabelleğe almayı veya bellek eşlemeli dosyaları kullanmalısınız.

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
katma