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 }