いくつかの自己解凍型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;
}
}