ZipUtils.java revision 914b37f4e3430606ec9850f05c51fb112279484b
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 UINT16_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 16-bit number.
60
61        int archiveSize = zipContents.capacity();
62        if (archiveSize < ZIP_EOCD_REC_MIN_SIZE) {
63            return -1;
64        }
65        int maxCommentLength = Math.min(archiveSize - ZIP_EOCD_REC_MIN_SIZE, UINT16_MAX_VALUE);
66        int eocdWithEmptyCommentStartPosition = archiveSize - ZIP_EOCD_REC_MIN_SIZE;
67        for (int expectedCommentLength = 0; expectedCommentLength < maxCommentLength;
68                expectedCommentLength++) {
69            int eocdStartPos = eocdWithEmptyCommentStartPosition - expectedCommentLength;
70            if (zipContents.getInt(eocdStartPos) == ZIP_EOCD_REC_SIG) {
71                int actualCommentLength =
72                        getUnsignedInt16(
73                                zipContents, eocdStartPos + ZIP_EOCD_COMMENT_LENGTH_FIELD_OFFSET);
74                if (actualCommentLength == expectedCommentLength) {
75                    return eocdStartPos;
76                }
77            }
78        }
79
80        return -1;
81    }
82
83    /**
84     * Returns {@code true} if the provided buffer contains a ZIP64 End of Central Directory
85     * Locator.
86     *
87     * <p>NOTE: Byte order of {@code zipContents} must be little-endian.
88     */
89    public static final boolean isZip64EndOfCentralDirectoryLocatorPresent(
90            ByteBuffer zipContents, int zipEndOfCentralDirectoryPosition) {
91        assertByteOrderLittleEndian(zipContents);
92
93        // ZIP64 End of Central Directory Locator immediately precedes the ZIP End of Central
94        // Directory Record.
95
96        int locatorPosition = zipEndOfCentralDirectoryPosition - ZIP64_EOCD_LOCATOR_SIZE;
97        if (locatorPosition < 0) {
98            return false;
99        }
100
101        return zipContents.getInt(locatorPosition) == ZIP64_EOCD_LOCATOR_SIG;
102    }
103
104    /**
105     * Returns the offset of the start of the ZIP Central Directory in the archive.
106     *
107     * <p>NOTE: Byte order of {@code zipEndOfCentralDirectory} must be little-endian.
108     */
109    public static long getZipEocdCentralDirectoryOffset(ByteBuffer zipEndOfCentralDirectory) {
110        assertByteOrderLittleEndian(zipEndOfCentralDirectory);
111        return getUnsignedInt32(
112                zipEndOfCentralDirectory,
113                zipEndOfCentralDirectory.position() + ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_OFFSET);
114    }
115
116    /**
117     * Sets the offset of the start of the ZIP Central Directory in the archive.
118     *
119     * <p>NOTE: Byte order of {@code zipEndOfCentralDirectory} must be little-endian.
120     */
121    public static void setZipEocdCentralDirectoryOffset(
122            ByteBuffer zipEndOfCentralDirectory, long offset) {
123        assertByteOrderLittleEndian(zipEndOfCentralDirectory);
124        setUnsignedInt32(
125                zipEndOfCentralDirectory,
126                zipEndOfCentralDirectory.position() + ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_OFFSET,
127                offset);
128    }
129
130    /**
131     * Returns the size (in bytes) of the ZIP Central Directory.
132     *
133     * <p>NOTE: Byte order of {@code zipEndOfCentralDirectory} must be little-endian.
134     */
135    public static long getZipEocdCentralDirectorySizeBytes(ByteBuffer zipEndOfCentralDirectory) {
136        assertByteOrderLittleEndian(zipEndOfCentralDirectory);
137        return getUnsignedInt32(
138                zipEndOfCentralDirectory,
139                zipEndOfCentralDirectory.position() + ZIP_EOCD_CENTRAL_DIR_SIZE_FIELD_OFFSET);
140    }
141
142    private static void assertByteOrderLittleEndian(ByteBuffer buffer) {
143        if (buffer.order() != ByteOrder.LITTLE_ENDIAN) {
144            throw new IllegalArgumentException("ByteBuffer byte order must be little endian");
145        }
146    }
147
148    private static int getUnsignedInt16(ByteBuffer buffer, int offset) {
149        return buffer.getShort(offset) & 0xffff;
150    }
151
152    private static long getUnsignedInt32(ByteBuffer buffer, int offset) {
153        return buffer.getInt(offset) & 0xffffffffL;
154    }
155
156    private static void setUnsignedInt32(ByteBuffer buffer, int offset, long value) {
157        if ((value < 0) || (value > 0xffffffffL)) {
158            throw new IllegalArgumentException("uint32 value of out range: " + value);
159        }
160        buffer.putInt(buffer.position() + offset, (int) value);
161    }
162}
163