• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2021-2022 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 package com.ohos.hapsigntool.zip;
17 
18 import com.ohos.hapsigntool.entity.Pair;
19 import com.ohos.hapsigntool.error.HapFormatException;
20 import com.ohos.hapsigntool.error.SignToolErrMsg;
21 
22 import java.io.IOException;
23 import java.nio.ByteBuffer;
24 import java.nio.ByteOrder;
25 
26 /**
27  * Utils functions of zip-files.
28  *
29  * @since 2021/12/22
30  */
31 public class ZipUtils {
32     private static final int ZIP_EOCD_SEGMENT_MIN_SIZE = 22;
33 
34     private static final int ZIP_EOCD_SEGMENT_FLAG = 0x06054b50;
35 
36     private static final int ZIP_CENTRAL_DIR_COUNT_OFFSET_IN_EOCD = 10;
37 
38     private static final int ZIP_CENTRAL_DIR_SIZE_OFFSET_IN_EOCD = 12;
39 
40     private static final int ZIP_CENTRAL_DIR_OFFSET_IN_EOCD = 16;
41 
42     private static final int ZIP_EOCD_COMMENT_LENGTH_OFFSET = 20;
43 
44     private static final int ZIP64_EOCD_LOCATOR_SIZE = 20;
45 
46     private static final int ZIP64_EOCD_LOCATOR_SIG = 0x07064b50;
47 
48     private static final int UINT16_MAX_VALUE = 0xffff;
49 
50     private static final long UINT32_MAX_VALUE = 0xffffffffL;
51 
52     private static final int ZIP_DATA_SIZE = 4;
53 
54     /**
55      * Constructor of Method
56      */
ZipUtils()57     private ZipUtils() {
58     }
59 
60     /**
61      * This function find Eocd by searching Eocd flag from input buffer(searchBuffer) and
62      * making sure the comment length is equal to the expected value
63      *
64      * @param searchBuffer data buffer used to search EOCD.
65      * @return offset from buffer start point.
66      */
findEocdInSearchBuffer(ByteBuffer searchBuffer)67     public static int findEocdInSearchBuffer(ByteBuffer searchBuffer) {
68         checkBufferIsLittleEndian(searchBuffer);
69         /*
70          * Eocd format:
71          * 4-bytes: End of central directory flag
72          * 2-bytes: Number of this disk
73          * 2-bytes: Number of the disk with the start of central directory
74          * 2-bytes: Total number of entries in the central directory on this disk
75          * 2-bytes: Total number of entries in the central directory
76          * 4-bytes: Size of central directory
77          * 4-bytes: offset of central directory in zip file
78          * 2-bytes: ZIP file comment length, the value n is in the range of [0, 65535]
79          * n-bytes: ZIP Comment block data
80          */
81         int searchBufferSize = searchBuffer.capacity();
82         if (searchBufferSize < ZIP_EOCD_SEGMENT_MIN_SIZE) {
83             return -1;
84         }
85 
86         int currentOffset = searchBufferSize - ZIP_EOCD_SEGMENT_MIN_SIZE;
87         while (currentOffset >= 0) {
88             if (searchBuffer.getInt(currentOffset) == ZIP_EOCD_SEGMENT_FLAG) {
89                 int commentLength = getUInt16FromBuffer(searchBuffer, currentOffset + ZIP_EOCD_COMMENT_LENGTH_OFFSET);
90                 int expectedCommentLength = searchBufferSize - ZIP_EOCD_SEGMENT_MIN_SIZE - currentOffset;
91                 if (commentLength == expectedCommentLength) {
92                     return currentOffset;
93                 }
94             }
95             currentOffset--;
96         }
97         return -1;
98     }
99 
100     /**
101      * Check whether the zip is zip64 by finding ZIP64 End of Central Directory Locator.
102      * ZIP64 End of Central Directory Locator immediately precedes the ZIP End of Central Directory.
103      *
104      * @param zip object of RandomAccessFile for zip-file.
105      * @param zipEocdOffset offset of the ZIP EOCD in the file.
106      * @return true, if ZIP64 End of Central Directory Locator is present.
107      * @throws IOException read file error.
108      */
checkZip64EoCDLocatorIsPresent(ZipDataInput zip, long zipEocdOffset)109     public static boolean checkZip64EoCDLocatorIsPresent(ZipDataInput zip, long zipEocdOffset) throws IOException {
110         long locatorPos = zipEocdOffset - ZIP64_EOCD_LOCATOR_SIZE;
111         if (locatorPos < 0) {
112             return false;
113         }
114         ByteBuffer byteBuffer = zip.createByteBuffer(locatorPos, ZIP_DATA_SIZE);
115         byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
116         return byteBuffer.getInt() == ZIP64_EOCD_LOCATOR_SIG;
117     }
118 
119     /**
120      * Get offset value of Central Directory from End of Central Directory Record.
121      *
122      * @param eocd buffer of End of Central Directory Record
123      * @return offset value of Central Directory.
124      */
getCentralDirectoryOffset(ByteBuffer eocd)125     public static long getCentralDirectoryOffset(ByteBuffer eocd) {
126         checkBufferIsLittleEndian(eocd);
127         return getUInt32FromBuffer(eocd, eocd.position() + ZIP_CENTRAL_DIR_OFFSET_IN_EOCD);
128     }
129 
130     /**
131      * set offset value of Central Directory to End of Central Directory Record.
132      *
133      * @param eocd buffer of End of Central Directory Record.
134      * @param offset offset value of Central Directory.
135      */
setCentralDirectoryOffset(ByteBuffer eocd, long offset)136     public static void setCentralDirectoryOffset(ByteBuffer eocd, long offset) {
137         checkBufferIsLittleEndian(eocd);
138         setUInt32ToBuffer(eocd, eocd.position() + ZIP_CENTRAL_DIR_OFFSET_IN_EOCD, offset);
139     }
140 
141     /**
142      * Get size of Central Directory from End of Central Directory Record.
143      *
144      * @param eocd buffer of End of Central Directory Record.
145      * @return size of Central Directory.
146      */
getCentralDirectorySize(ByteBuffer eocd)147     public static long getCentralDirectorySize(ByteBuffer eocd) {
148         checkBufferIsLittleEndian(eocd);
149         return getUInt32FromBuffer(eocd, eocd.position() + ZIP_CENTRAL_DIR_SIZE_OFFSET_IN_EOCD);
150     }
151 
152     /**
153      * Get total count of Central Directory from End of Central Directory Record.
154      *
155      * @param eocd buffer of End of Central Directory Record.
156      * @return size of Central Directory.
157      */
getCentralDirectoryCount(ByteBuffer eocd)158     public static int getCentralDirectoryCount(ByteBuffer eocd) {
159         checkBufferIsLittleEndian(eocd);
160         return getUInt16FromBuffer(eocd, eocd.position() + ZIP_CENTRAL_DIR_COUNT_OFFSET_IN_EOCD);
161     }
162 
checkBufferIsLittleEndian(ByteBuffer buffer)163     private static void checkBufferIsLittleEndian(ByteBuffer buffer) {
164         if (buffer.order() == ByteOrder.LITTLE_ENDIAN) {
165             return;
166         }
167         throw new IllegalArgumentException("ByteBuffer is not little endian");
168     }
169 
getUInt16FromBuffer(ByteBuffer buffer, int offset)170     static int getUInt16FromBuffer(ByteBuffer buffer, int offset) {
171         return buffer.getShort(offset) & 0xffff;
172     }
173 
getUInt32FromBuffer(ByteBuffer buffer, int offset)174     static long getUInt32FromBuffer(ByteBuffer buffer, int offset) {
175         return buffer.getInt(offset) & UINT32_MAX_VALUE;
176     }
177 
setUInt32ToBuffer(ByteBuffer buffer, int offset, long value)178     private static void setUInt32ToBuffer(ByteBuffer buffer, int offset, long value) {
179         if ((value < 0) || (value > UINT32_MAX_VALUE)) {
180             throw new IllegalArgumentException("uint32 value of out range: " + value);
181         }
182         buffer.putInt(buffer.position() + offset, (int) value);
183     }
184 
185     /**
186      * Find the key information for parsing the zip file.
187      *
188      * @param in zip file
189      * @return the key information for parsing the zip file.
190      * @throws IOException file operation error
191      * @throws HapFormatException hap file format error
192      */
findZipInfo(ZipDataInput in)193     public static ZipFileInfo findZipInfo(ZipDataInput in) throws IOException, HapFormatException {
194         Pair<Long, ByteBuffer> eocdOffsetAndBuffer = findEocdInHap(in);
195         if (eocdOffsetAndBuffer == null) {
196             throw new HapFormatException(SignToolErrMsg.ZIP_FORMAT_FAILED
197                     .toString("ZIP End of Central Directory not found"));
198         }
199         long eocdOffset = eocdOffsetAndBuffer.getFirst();
200         ByteBuffer eocdBuffer = eocdOffsetAndBuffer.getSecond().order(ByteOrder.LITTLE_ENDIAN);
201         long centralDirectoryStartOffset = ZipUtils.getCentralDirectoryOffset(eocdBuffer);
202         if (centralDirectoryStartOffset > eocdOffset) {
203             throw new HapFormatException(SignToolErrMsg.ZIP_FORMAT_FAILED
204                     .toString("ZIP Central Directory start offset(" + centralDirectoryStartOffset
205                 + ") larger than ZIP End of Central Directory offset(" + eocdOffset + ")"));
206         }
207         long centralDirectorySizeLong = ZipUtils.getCentralDirectorySize(eocdBuffer);
208         if (centralDirectorySizeLong > Integer.MAX_VALUE) {
209             throw new HapFormatException(SignToolErrMsg.ZIP_FORMAT_FAILED
210                     .toString("ZIP Central Directory out of range: " + centralDirectorySizeLong));
211         }
212         int centralDirectorySize = (int) centralDirectorySizeLong;
213         long centralDirectoryEndOffset = centralDirectoryStartOffset + centralDirectorySizeLong;
214         if (centralDirectoryEndOffset != eocdOffset) {
215             throw new HapFormatException(SignToolErrMsg.ZIP_FORMAT_FAILED
216                     .toString("ZIP Central Directory end offset(" + centralDirectoryEndOffset + ") "
217                 + " different from ZIP End of Central Directory offset(" + eocdOffset + ")"));
218         }
219         int centralDirectoryCount = ZipUtils.getCentralDirectoryCount(eocdBuffer);
220         return new ZipFileInfo(centralDirectoryStartOffset, centralDirectorySize, centralDirectoryCount, eocdOffset,
221                 eocdBuffer);
222     }
223 
findEocdInHap(ZipDataInput in)224     private static Pair<Long, ByteBuffer> findEocdInHap(ZipDataInput in) throws IOException {
225         Pair<Long, ByteBuffer> eocdInHap = findEocdInHap(in, 0);
226         if (eocdInHap != null) {
227             return eocdInHap;
228         }
229         return findEocdInHap(in, UINT16_MAX_VALUE);
230     }
231 
findEocdInHap(ZipDataInput zip, int maxCommentSize)232     private static Pair<Long, ByteBuffer> findEocdInHap(ZipDataInput zip, int maxCommentSize) throws IOException {
233         if ((maxCommentSize < 0) || (maxCommentSize > UINT16_MAX_VALUE)) {
234             throw new IllegalArgumentException("maxCommentSize: " + maxCommentSize);
235         }
236         long fileSize = zip.size();
237         if (fileSize < ZIP_EOCD_SEGMENT_MIN_SIZE) {
238             throw new IllegalArgumentException("file length " + fileSize + " is too smaller");
239         }
240         int finalMaxCommentSize = (int) Math.min(maxCommentSize, fileSize - ZIP_EOCD_SEGMENT_MIN_SIZE);
241         int searchBufferSize = finalMaxCommentSize + ZIP_EOCD_SEGMENT_MIN_SIZE;
242         long bufferOffsetInFile = fileSize - searchBufferSize;
243         ByteBuffer searchEocdBuffer = zip.createByteBuffer(bufferOffsetInFile, searchBufferSize);
244         searchEocdBuffer.order(ByteOrder.LITTLE_ENDIAN);
245         int eocdOffsetInSearchBuffer = findEocdInSearchBuffer(searchEocdBuffer);
246         if (eocdOffsetInSearchBuffer == -1) {
247             return null;
248         }
249         searchEocdBuffer.position(eocdOffsetInSearchBuffer);
250         ByteBuffer eocdBuffer = searchEocdBuffer.slice().order(ByteOrder.LITTLE_ENDIAN);
251         return Pair.create(bufferOffsetInFile + eocdOffsetInSearchBuffer, eocdBuffer);
252     }
253 }