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 }