• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.signapk;
18 
19 import java.nio.ByteBuffer;
20 import 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  */
28 public abstract class ZipUtils {
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      */
findZipEndOfCentralDirectoryRecord(ByteBuffer zipContents)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      */
isZip64EndOfCentralDirectoryLocatorPresent( ByteBuffer zipContents, int zipEndOfCentralDirectoryPosition)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      */
getZipEocdCentralDirectoryOffset(ByteBuffer zipEndOfCentralDirectory)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      */
setZipEocdCentralDirectoryOffset( ByteBuffer zipEndOfCentralDirectory, long offset)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      */
getZipEocdCentralDirectorySizeBytes(ByteBuffer zipEndOfCentralDirectory)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 
assertByteOrderLittleEndian(ByteBuffer buffer)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 
getUnsignedInt16(ByteBuffer buffer, int offset)148     private static int getUnsignedInt16(ByteBuffer buffer, int offset) {
149         return buffer.getShort(offset) & 0xffff;
150     }
151 
getUnsignedInt32(ByteBuffer buffer, int offset)152     private static long getUnsignedInt32(ByteBuffer buffer, int offset) {
153         return buffer.getInt(offset) & 0xffffffffL;
154     }
155 
setUnsignedInt32(ByteBuffer buffer, int offset, long value)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