ZipUtils.java revision dd910c5945272e9820dfd9d7798ba32aa7dfc73f
1/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.signapk;
18
19import java.nio.ByteBuffer;
20import java.nio.ByteOrder;
21
22/**
23 * Assorted ZIP format helpers.
24 *
25 * <p>NOTE: Most helper methods operating on {@code ByteBuffer} instances expect that the byte
26 * order of these buffers is little-endian.
27 */
28public abstract class ZipUtils {
29    private ZipUtils() {}
30
31    private static final int ZIP_EOCD_REC_MIN_SIZE = 22;
32    private static final int ZIP_EOCD_REC_SIG = 0x06054b50;
33    private static final int ZIP_EOCD_CENTRAL_DIR_SIZE_FIELD_OFFSET = 12;
34    private static final int ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_OFFSET = 16;
35    private static final int ZIP_EOCD_COMMENT_LENGTH_FIELD_OFFSET = 20;
36
37    private static final int ZIP64_EOCD_LOCATOR_SIZE = 20;
38    private static final int ZIP64_EOCD_LOCATOR_SIG = 0x07064b50;
39
40    private static final int UINT32_MAX_VALUE = 0xffff;
41
42    /**
43     * Returns the position at which ZIP End of Central Directory record starts in the provided
44     * buffer or {@code -1} if the record is not present.
45     *
46     * <p>NOTE: Byte order of {@code zipContents} must be little-endian.
47     */
48    public static int findZipEndOfCentralDirectoryRecord(ByteBuffer zipContents) {
49        assertByteOrderLittleEndian(zipContents);
50
51        // ZIP End of Central Directory (EOCD) record is located at the very end of the ZIP archive.
52        // The record can be identified by its 4-byte signature/magic which is located at the very
53        // beginning of the record. A complication is that the record is variable-length because of
54        // the comment field.
55        // The algorithm for locating the ZIP EOCD record is as follows. We search backwards from
56        // end of the buffer for the EOCD record signature. Whenever we find a signature, we check
57        // the candidate record's comment length is such that the remainder of the record takes up
58        // exactly the remaining bytes in the buffer. The search is bounded because the maximum
59        // size of the comment field is 65535 bytes because the field is an unsigned 32-bit number.
60
61        int archiveSize = zipContents.capacity();
62        if (archiveSize < ZIP_EOCD_REC_MIN_SIZE) {
63            System.out.println("File size smaller than EOCD min size");
64            return -1;
65        }
66        int maxCommentLength = Math.min(archiveSize - ZIP_EOCD_REC_MIN_SIZE, UINT32_MAX_VALUE);
67        int eocdWithEmptyCommentStartPosition = archiveSize - ZIP_EOCD_REC_MIN_SIZE;
68        for (int expectedCommentLength = 0; expectedCommentLength < maxCommentLength;
69                expectedCommentLength++) {
70            int eocdStartPos = eocdWithEmptyCommentStartPosition - expectedCommentLength;
71            if (zipContents.getInt(eocdStartPos) == ZIP_EOCD_REC_SIG) {
72                int actualCommentLength =
73                        getUnsignedInt16(
74                                zipContents, eocdStartPos + ZIP_EOCD_COMMENT_LENGTH_FIELD_OFFSET);
75                if (actualCommentLength == expectedCommentLength) {
76                    return eocdStartPos;
77                }
78            }
79        }
80
81        return -1;
82    }
83
84    /**
85     * Returns {@code true} if the provided buffer contains a ZIP64 End of Central Directory
86     * Locator.
87     *
88     * <p>NOTE: Byte order of {@code zipContents} must be little-endian.
89     */
90    public static final boolean isZip64EndOfCentralDirectoryLocatorPresent(
91            ByteBuffer zipContents, int zipEndOfCentralDirectoryPosition) {
92        assertByteOrderLittleEndian(zipContents);
93
94        // ZIP64 End of Central Directory Locator immediately precedes the ZIP End of Central
95        // Directory Record.
96
97        int locatorPosition = zipEndOfCentralDirectoryPosition - ZIP64_EOCD_LOCATOR_SIZE;
98        if (locatorPosition < 0) {
99            return false;
100        }
101
102        return zipContents.getInt(locatorPosition) == ZIP64_EOCD_LOCATOR_SIG;
103    }
104
105    /**
106     * Returns the offset of the start of the ZIP Central Directory in the archive.
107     *
108     * <p>NOTE: Byte order of {@code zipEndOfCentralDirectory} must be little-endian.
109     */
110    public static long getZipEocdCentralDirectoryOffset(ByteBuffer zipEndOfCentralDirectory) {
111        assertByteOrderLittleEndian(zipEndOfCentralDirectory);
112        return getUnsignedInt32(
113                zipEndOfCentralDirectory,
114                zipEndOfCentralDirectory.position() + ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_OFFSET);
115    }
116
117    /**
118     * Sets the offset of the start of the ZIP Central Directory in the archive.
119     *
120     * <p>NOTE: Byte order of {@code zipEndOfCentralDirectory} must be little-endian.
121     */
122    public static void setZipEocdCentralDirectoryOffset(
123            ByteBuffer zipEndOfCentralDirectory, long offset) {
124        assertByteOrderLittleEndian(zipEndOfCentralDirectory);
125        setUnsignedInt32(
126                zipEndOfCentralDirectory,
127                zipEndOfCentralDirectory.position() + ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_OFFSET,
128                offset);
129    }
130
131    /**
132     * Returns the size (in bytes) of the ZIP Central Directory.
133     *
134     * <p>NOTE: Byte order of {@code zipEndOfCentralDirectory} must be little-endian.
135     */
136    public static long getZipEocdCentralDirectorySizeBytes(ByteBuffer zipEndOfCentralDirectory) {
137        assertByteOrderLittleEndian(zipEndOfCentralDirectory);
138        return getUnsignedInt32(
139                zipEndOfCentralDirectory,
140                zipEndOfCentralDirectory.position() + ZIP_EOCD_CENTRAL_DIR_SIZE_FIELD_OFFSET);
141    }
142
143    private static void assertByteOrderLittleEndian(ByteBuffer buffer) {
144        if (buffer.order() != ByteOrder.LITTLE_ENDIAN) {
145            throw new IllegalArgumentException("ByteBuffer byte order must be little endian");
146        }
147    }
148
149    private static int getUnsignedInt16(ByteBuffer buffer, int offset) {
150        return buffer.getShort(offset) & 0xffff;
151    }
152
153    private static long getUnsignedInt32(ByteBuffer buffer, int offset) {
154        return buffer.getInt(offset) & 0xffffffffL;
155    }
156
157    private static void setUnsignedInt32(ByteBuffer buffer, int offset, long value) {
158        if ((value < 0) || (value > 0xffffffffL)) {
159            throw new IllegalArgumentException("uint32 value of out range: " + value);
160        }
161        buffer.putInt(buffer.position() + offset, (int) value);
162    }
163}
164