// 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 a single central directory entry. */ public class ZipCentralHeader extends ZipLocalHeader { /** Zip version of compression tool that encoded the file. */ public int versionCreated; /** Number of disk on which this file begins. (Must be 0.) */ public int idxDisk; /** Internal zip attributes. */ public int attribInternal; /** Filesystem-dependent file attributes. */ public long attribExternal; /** Start offset of associated local header structure. */ public long ofsLocalHeader; /** Optional file comment as ascii string. (Is never {@code null}) */ public byte[] comment; // Cached local header private ZipLocalHeader localHeader; public ZipCentralHeader(ByteBuffer buffer, long absOffset) throws IOException { super(absOffset, buffer.getInt() & 0xffffffffL); long headerStart = buffer.position() - 4L; if (this.signature != CENSIG) { zerror("invalid CEN header (bad signature)"); } this.versionCreated = buffer.getShort() & 0xffff; this.version = buffer.getShort() & 0xffff; if (this.version > 20) { zerror("Unsupported zip version: " + this.version); } this.flags = buffer.getShort() & 0xffff; this.compression = buffer.getShort() & 0xffff; if (this.compression != 0) { zerror("Unsupported compression method: " + this.compression); } this.mtime = ZipUtils.dosToJavaTime(buffer.getInt() & 0xffffffffL); this.atime = this.ctime = this.mtime; this.crc32 = buffer.getInt() & 0xffffffffL; this.sizeCompressed = buffer.getInt() & 0xffffffffL; this.sizeUncompressed = buffer.getInt() & 0xffffffffL; if ((this.sizeCompressed == 0xffffffffL) || (this.sizeUncompressed == 0xffffffffL)) { zerror("ZIP64 header not supported"); } short nameLength = buffer.getShort(); short extraLength = buffer.getShort(); short commentLength = buffer.getShort(); this.idxDisk = buffer.getShort() & 0xffff; this.attribInternal = buffer.getShort() & 0xffff; this.attribExternal = buffer.getInt() & 0xffffffffL; this.ofsLocalHeader = buffer.getInt() & 0xffffffffL; this.fileName = new byte[nameLength]; buffer.get(this.fileName); this.extra = new byte[extraLength]; if (extraLength > 0) { buffer.get(this.extra); } this.comment = new byte[commentLength]; if (commentLength > 0) { buffer.get(this.comment); } this.size = (int)(buffer.position() - headerStart); this.localHeader = null; } /** * Returns the absolute offset to the start of file data. * @param ch ByteChannel of the zip archive. * @return Absolute offset to data start from beginning of zip archive. */ public long getDataOffset(SeekableByteChannel ch) throws IOException { synchronized (ch) { return getLocalHeader(ch).getDataOffset(); } } @Override public boolean equals(Object o) { if (this == o) { return true; } else if (o instanceof ZipCentralHeader) { return (((ZipCentralHeader)o).ofsLocalHeader == this.ofsLocalHeader); } else { return false; } } @Override public int compareTo(ZipBaseHeader o) { if (this == o) { return 0; } else if (o instanceof ZipCentralHeader) { if (this.ofsLocalHeader < ((ZipCentralHeader)o).ofsLocalHeader) { return -1; } else if (this.ofsLocalHeader > ((ZipCentralHeader)o).ofsLocalHeader) { return 1; } else { return 0; } } else if (o instanceof ZipBaseHeader) { return super.compareTo(o); } else { throw new NullPointerException(); } } private ZipLocalHeader getLocalHeader(SeekableByteChannel ch) throws IOException { if (localHeader == null) { // reading base LOC header int locSize = LOCHDR; ByteBuffer locBuf = StreamUtils.getByteBuffer(locSize); if (ch.position(ofsLocalHeader).read(locBuf) != locSize) { zerror("read LOC header failed"); } locBuf.flip(); int nameLen = locBuf.getShort(LOCNAM) & 0xffff; int extraLen = locBuf.getShort(LOCEXT) & 0xffff; locSize += nameLen + extraLen; // reading LOC header, including filename and extra data locBuf = StreamUtils.getByteBuffer(locSize); if (ch.position(ofsLocalHeader).read(locBuf) != locSize) { zerror("read LOC header failed"); } locBuf.flip(); ZipLocalHeader locHeader = new ZipLocalHeader(locBuf, ofsLocalHeader); if (!Arrays.equals(this.fileName, locHeader.fileName)) { // just in case zerror("Filename mismatch between CEN and LOC"); } if (sizeCompressed != locHeader.sizeCompressed) { // just in case zerror("File size mismatch between CEN and LOC"); } localHeader = locHeader; } return localHeader; } }