package com.topjohnwu.signing; import java.nio.ByteBuffer; import java.nio.ByteOrder; /** * Assorted ZIP format helpers. * *

NOTE: Most helper methods operating on {@code ByteBuffer} instances expect that the byte * order of these buffers is little-endian. */ public abstract class ZipUtils { private static final int ZIP_EOCD_REC_MIN_SIZE = 22; private static final int ZIP_EOCD_REC_SIG = 0x06054b50; private static final int ZIP_EOCD_CENTRAL_DIR_SIZE_FIELD_OFFSET = 12; private static final int ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_OFFSET = 16; private static final int ZIP_EOCD_COMMENT_LENGTH_FIELD_OFFSET = 20; private static final int ZIP64_EOCD_LOCATOR_SIZE = 20; private static final int ZIP64_EOCD_LOCATOR_SIG = 0x07064b50; private static final int UINT16_MAX_VALUE = 0xffff; private ZipUtils() { } /** * Returns the position at which ZIP End of Central Directory record starts in the provided * buffer or {@code -1} if the record is not present. * *

NOTE: Byte order of {@code zipContents} must be little-endian. */ public static int findZipEndOfCentralDirectoryRecord(ByteBuffer zipContents) { assertByteOrderLittleEndian(zipContents); // ZIP End of Central Directory (EOCD) record is located at the very end of the ZIP archive. // The record can be identified by its 4-byte signature/magic which is located at the very // beginning of the record. A complication is that the record is variable-length because of // the comment field. // The algorithm for locating the ZIP EOCD record is as follows. We search backwards from // end of the buffer for the EOCD record signature. Whenever we find a signature, we check // the candidate record's comment length is such that the remainder of the record takes up // exactly the remaining bytes in the buffer. The search is bounded because the maximum // size of the comment field is 65535 bytes because the field is an unsigned 16-bit number. int archiveSize = zipContents.capacity(); if (archiveSize < ZIP_EOCD_REC_MIN_SIZE) { return -1; } int maxCommentLength = Math.min(archiveSize - ZIP_EOCD_REC_MIN_SIZE, UINT16_MAX_VALUE); int eocdWithEmptyCommentStartPosition = archiveSize - ZIP_EOCD_REC_MIN_SIZE; for (int expectedCommentLength = 0; expectedCommentLength < maxCommentLength; expectedCommentLength++) { int eocdStartPos = eocdWithEmptyCommentStartPosition - expectedCommentLength; if (zipContents.getInt(eocdStartPos) == ZIP_EOCD_REC_SIG) { int actualCommentLength = getUnsignedInt16(zipContents, eocdStartPos + ZIP_EOCD_COMMENT_LENGTH_FIELD_OFFSET); if (actualCommentLength == expectedCommentLength) { return eocdStartPos; } } } return -1; } /** * Returns {@code true} if the provided buffer contains a ZIP64 End of Central Directory * Locator. * *

NOTE: Byte order of {@code zipContents} must be little-endian. */ public static boolean isZip64EndOfCentralDirectoryLocatorPresent(ByteBuffer zipContents, int zipEndOfCentralDirectoryPosition) { assertByteOrderLittleEndian(zipContents); // ZIP64 End of Central Directory Locator immediately precedes the ZIP End of Central // Directory Record. int locatorPosition = zipEndOfCentralDirectoryPosition - ZIP64_EOCD_LOCATOR_SIZE; if (locatorPosition < 0) { return false; } return zipContents.getInt(locatorPosition) == ZIP64_EOCD_LOCATOR_SIG; } /** * Returns the offset of the start of the ZIP Central Directory in the archive. * *

NOTE: Byte order of {@code zipEndOfCentralDirectory} must be little-endian. */ public static long getZipEocdCentralDirectoryOffset(ByteBuffer zipEndOfCentralDirectory) { assertByteOrderLittleEndian(zipEndOfCentralDirectory); return getUnsignedInt32(zipEndOfCentralDirectory, zipEndOfCentralDirectory.position() + ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_OFFSET); } /** * Sets the offset of the start of the ZIP Central Directory in the archive. * *

NOTE: Byte order of {@code zipEndOfCentralDirectory} must be little-endian. */ public static void setZipEocdCentralDirectoryOffset(ByteBuffer zipEndOfCentralDirectory, long offset) { assertByteOrderLittleEndian(zipEndOfCentralDirectory); setUnsignedInt32(zipEndOfCentralDirectory, zipEndOfCentralDirectory.position() + ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_OFFSET, offset); } /** * Returns the size (in bytes) of the ZIP Central Directory. * *

NOTE: Byte order of {@code zipEndOfCentralDirectory} must be little-endian. */ public static long getZipEocdCentralDirectorySizeBytes(ByteBuffer zipEndOfCentralDirectory) { assertByteOrderLittleEndian(zipEndOfCentralDirectory); return getUnsignedInt32(zipEndOfCentralDirectory, zipEndOfCentralDirectory.position() + ZIP_EOCD_CENTRAL_DIR_SIZE_FIELD_OFFSET); } private static void assertByteOrderLittleEndian(ByteBuffer buffer) { if (buffer.order() != ByteOrder.LITTLE_ENDIAN) { throw new IllegalArgumentException("ByteBuffer byte order must be little endian"); } } private static int getUnsignedInt16(ByteBuffer buffer, int offset) { return buffer.getShort(offset) & 0xffff; } private static long getUnsignedInt32(ByteBuffer buffer, int offset) { return buffer.getInt(offset) & 0xffffffffL; } private static void setUnsignedInt32(ByteBuffer buffer, int offset, long value) { if ((value < 0) || (value > 0xffffffffL)) { throw new IllegalArgumentException("uint32 value of out range: " + value); } buffer.putInt(buffer.position() + offset, (int) value); } }