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