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.ZipException; 21 import com.ohos.hapsigntool.utils.FileUtils; 22 23 import org.apache.logging.log4j.LogManager; 24 import org.apache.logging.log4j.Logger; 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 Logger LOGGER = LogManager.getLogger(Zip.class); 42 43 /** 44 * file is uncompress file flag 45 */ 46 public static final int 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, 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 = FileUtils.appendWriteFileByOffsetToFile(file, fos, 193 zipEntryData.getFileOffset(), zipEntryData.getFileSize()); 194 if (!isSuccess) { 195 throw new ZipException("write zip data failed"); 196 } 197 if (zipEntryData.getDataDescriptor() != null) { 198 FileUtils.writeByteToOutFile(zipEntryData.getDataDescriptor().toBytes(), fos); 199 } 200 } 201 if (signingBlock != null) { 202 FileUtils.writeByteToOutFile(signingBlock, fos); 203 } 204 for (ZipEntry entry : zipEntries) { 205 CentralDirectory cd = entry.getCentralDirectory(); 206 FileUtils.writeByteToOutFile(cd.toBytes(), fos); 207 } 208 FileUtils.writeByteToOutFile(endOfCentralDirectory.toBytes(), fos); 209 } catch (IOException e) { 210 CustomException.throwException(ERROR.ZIP_ERROR, e.getMessage()); 211 } 212 } 213 214 /** 215 * alignment uncompress entry 216 * 217 * @param alignment int alignment 218 */ alignment(int alignment)219 public void alignment(int alignment) { 220 try { 221 sort(); 222 boolean isFirstUnRunnableFile = true; 223 for (ZipEntry entry : zipEntries) { 224 ZipEntryData zipEntryData = entry.getZipEntryData(); 225 short method = zipEntryData.getZipEntryHeader().getMethod(); 226 if (method != FILE_UNCOMPRESS_METHOD_FLAG && !isFirstUnRunnableFile) { 227 // only align uncompressed entry and the first compress entry. 228 break; 229 } 230 int alignBytes; 231 if (method == FILE_UNCOMPRESS_METHOD_FLAG && FileUtils.isRunnableFile( 232 zipEntryData.getZipEntryHeader().getFileName())) { 233 // .abc and .so file align 4096 byte. 234 alignBytes = 4096; 235 } else if (isFirstUnRunnableFile) { 236 // the first file after runnable file, align 4096 byte. 237 alignBytes = 4096; 238 isFirstUnRunnableFile = false; 239 } else { 240 // normal file align 4 byte. 241 alignBytes = alignment; 242 } 243 int add = entry.alignment(alignBytes); 244 if (add > 0) { 245 resetOffset(); 246 } 247 } 248 } catch (ZipException e) { 249 CustomException.throwException(ERROR.ZIP_ERROR, e.getMessage()); 250 } 251 } 252 253 /** 254 * remove sign block 255 */ removeSignBlock()256 public void removeSignBlock() { 257 signingBlock = null; 258 resetOffset(); 259 } 260 261 /** 262 * sort uncompress entry in the front. 263 */ sort()264 private void sort() { 265 // sort uncompress file (so, abc, an) - other uncompress file - compress file 266 zipEntries.sort((entry1, entry2) -> { 267 short entry1Method = entry1.getZipEntryData().getZipEntryHeader().getMethod(); 268 short entry2Method = entry2.getZipEntryData().getZipEntryHeader().getMethod(); 269 String entry1FileName = entry1.getZipEntryData().getZipEntryHeader().getFileName(); 270 String entry2FileName = entry2.getZipEntryData().getZipEntryHeader().getFileName(); 271 if (entry1Method == FILE_UNCOMPRESS_METHOD_FLAG && entry2Method == FILE_UNCOMPRESS_METHOD_FLAG) { 272 boolean isRunnableFile1 = FileUtils.isRunnableFile(entry1FileName); 273 boolean isRunnableFile2 = FileUtils.isRunnableFile(entry2FileName); 274 if (isRunnableFile1 && isRunnableFile2) { 275 return entry1FileName.compareTo(entry2FileName); 276 } else if (isRunnableFile1) { 277 return -1; 278 } else if (isRunnableFile2) { 279 return 1; 280 } 281 } else if (entry1Method == FILE_UNCOMPRESS_METHOD_FLAG) { 282 return -1; 283 } else if (entry2Method == FILE_UNCOMPRESS_METHOD_FLAG) { 284 return 1; 285 } 286 return entry1FileName.compareTo(entry2FileName); 287 }); 288 resetOffset(); 289 } 290 resetOffset()291 private void resetOffset() { 292 long offset = 0L; 293 long cdLength = 0L; 294 for (ZipEntry entry : zipEntries) { 295 entry.getCentralDirectory().setOffset(offset); 296 offset += entry.getZipEntryData().getLength(); 297 cdLength += entry.getCentralDirectory().getLength(); 298 } 299 if (signingBlock != null) { 300 offset += signingBlock.length; 301 } 302 cDOffset = offset; 303 endOfCentralDirectory.setOffset(offset); 304 endOfCentralDirectory.setcDSize(cdLength); 305 offset += cdLength; 306 eOCDOffset = offset; 307 } 308 getZipEntries()309 public List<ZipEntry> getZipEntries() { 310 return zipEntries; 311 } 312 setZipEntries(List<ZipEntry> zipEntries)313 public void setZipEntries(List<ZipEntry> zipEntries) { 314 this.zipEntries = zipEntries; 315 } 316 getSigningOffset()317 public long getSigningOffset() { 318 return signingOffset; 319 } 320 setSigningOffset(long signingOffset)321 public void setSigningOffset(long signingOffset) { 322 this.signingOffset = signingOffset; 323 } 324 getSigningBlock()325 public byte[] getSigningBlock() { 326 return signingBlock; 327 } 328 setSigningBlock(byte[] signingBlock)329 public void setSigningBlock(byte[] signingBlock) { 330 this.signingBlock = signingBlock; 331 } 332 getCDOffset()333 public long getCDOffset() { 334 return cDOffset; 335 } 336 setCDOffset(long cDOffset)337 public void setCDOffset(long cDOffset) { 338 this.cDOffset = cDOffset; 339 } 340 getEOCDOffset()341 public long getEOCDOffset() { 342 return eOCDOffset; 343 } 344 setEOCDOffset(long eOCDOffset)345 public void setEOCDOffset(long eOCDOffset) { 346 this.eOCDOffset = eOCDOffset; 347 } 348 getEndOfCentralDirectory()349 public EndOfCentralDirectory getEndOfCentralDirectory() { 350 return endOfCentralDirectory; 351 } 352 setEndOfCentralDirectory(EndOfCentralDirectory endOfCentralDirectory)353 public void setEndOfCentralDirectory(EndOfCentralDirectory endOfCentralDirectory) { 354 this.endOfCentralDirectory = endOfCentralDirectory; 355 } 356 getFile()357 public String getFile() { 358 return file; 359 } 360 setFile(String file)361 public void setFile(String file) { 362 this.file = file; 363 } 364 }