1 /* 2 * Copyright (c) 2023-2023 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.error.CustomException; 19 import com.ohos.hapsigntool.error.ERROR; 20 import com.ohos.hapsigntool.error.SignToolErrMsg; 21 import com.ohos.hapsigntool.error.ZipException; 22 import com.ohos.hapsigntool.utils.FileUtils; 23 24 import com.ohos.hapsigntool.utils.LogUtils; 25 26 import java.io.File; 27 import java.io.FileOutputStream; 28 import java.io.IOException; 29 import java.nio.ByteBuffer; 30 import java.nio.ByteOrder; 31 import java.util.ArrayList; 32 import java.util.List; 33 import java.util.Optional; 34 35 /** 36 * resolve zip data 37 * 38 * @since 2023/12/02 39 */ 40 public class Zip { 41 private static final LogUtils LOGGER = new LogUtils(Zip.class); 42 43 /** 44 * file is uncompress file flag 45 */ 46 public static final short FILE_UNCOMPRESS_METHOD_FLAG = 0; 47 48 /** 49 * max comment length 50 */ 51 public static final int MAX_COMMENT_LENGTH = 65535; 52 53 private List<ZipEntry> zipEntries; 54 55 private long signingOffset; 56 57 private byte[] signingBlock; 58 59 private long cDOffset; 60 61 private long eOCDOffset; 62 63 private EndOfCentralDirectory endOfCentralDirectory; 64 65 private String file; 66 67 /** 68 * create Zip by file 69 * 70 * @param inputFile file 71 */ Zip(File inputFile)72 public Zip(File inputFile) { 73 try { 74 this.file = inputFile.getCanonicalPath(); 75 if (!inputFile.exists()) { 76 throw new ZipException("read zip file failed"); 77 } 78 long start = System.currentTimeMillis(); 79 // 1. get eocd data 80 endOfCentralDirectory = getZipEndOfCentralDirectory(inputFile); 81 cDOffset = endOfCentralDirectory.getOffset(); 82 long eocdEnd = System.currentTimeMillis(); 83 LOGGER.debug("getZipEndOfCentralDirectory use {} ms", eocdEnd - start); 84 // 2. use eocd's cd offset, get cd data 85 getZipCentralDirectory(inputFile); 86 long cdEnd = System.currentTimeMillis(); 87 LOGGER.debug("getZipCentralDirectory use {} ms", cdEnd - start); 88 // 3. use cd's entry offset and file size, get entry data 89 getZipEntries(inputFile); 90 ZipEntry endEntry = zipEntries.get(zipEntries.size() - 1); 91 CentralDirectory endCD = endEntry.getCentralDirectory(); 92 ZipEntryData endEntryData = endEntry.getZipEntryData(); 93 signingOffset = endCD.getOffset() + endEntryData.getLength(); 94 long entryEnd = System.currentTimeMillis(); 95 LOGGER.debug("getZipEntries use {} ms", entryEnd - start); 96 // 4. file all data - eocd - cd - entry = sign block 97 signingBlock = getSigningBlock(inputFile); 98 } catch (IOException e) { 99 CustomException.throwException(ERROR.ZIP_ERROR, SignToolErrMsg.READ_ZIP_FAILED.toString(e.getMessage())); 100 } 101 } 102 getZipEndOfCentralDirectory(File file)103 private EndOfCentralDirectory getZipEndOfCentralDirectory(File file) throws IOException { 104 if (file.length() < EndOfCentralDirectory.EOCD_LENGTH) { 105 throw new ZipException("find zip eocd failed"); 106 } 107 108 // try to read EOCD without comment 109 int eocdLength = EndOfCentralDirectory.EOCD_LENGTH; 110 eOCDOffset = file.length() - eocdLength; 111 byte[] bytes = FileUtils.readFileByOffsetAndLength(file, eOCDOffset, eocdLength); 112 Optional<EndOfCentralDirectory> eocdByBytes = EndOfCentralDirectory.getEOCDByBytes(bytes); 113 if (eocdByBytes.isPresent()) { 114 return eocdByBytes.get(); 115 } 116 117 // try to search EOCD with comment 118 long eocdMaxLength = Math.min(EndOfCentralDirectory.EOCD_LENGTH + MAX_COMMENT_LENGTH, file.length()); 119 eOCDOffset = file.length() - eocdMaxLength; 120 bytes = FileUtils.readFileByOffsetAndLength(file, eOCDOffset, eocdMaxLength); 121 for (int start = 0; start < eocdMaxLength; start++) { 122 eocdByBytes = EndOfCentralDirectory.getEOCDByBytes(bytes, start); 123 if (eocdByBytes.isPresent()) { 124 eOCDOffset += start; 125 return eocdByBytes.get(); 126 } 127 } 128 throw new ZipException("read zip failed: can not find eocd in file"); 129 } 130 getZipCentralDirectory(File file)131 private void getZipCentralDirectory(File file) throws IOException { 132 zipEntries = new ArrayList<>(endOfCentralDirectory.getCDTotal()); 133 // read full central directory bytes 134 byte[] cdBytes = FileUtils.readFileByOffsetAndLength(file, cDOffset, endOfCentralDirectory.getCDSize()); 135 if (cdBytes.length < CentralDirectory.CD_LENGTH) { 136 throw new ZipException("find zip cd failed"); 137 } 138 ByteBuffer bf = ByteBuffer.wrap(cdBytes); 139 bf.order(ByteOrder.LITTLE_ENDIAN); 140 int offset = 0; 141 // one by one format central directory 142 while (offset < cdBytes.length) { 143 CentralDirectory cd = CentralDirectory.getCentralDirectory(bf); 144 ZipEntry entry = new ZipEntry(); 145 entry.setCentralDirectory(cd); 146 zipEntries.add(entry); 147 offset += cd.getLength(); 148 } 149 if (offset + cDOffset != eOCDOffset) { 150 throw new ZipException("cd end offset not equals to eocd offset, maybe this is a zip64 file"); 151 } 152 } 153 getSigningBlock(File file)154 private byte[] getSigningBlock(File file) throws IOException { 155 long size = cDOffset - signingOffset; 156 if (size < 0) { 157 throw new ZipException("signing offset in front of entry end"); 158 } 159 if (size == 0) { 160 return new byte[0]; 161 } 162 return FileUtils.readFileByOffsetAndLength(file, signingOffset, size); 163 } 164 getZipEntries(File file)165 private void getZipEntries(File file) throws IOException { 166 // use central directory data, find entry data 167 for (ZipEntry entry : zipEntries) { 168 CentralDirectory cd = entry.getCentralDirectory(); 169 long offset = cd.getOffset(); 170 long unCompressedSize = cd.getUnCompressedSize(); 171 long compressedSize = cd.getCompressedSize(); 172 long fileSize = cd.getMethod() == FILE_UNCOMPRESS_METHOD_FLAG ? unCompressedSize : compressedSize; 173 174 ZipEntryData zipEntryData = ZipEntryData.getZipEntry(file, offset, fileSize); 175 if (cDOffset - offset < zipEntryData.getLength()) { 176 throw new ZipException("cd offset in front of entry end"); 177 } 178 entry.setZipEntryData(zipEntryData); 179 } 180 } 181 182 /** 183 * output zip to zip file 184 * 185 * @param outFile file path 186 */ toFile(String outFile)187 public void toFile(String outFile) { 188 try (FileOutputStream fos = new FileOutputStream(outFile)) { 189 for (ZipEntry entry : zipEntries) { 190 ZipEntryData zipEntryData = entry.getZipEntryData(); 191 FileUtils.writeByteToOutFile(zipEntryData.getZipEntryHeader().toBytes(), fos); 192 boolean isSuccess; 193 if (entry.getZipEntryData().getData() != null) { 194 ByteBuffer bf = ByteBuffer.wrap(entry.getZipEntryData().getData()); 195 bf.order(ByteOrder.LITTLE_ENDIAN); 196 isSuccess = FileUtils.writeByteToOutFile(bf.array(), fos); 197 } else { 198 isSuccess = FileUtils.appendWriteFileByOffsetToFile(file, fos, 199 zipEntryData.getFileOffset(), zipEntryData.getFileSize()); 200 } 201 if (!isSuccess) { 202 throw new ZipException("write zip data failed"); 203 } 204 if (zipEntryData.getDataDescriptor() != null) { 205 FileUtils.writeByteToOutFile(zipEntryData.getDataDescriptor().toBytes(), fos); 206 } 207 } 208 if (signingBlock != null) { 209 FileUtils.writeByteToOutFile(signingBlock, fos); 210 } 211 for (ZipEntry entry : zipEntries) { 212 CentralDirectory cd = entry.getCentralDirectory(); 213 FileUtils.writeByteToOutFile(cd.toBytes(), fos); 214 } 215 FileUtils.writeByteToOutFile(endOfCentralDirectory.toBytes(), fos); 216 } catch (IOException e) { 217 CustomException.throwException(ERROR.ZIP_ERROR, SignToolErrMsg.WRITE_ZIP_FAILED.toString(e.getMessage())); 218 } 219 } 220 221 /** 222 * alignment uncompress entry 223 * 224 * @param alignment int alignment 225 */ alignment(int alignment)226 public void alignment(int alignment) { 227 try { 228 sort(); 229 boolean isFirstUnRunnableFile = true; 230 for (ZipEntry entry : zipEntries) { 231 ZipEntryData zipEntryData = entry.getZipEntryData(); 232 short method = zipEntryData.getZipEntryHeader().getMethod(); 233 if (method != FILE_UNCOMPRESS_METHOD_FLAG && !isFirstUnRunnableFile) { 234 // only align uncompressed entry and the first compress entry. 235 break; 236 } 237 int alignBytes; 238 EntryType type = entry.getZipEntryData().getType(); 239 if ((type == EntryType.RUNNABLE_FILE && method == FILE_UNCOMPRESS_METHOD_FLAG) || 240 type == EntryType.BIT_MAP) { 241 // .abc and .so file align 4096 byte. 242 alignBytes = 4096; 243 } else if (isFirstUnRunnableFile) { 244 // the first file after runnable file, align 4096 byte. 245 alignBytes = 4096; 246 isFirstUnRunnableFile = false; 247 } else { 248 // normal file align 4 byte. 249 alignBytes = alignment; 250 } 251 int add = entry.alignment(alignBytes); 252 if (add > 0) { 253 resetOffset(); 254 } 255 } 256 } catch (ZipException e) { 257 CustomException.throwException(ERROR.ZIP_ERROR, SignToolErrMsg.ALIGNMENT_ZIP_FAILED 258 .toString(e.getMessage())); 259 } 260 } 261 262 /** 263 * add bit map entry 264 * 265 * @param data bitmap data 266 * @throws ZipException ZipException 267 */ addBitMap(byte[] data)268 public void addBitMap(byte[] data) throws ZipException { 269 zipEntries.removeIf(e -> e.getZipEntryData().getType() == EntryType.BIT_MAP); 270 ZipEntry entry = new ZipEntry.Builder().setMethod(FILE_UNCOMPRESS_METHOD_FLAG) 271 .setUncompressedSize(data.length) 272 .setCompressedSize(data.length) 273 .setFileName(FileUtils.BIT_MAP_FILENAME) 274 .setData(data) 275 .build(); 276 zipEntries.add(entry); 277 } 278 279 /** 280 * remove sign block 281 */ removeSignBlock()282 public void removeSignBlock() { 283 signingBlock = null; 284 resetOffset(); 285 } 286 287 /** 288 * sort uncompress entry in the front. 289 */ sort()290 private void sort() { 291 // sort uncompress file (so, abc, an) - bitmap - other uncompress file - compress file 292 zipEntries.sort((entry1, entry2) -> { 293 short entry1Method = entry1.getZipEntryData().getZipEntryHeader().getMethod(); 294 short entry2Method = entry2.getZipEntryData().getZipEntryHeader().getMethod(); 295 String entry1FileName = entry1.getZipEntryData().getZipEntryHeader().getFileName(); 296 String entry2FileName = entry2.getZipEntryData().getZipEntryHeader().getFileName(); 297 if (entry1Method == FILE_UNCOMPRESS_METHOD_FLAG && entry2Method == FILE_UNCOMPRESS_METHOD_FLAG) { 298 EntryType entry1Type = entry1.getZipEntryData().getType(); 299 EntryType entry2Type = entry2.getZipEntryData().getType(); 300 if (entry1Type != entry2Type) { 301 return entry1Type.compareTo(entry2Type); 302 } 303 return entry1FileName.compareTo(entry2FileName); 304 } else if (entry1Method == FILE_UNCOMPRESS_METHOD_FLAG) { 305 return -1; 306 } else if (entry2Method == FILE_UNCOMPRESS_METHOD_FLAG) { 307 return 1; 308 } 309 return entry1FileName.compareTo(entry2FileName); 310 }); 311 resetOffset(); 312 } 313 resetOffset()314 private void resetOffset() { 315 long offset = 0L; 316 long cdLength = 0L; 317 for (ZipEntry entry : zipEntries) { 318 entry.updateLength(); 319 entry.getCentralDirectory().setOffset(offset); 320 offset += entry.getZipEntryData().getLength(); 321 cdLength += entry.getCentralDirectory().getLength(); 322 } 323 if (signingBlock != null) { 324 offset += signingBlock.length; 325 } 326 cDOffset = offset; 327 endOfCentralDirectory.setOffset(offset); 328 endOfCentralDirectory.setCDSize(cdLength); 329 offset += cdLength; 330 eOCDOffset = offset; 331 endOfCentralDirectory.setCDTotal(zipEntries.size()); 332 endOfCentralDirectory.setThisDiskCDNum(zipEntries.size()); 333 } 334 getZipEntries()335 public List<ZipEntry> getZipEntries() { 336 return zipEntries; 337 } 338 setZipEntries(List<ZipEntry> zipEntries)339 public void setZipEntries(List<ZipEntry> zipEntries) { 340 this.zipEntries = zipEntries; 341 } 342 getSigningOffset()343 public long getSigningOffset() { 344 return signingOffset; 345 } 346 setSigningOffset(long signingOffset)347 public void setSigningOffset(long signingOffset) { 348 this.signingOffset = signingOffset; 349 } 350 getSigningBlock()351 public byte[] getSigningBlock() { 352 return signingBlock; 353 } 354 setSigningBlock(byte[] signingBlock)355 public void setSigningBlock(byte[] signingBlock) { 356 this.signingBlock = signingBlock; 357 } 358 getCDOffset()359 public long getCDOffset() { 360 return cDOffset; 361 } 362 setCDOffset(long cDOffset)363 public void setCDOffset(long cDOffset) { 364 this.cDOffset = cDOffset; 365 } 366 getEOCDOffset()367 public long getEOCDOffset() { 368 return eOCDOffset; 369 } 370 setEOCDOffset(long eOCDOffset)371 public void setEOCDOffset(long eOCDOffset) { 372 this.eOCDOffset = eOCDOffset; 373 } 374 getEndOfCentralDirectory()375 public EndOfCentralDirectory getEndOfCentralDirectory() { 376 return endOfCentralDirectory; 377 } 378 setEndOfCentralDirectory(EndOfCentralDirectory endOfCentralDirectory)379 public void setEndOfCentralDirectory(EndOfCentralDirectory endOfCentralDirectory) { 380 this.endOfCentralDirectory = endOfCentralDirectory; 381 } 382 getFile()383 public String getFile() { 384 return file; 385 } 386 setFile(String file)387 public void setFile(String file) { 388 this.file = file; 389 } 390 }