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