// Near Infinity - An Infinity Engine Browser and Editor // Copyright (C) 2001 - 2005 Jon Olav Hauglid // See LICENSE.txt for license information package org.infinity.util.io.zip; import static org.infinity.util.io.zip.ZipConstants.*; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.SeekableByteChannel; import java.util.Arrays; import org.infinity.util.io.StreamUtils; /** * Storage class for the end of central directory entry. */ public class ZipCentralEndHeader extends ZipBaseHeader { /** Number of disk where this structure is located. */ public int idxDisk; /** Number of disk where the central directory starts. */ public int idxDiskCentral; /** Total number of central directory entries on this disk. */ public int numEntriesDisk; /** Total number of central directory entries. */ public int numEntries; /** Size of central directory in bytes. */ public long sizeCentral; /** Start offset of central directory. */ public long ofsCentral; /** Zip file comment. (Is never {@code null}) */ public byte[] comment; public ZipCentralEndHeader(ByteBuffer buffer, long absOffset) throws IOException { super(absOffset, buffer.getInt()); long headerStart = buffer.position() - 4L; this.idxDisk = buffer.getShort(); this.idxDiskCentral = buffer.getShort(); this.numEntriesDisk = buffer.getShort(); this.numEntries = buffer.getShort(); this.sizeCentral = buffer.getInt(); this.ofsCentral = buffer.getInt(); short commentLength = buffer.getShort(); this.comment = new byte[commentLength]; if (commentLength > 0) { buffer.get(this.comment); } this.size = (int)(buffer.position() - headerStart); } // Attempts to find the CEN end header in the specified file static ZipCentralEndHeader findZipEndHeader(SeekableByteChannel ch) throws IOException { if (ch == null) { throw new NullPointerException(); } if (!ch.isOpen()) { throw new IOException("Channel not open"); } // try to find CEN from back to front first byte[] buf = new byte[READBLOCKSZ]; long zipLen = ch.size(); long minHeader = (zipLen - END_MAXLEN) > 0 ? zipLen - END_MAXLEN : 0; long minPos = minHeader - (buf.length - ENDHDR); ZipCentralEndHeader end = null; for (long pos = zipLen - buf.length; (pos >= minPos) && (end == null); pos -= (buf.length - ENDHDR)) { int off = 0; if (pos < 0) { // Pretend there are some NUL bytes before start of file off = (int)-pos; Arrays.fill(buf, 0, off, (byte)0); } int len = buf.length - off; if (readFullyAt(ch, buf, off, len, pos + off) != len) { zerror("zip END header not found"); } // Now scan the block backwards for END header signature for (int i = buf.length - ENDHDR; (i >= 0) && (end == null); i--) { long sig = CENSIG(buf, i); if ((sig == ENDSIG) && (pos + i + ENDHDR + ENDCOM(buf, i) <= zipLen)) { // Found END header buf = Arrays.copyOfRange(buf, i, i + ENDHDR); end = new ZipCentralEndHeader(StreamUtils.getByteBuffer(buf), pos + i); } } } // Double check by parsing through CEN and updating END structure if necessary, // or search for END structure from front to back as fall back solution. if (end != null && end.ofsCentral >= end.offset) { zerror("invalid END header (bad central directory size)"); } long curPos = (end != null) ? end.ofsCentral : 0; long endPos = (end != null) ? end.offset : zipLen; int bufSize = Math.max(Math.max(Math.max(12, LOCHDR), CENHDR), ENDHDR); buf = new byte[bufSize]; // do a sequential search to find the first instance of a supported header signature while (curPos < endPos) { readFullyAt(ch, buf, 0, 4, curPos); long sig = CENSIG(buf, 0); if (sig == LOCSIG || sig == CENSIG || sig == ZIP64_ENDSIG || sig == ZIP64_LOCHDR || sig == ENDSIG) { break; } curPos++; } while (curPos < endPos) { long sig = LOCSIG(buf); if (sig == LOCSIG) { if (readFullyAt(ch, buf, 0, LOCHDR, curPos) != LOCHDR) { zerror("read LOC structure failed"); } long csize = LOCSIZ(buf); long size = LOCLEN(buf); if (csize == ZIP64_MINVAL || size == ZIP64_MINVAL) { zerror("ZIP64 LOC structure not supported"); } } else if (sig == CENSIG) { // central directory record if (readFullyAt(ch, buf, 0, CENHDR, curPos) != CENHDR) { zerror("read CEN tables failed"); } curPos += CENHDR + CENNAM(buf, 0) + CENEXT(buf, 0) + CENCOM(buf, 0); } else if (sig == ZIP64_ENDSIG) { // zip64 end of central directory record if (readFullyAt(ch, buf, 0, 12, curPos) != 12) { zerror("read ZIP64 end header failed"); } curPos += 12 + LL(buf, 4); } else if (sig == ZIP64_LOCSIG) { // zip64 end of central directory locator curPos += ZIP64_LOCHDR; } else if (sig == ENDSIG) { // END header if (readFullyAt(ch, buf, 0, ENDHDR, curPos) != ENDHDR) { zerror("zip END header not found"); } if (end == null) { end = new ZipCentralEndHeader(StreamUtils.getByteBuffer(buf), curPos); } break; } else { zerror("invalid header data found"); } readFullyAt(ch, buf, 0, 4, curPos); } if (end != null) { if (end.sizeCentral == ZIP64_MINVAL || end.ofsCentral == ZIP64_MINVAL || end.numEntries == ZIP64_MINVAL32) { // need to find the zip64 end byte[] loc64 = new byte[ZIP64_LOCHDR]; if (readFullyAt(ch, loc64, 0, loc64.length, end.offset - ZIP64_LOCHDR) != loc64.length) { return end; } long end64pos = ZIP64_LOCOFF(loc64); byte[] end64buf = new byte[ZIP64_ENDHDR]; if (readFullyAt(ch, end64buf, 0, end64buf.length, end64pos) != end64buf.length) { return end; } // end64 found, re-calculate everything. end.sizeCentral = ZIP64_ENDSIZ(end64buf); end.ofsCentral = ZIP64_ENDOFF(end64buf); end.numEntries = (int)ZIP64_ENDTOT(end64buf); // assume total < 2g end.offset = end64pos; } return end; } zerror("zip END header not found"); return null; // make compiler happy } }