1e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin/* 2e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin * Copyright (C) 2016 The Android Open Source Project 3e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin * 4e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin * Licensed under the Apache License, Version 2.0 (the "License"); 5e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin * you may not use this file except in compliance with the License. 6e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin * You may obtain a copy of the License at 7e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin * 8e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin * http://www.apache.org/licenses/LICENSE-2.0 9e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin * 10e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin * Unless required by applicable law or agreed to in writing, software 11e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin * distributed under the License is distributed on an "AS IS" BASIS, 12e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin * See the License for the specific language governing permissions and 14e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin * limitations under the License. 15e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin */ 16e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin 17e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubinpackage android.util.apk; 18e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin 190722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubinimport android.util.Pair; 200722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin 210722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubinimport java.io.IOException; 220722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubinimport java.io.RandomAccessFile; 23e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubinimport java.nio.ByteBuffer; 24e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubinimport java.nio.ByteOrder; 25e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin 26e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin/** 27e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin * Assorted ZIP format helpers. 28e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin * 290722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin * <p>NOTE: Most helper methods operating on {@code ByteBuffer} instances expect that the byte 30e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin * order of these buffers is little-endian. 31e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin */ 32e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubinabstract class ZipUtils { 33e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin private ZipUtils() {} 34e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin 35e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin private static final int ZIP_EOCD_REC_MIN_SIZE = 22; 36e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin private static final int ZIP_EOCD_REC_SIG = 0x06054b50; 37e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin private static final int ZIP_EOCD_CENTRAL_DIR_SIZE_FIELD_OFFSET = 12; 38e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin private static final int ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_OFFSET = 16; 39e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin private static final int ZIP_EOCD_COMMENT_LENGTH_FIELD_OFFSET = 20; 40e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin 41e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin private static final int ZIP64_EOCD_LOCATOR_SIZE = 20; 420722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin private static final int ZIP64_EOCD_LOCATOR_SIG_REVERSE_BYTE_ORDER = 0x504b0607; 43e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin 440722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin private static final int UINT16_MAX_VALUE = 0xffff; 450722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin 460722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin /** 470722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin * Returns the ZIP End of Central Directory record of the provided ZIP file. 480722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin * 490722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin * @return contents of the ZIP End of Central Directory record and the record's offset in the 500722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin * file or {@code null} if the file does not contain the record. 510722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin * 520722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin * @throws IOException if an I/O error occurs while reading the file. 530722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin */ 540722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin static Pair<ByteBuffer, Long> findZipEndOfCentralDirectoryRecord(RandomAccessFile zip) 550722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin throws IOException { 560722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin // ZIP End of Central Directory (EOCD) record is located at the very end of the ZIP archive. 570722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin // The record can be identified by its 4-byte signature/magic which is located at the very 580722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin // beginning of the record. A complication is that the record is variable-length because of 590722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin // the comment field. 600722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin // The algorithm for locating the ZIP EOCD record is as follows. We search backwards from 610722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin // end of the buffer for the EOCD record signature. Whenever we find a signature, we check 620722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin // the candidate record's comment length is such that the remainder of the record takes up 630722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin // exactly the remaining bytes in the buffer. The search is bounded because the maximum 640722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin // size of the comment field is 65535 bytes because the field is an unsigned 16-bit number. 650722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin 660722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin long fileSize = zip.length(); 670722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin if (fileSize < ZIP_EOCD_REC_MIN_SIZE) { 680722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin return null; 690722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin } 700722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin 710722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin // Optimization: 99.99% of APKs have a zero-length comment field in the EoCD record and thus 720722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin // the EoCD record offset is known in advance. Try that offset first to avoid unnecessarily 730722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin // reading more data. 740722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin Pair<ByteBuffer, Long> result = findZipEndOfCentralDirectoryRecord(zip, 0); 750722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin if (result != null) { 760722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin return result; 770722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin } 780722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin 790722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin // EoCD does not start where we expected it to. Perhaps it contains a non-empty comment 800722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin // field. Expand the search. The maximum size of the comment field in EoCD is 65535 because 810722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin // the comment length field is an unsigned 16-bit number. 820722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin return findZipEndOfCentralDirectoryRecord(zip, UINT16_MAX_VALUE); 830722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin } 840722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin 850722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin /** 860722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin * Returns the ZIP End of Central Directory record of the provided ZIP file. 870722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin * 880722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin * @param maxCommentSize maximum accepted size (in bytes) of EoCD comment field. The permitted 890722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin * value is from 0 to 65535 inclusive. The smaller the value, the faster this method 900722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin * locates the record, provided its comment field is no longer than this value. 910722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin * 920722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin * @return contents of the ZIP End of Central Directory record and the record's offset in the 930722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin * file or {@code null} if the file does not contain the record. 940722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin * 950722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin * @throws IOException if an I/O error occurs while reading the file. 960722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin */ 970722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin private static Pair<ByteBuffer, Long> findZipEndOfCentralDirectoryRecord( 980722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin RandomAccessFile zip, int maxCommentSize) throws IOException { 990722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin // ZIP End of Central Directory (EOCD) record is located at the very end of the ZIP archive. 1000722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin // The record can be identified by its 4-byte signature/magic which is located at the very 1010722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin // beginning of the record. A complication is that the record is variable-length because of 1020722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin // the comment field. 1030722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin // The algorithm for locating the ZIP EOCD record is as follows. We search backwards from 1040722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin // end of the buffer for the EOCD record signature. Whenever we find a signature, we check 1050722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin // the candidate record's comment length is such that the remainder of the record takes up 1060722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin // exactly the remaining bytes in the buffer. The search is bounded because the maximum 1070722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin // size of the comment field is 65535 bytes because the field is an unsigned 16-bit number. 1080722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin 1090722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin if ((maxCommentSize < 0) || (maxCommentSize > UINT16_MAX_VALUE)) { 1100722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin throw new IllegalArgumentException("maxCommentSize: " + maxCommentSize); 1110722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin } 1120722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin 1130722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin long fileSize = zip.length(); 1140722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin if (fileSize < ZIP_EOCD_REC_MIN_SIZE) { 1150722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin // No space for EoCD record in the file. 1160722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin return null; 1170722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin } 1180722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin // Lower maxCommentSize if the file is too small. 1190722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin maxCommentSize = (int) Math.min(maxCommentSize, fileSize - ZIP_EOCD_REC_MIN_SIZE); 1200722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin 1210722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin ByteBuffer buf = ByteBuffer.allocate(ZIP_EOCD_REC_MIN_SIZE + maxCommentSize); 1220722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin buf.order(ByteOrder.LITTLE_ENDIAN); 1230722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin long bufOffsetInFile = fileSize - buf.capacity(); 1240722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin zip.seek(bufOffsetInFile); 1250722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin zip.readFully(buf.array(), buf.arrayOffset(), buf.capacity()); 1260722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin int eocdOffsetInBuf = findZipEndOfCentralDirectoryRecord(buf); 1270722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin if (eocdOffsetInBuf == -1) { 1280722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin // No EoCD record found in the buffer 1290722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin return null; 1300722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin } 1310722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin // EoCD found 1320722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin buf.position(eocdOffsetInBuf); 1330722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin ByteBuffer eocd = buf.slice(); 1340722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin eocd.order(ByteOrder.LITTLE_ENDIAN); 1350722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin return Pair.create(eocd, bufOffsetInFile + eocdOffsetInBuf); 1360722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin } 137e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin 138e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin /** 139e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin * Returns the position at which ZIP End of Central Directory record starts in the provided 140e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin * buffer or {@code -1} if the record is not present. 141e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin * 142e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin * <p>NOTE: Byte order of {@code zipContents} must be little-endian. 143e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin */ 1440722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin private static int findZipEndOfCentralDirectoryRecord(ByteBuffer zipContents) { 145e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin assertByteOrderLittleEndian(zipContents); 146e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin 147e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin // ZIP End of Central Directory (EOCD) record is located at the very end of the ZIP archive. 148e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin // The record can be identified by its 4-byte signature/magic which is located at the very 149e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin // beginning of the record. A complication is that the record is variable-length because of 150e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin // the comment field. 151e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin // The algorithm for locating the ZIP EOCD record is as follows. We search backwards from 152e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin // end of the buffer for the EOCD record signature. Whenever we find a signature, we check 153e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin // the candidate record's comment length is such that the remainder of the record takes up 154e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin // exactly the remaining bytes in the buffer. The search is bounded because the maximum 1550722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin // size of the comment field is 65535 bytes because the field is an unsigned 16-bit number. 156e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin 157e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin int archiveSize = zipContents.capacity(); 158e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin if (archiveSize < ZIP_EOCD_REC_MIN_SIZE) { 159e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin return -1; 160e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin } 1610722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin int maxCommentLength = Math.min(archiveSize - ZIP_EOCD_REC_MIN_SIZE, UINT16_MAX_VALUE); 162e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin int eocdWithEmptyCommentStartPosition = archiveSize - ZIP_EOCD_REC_MIN_SIZE; 1639694657967c7fb62a74c187d01e1aaed1f2db7acAlex Klyubin for (int expectedCommentLength = 0; expectedCommentLength <= maxCommentLength; 164e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin expectedCommentLength++) { 165e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin int eocdStartPos = eocdWithEmptyCommentStartPosition - expectedCommentLength; 166e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin if (zipContents.getInt(eocdStartPos) == ZIP_EOCD_REC_SIG) { 167e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin int actualCommentLength = 168e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin getUnsignedInt16( 169e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin zipContents, eocdStartPos + ZIP_EOCD_COMMENT_LENGTH_FIELD_OFFSET); 170e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin if (actualCommentLength == expectedCommentLength) { 171e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin return eocdStartPos; 172e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin } 173e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin } 174e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin } 175e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin 176e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin return -1; 177e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin } 178e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin 179e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin /** 1800722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin * Returns {@code true} if the provided file contains a ZIP64 End of Central Directory 181e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin * Locator. 182e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin * 1830722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin * @param zipEndOfCentralDirectoryPosition offset of the ZIP End of Central Directory record 1840722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin * in the file. 1850722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin * 1860722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin * @throws IOException if an I/O error occurs while reading the file. 187e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin */ 188e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin public static final boolean isZip64EndOfCentralDirectoryLocatorPresent( 1890722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin RandomAccessFile zip, long zipEndOfCentralDirectoryPosition) throws IOException { 190e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin 191e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin // ZIP64 End of Central Directory Locator immediately precedes the ZIP End of Central 192e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin // Directory Record. 1930722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin long locatorPosition = zipEndOfCentralDirectoryPosition - ZIP64_EOCD_LOCATOR_SIZE; 194e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin if (locatorPosition < 0) { 195e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin return false; 196e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin } 197e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin 1980722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin zip.seek(locatorPosition); 1990722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin // RandomAccessFile.readInt assumes big-endian byte order, but ZIP format uses 2000722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin // little-endian. 2010722ffcd0699406efe21d2bd69cc8c1708fe858cAlex Klyubin return zip.readInt() == ZIP64_EOCD_LOCATOR_SIG_REVERSE_BYTE_ORDER; 202e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin } 203e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin 204e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin /** 205e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin * Returns the offset of the start of the ZIP Central Directory in the archive. 206e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin * 207e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin * <p>NOTE: Byte order of {@code zipEndOfCentralDirectory} must be little-endian. 208e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin */ 209e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin public static long getZipEocdCentralDirectoryOffset(ByteBuffer zipEndOfCentralDirectory) { 210e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin assertByteOrderLittleEndian(zipEndOfCentralDirectory); 211e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin return getUnsignedInt32( 212e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin zipEndOfCentralDirectory, 213e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin zipEndOfCentralDirectory.position() + ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_OFFSET); 214e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin } 215e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin 216e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin /** 217e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin * Sets the offset of the start of the ZIP Central Directory in the archive. 218e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin * 219e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin * <p>NOTE: Byte order of {@code zipEndOfCentralDirectory} must be little-endian. 220e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin */ 221e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin public static void setZipEocdCentralDirectoryOffset( 222e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin ByteBuffer zipEndOfCentralDirectory, long offset) { 223e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin assertByteOrderLittleEndian(zipEndOfCentralDirectory); 224e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin setUnsignedInt32( 225e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin zipEndOfCentralDirectory, 226e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin zipEndOfCentralDirectory.position() + ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_OFFSET, 227e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin offset); 228e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin } 229e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin 230e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin /** 231e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin * Returns the size (in bytes) of the ZIP Central Directory. 232e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin * 233e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin * <p>NOTE: Byte order of {@code zipEndOfCentralDirectory} must be little-endian. 234e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin */ 235e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin public static long getZipEocdCentralDirectorySizeBytes(ByteBuffer zipEndOfCentralDirectory) { 236e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin assertByteOrderLittleEndian(zipEndOfCentralDirectory); 237e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin return getUnsignedInt32( 238e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin zipEndOfCentralDirectory, 239e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin zipEndOfCentralDirectory.position() + ZIP_EOCD_CENTRAL_DIR_SIZE_FIELD_OFFSET); 240e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin } 241e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin 242e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin private static void assertByteOrderLittleEndian(ByteBuffer buffer) { 243e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin if (buffer.order() != ByteOrder.LITTLE_ENDIAN) { 244e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin throw new IllegalArgumentException("ByteBuffer byte order must be little endian"); 245e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin } 246e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin } 247e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin 248e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin private static int getUnsignedInt16(ByteBuffer buffer, int offset) { 249e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin return buffer.getShort(offset) & 0xffff; 250e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin } 251e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin 252e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin private static long getUnsignedInt32(ByteBuffer buffer, int offset) { 253e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin return buffer.getInt(offset) & 0xffffffffL; 254e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin } 255e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin 256e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin private static void setUnsignedInt32(ByteBuffer buffer, int offset, long value) { 257e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin if ((value < 0) || (value > 0xffffffffL)) { 258e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin throw new IllegalArgumentException("uint32 value of out range: " + value); 259e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin } 260e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin buffer.putInt(buffer.position() + offset, (int) value); 261e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin } 262e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin} 263