1 /* 2 * Copyright (c) 2024 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 ohos; 17 18 import com.alibaba.fastjson.JSON; 19 import com.alibaba.fastjson.JSONArray; 20 import com.alibaba.fastjson.JSONException; 21 import com.alibaba.fastjson.JSONObject; 22 import com.alibaba.fastjson.serializer.SerializerFeature; 23 import org.apache.commons.compress.archivers.zip.DefaultBackingStoreSupplier; 24 import org.apache.commons.compress.archivers.zip.ParallelScatterZipCreator; 25 import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; 26 import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream; 27 import org.apache.commons.compress.archivers.zip.ZipFile; 28 import org.apache.commons.compress.parallel.InputStreamSupplier; 29 import org.apache.commons.compress.utils.IOUtils; 30 31 import java.io.ByteArrayInputStream; 32 import java.io.ByteArrayOutputStream; 33 import java.io.File; 34 import java.io.FileInputStream; 35 import java.io.IOException; 36 import java.io.InputStream; 37 import java.nio.file.FileVisitResult; 38 import java.nio.file.Files; 39 import java.nio.file.Path; 40 import java.nio.file.Paths; 41 import java.nio.file.SimpleFileVisitor; 42 import java.nio.file.attribute.BasicFileAttributes; 43 import java.security.MessageDigest; 44 import java.security.NoSuchAlgorithmException; 45 import java.util.ArrayList; 46 import java.util.Enumeration; 47 import java.util.HashSet; 48 import java.util.List; 49 import java.util.Locale; 50 import java.util.Set; 51 import java.util.concurrent.ExecutionException; 52 import java.util.concurrent.LinkedBlockingQueue; 53 import java.util.concurrent.ThreadPoolExecutor; 54 import java.util.concurrent.TimeUnit; 55 import java.util.stream.Stream; 56 import java.util.zip.CRC32; 57 import java.util.zip.CheckedOutputStream; 58 import java.util.zip.ZipEntry; 59 import java.util.zip.ZipOutputStream; 60 61 /** 62 * PackageUtil 63 * 64 * @since 2024-06-18 65 */ 66 public class PackageUtil { 67 private static final Log LOG = new Log(""); 68 69 /** 70 * get the package name list from pack.info 71 * 72 * @param path the path dir or hsp contains pack.info, or the path of pack.info 73 * @return the list of package name 74 */ getPackageNameFromPath(Path path)75 public static List<String> getPackageNameFromPath(Path path) { 76 List<String> list = new ArrayList<>(); 77 if (!Files.exists(path)) { 78 LOG.warning("getPackageNameFromPath path not exists: " + path); 79 return list; 80 } 81 if (Files.isRegularFile(path)) { 82 String filename = path.getFileName().toString(); 83 if (filename.endsWith(Constants.HSP_SUFFIX)) { 84 // .hsp: return filename 85 list.add(filename.substring(0, filename.lastIndexOf(Constants.HSP_SUFFIX))); 86 return list; 87 } 88 } 89 String content = getPackInfoContentFromPath(path); 90 if (content == null) { 91 return list; 92 } 93 return getPackageNameFromPackInfo(content); 94 } 95 96 /** 97 * get the package name list from module.json 98 * 99 * @param path the path dir or hsp contains module.json, or the path of module.json 100 * @return the bundleType 101 */ getBundleTypeFromPath(Path path)102 public static String getBundleTypeFromPath(Path path) { 103 if (!Files.exists(path)) { 104 LOG.warning("getBundleTypeFromPath path not exists: " + path); 105 return ""; 106 } 107 String content = getModuleJsonContentFromPath(path); 108 if (content == null) { 109 return ""; 110 } 111 return getBundleTypeFromModuleJson(content); 112 } 113 114 /** 115 * get the package name list from pack.info 116 * 117 * @param packInfoContent the content of pack.info 118 * @return the list of package name 119 */ getPackageNameFromPackInfo(String packInfoContent)120 public static List<String> getPackageNameFromPackInfo(String packInfoContent) { 121 List<String> packages = new ArrayList<>(); 122 try { 123 JSONObject jsonObject = JSON.parseObject(packInfoContent, JSONObject.class); 124 if (jsonObject == null) { 125 LOG.warning("getPackagesFromPackInfo failed, json format invalid."); 126 return packages; 127 } 128 JSONArray jsonArray = jsonObject.getJSONArray(Constants.PACKAGES); 129 if (jsonArray == null) { 130 LOG.warning("getPackagesFromPackInfo failed, json format invalid."); 131 return packages; 132 } 133 for (int i = 0; i < jsonArray.size(); i++) { 134 JSONObject object = jsonArray.getJSONObject(i); 135 String packageName = object.getString(Constants.MODULE_NAME); 136 if (packageName != null) { 137 packages.add(packageName); 138 } 139 } 140 return packages; 141 } catch (JSONException ex) { 142 LOG.warning("getPackagesFromPackInfo err: " + ex.getMessage()); 143 return new ArrayList<>(); 144 } 145 } 146 getBundleTypeFromModuleJson(String moduleJsonContent)147 private static String getBundleTypeFromModuleJson(String moduleJsonContent) { 148 try { 149 JSONObject jsonObject = JSON.parseObject(moduleJsonContent, JSONObject.class); 150 if (jsonObject == null) { 151 LOG.warning("getBundleTypeFromModuleJson failed, parse json is null."); 152 return ""; 153 } 154 JSONObject appObject = jsonObject.getJSONObject(Constants.APP); 155 if (appObject == null) { 156 LOG.warning("getBundleTypeFromModuleJson failed, [app] is null."); 157 return ""; 158 } 159 String bundleType = appObject.getString(Constants.BUNDLE_TYPE); 160 return bundleType != null ? bundleType : Constants.APP; 161 } catch (JSONException ex) { 162 LOG.warning("getBundleTypeFromModuleJson failed: " + ex.getMessage()); 163 } 164 return ""; 165 } 166 parseModuleJsonInfo(Path moduleJson)167 private static ModuleJsonInfo parseModuleJsonInfo(Path moduleJson) { 168 ModuleJsonInfo moduleJsonInfo = new ModuleJsonInfo(); 169 try (FileInputStream input = new FileInputStream(moduleJson.toFile())) { 170 JSONObject jsonObject = JSON.parseObject(input, JSONObject.class); 171 if (jsonObject == null) { 172 LOG.warning("parseModuleJsonInfo failed, json format invalid."); 173 return moduleJsonInfo; 174 } 175 JSONObject appObject = jsonObject.getJSONObject(Constants.APP); 176 if (appObject == null) { 177 LOG.warning("parseModuleJsonInfo failed, not found [app]"); 178 return moduleJsonInfo; 179 } 180 JSONObject moduleObject = jsonObject.getJSONObject(Constants.MODULE); 181 if (moduleObject == null) { 182 LOG.warning("parseModuleJsonInfo failed, not found [module]"); 183 return moduleJsonInfo; 184 } 185 moduleJsonInfo.setGenerateBuildHash( 186 appObject.getBooleanValue(Constants.GENERATE_BUILD_HASH) || 187 moduleObject.getBooleanValue(Constants.GENERATE_BUILD_HASH)); 188 moduleJsonInfo.setCompressNativeLibs( 189 moduleObject.getBooleanValue(Constants.COMPRESS_NATIVE_LIBS)); 190 String moduleType = moduleObject.getString(Constants.MODULE_TYPE); 191 moduleJsonInfo.setModuleType(moduleType != null ? moduleType : ""); 192 String moduleName = moduleObject.getString(Constants.MODULE_NAME); 193 moduleJsonInfo.setModuleName(moduleName != null ? moduleName : ""); 194 return moduleJsonInfo; 195 } catch (IOException ex) { 196 LOG.warning("parseModuleJsonInfo err: " + ex.getMessage()); 197 } 198 return moduleJsonInfo; 199 } 200 getPackInfoContentFromPath(Path path)201 private static String getPackInfoContentFromPath(Path path) { 202 try { 203 if (Files.isRegularFile(path)) { 204 String filename = path.getFileName().toString(); 205 if (filename.equals(Constants.FILE_PACK_INFO)) { 206 return new String(Files.readAllBytes(path)); 207 } else if (filename.endsWith(Constants.HSP_SUFFIX)) { 208 return getZipEntryContent(path, Constants.FILE_PACK_INFO); 209 } 210 } else { 211 return new String(Files.readAllBytes(path.resolve(Constants.FILE_PACK_INFO))); 212 } 213 } catch (IOException ex) { 214 LOG.warning("getPackInfoContentFromPath err: " + ex.getMessage()); 215 } 216 return null; 217 } 218 getModuleJsonContentFromPath(Path path)219 private static String getModuleJsonContentFromPath(Path path) { 220 try { 221 if (Files.isRegularFile(path)) { 222 String filename = path.getFileName().toString(); 223 if (filename.equals(Constants.FILE_MODULE_JSON)) { 224 return new String(Files.readAllBytes(path)); 225 } else if (filename.endsWith(Constants.HSP_SUFFIX)) { 226 return getZipEntryContent(path, Constants.FILE_MODULE_JSON); 227 } 228 } else { 229 return new String(Files.readAllBytes(path.resolve(Constants.FILE_MODULE_JSON))); 230 } 231 } catch (IOException ex) { 232 LOG.warning("getPackInfoContentFromPath err: " + ex.getMessage()); 233 } 234 return null; 235 } 236 getZipEntryContent(Path zipPath, String entryName)237 private static String getZipEntryContent(Path zipPath, String entryName) { 238 if (!Files.isRegularFile(zipPath)) { 239 return null; 240 } 241 try (ZipFile zipFile = new ZipFile(zipPath.toFile()); 242 ByteArrayOutputStream output = new ByteArrayOutputStream()) { 243 ZipArchiveEntry zipEntry = zipFile.getEntry(entryName); 244 if (zipEntry != null) { 245 IOUtils.copy(zipFile.getInputStream(zipEntry), output); 246 return output.toString(); 247 } 248 } catch (IOException ex) { 249 LOG.warning("getZipEntryContent err: " + ex.getMessage()); 250 } 251 return null; 252 } 253 254 /** 255 * pack hap or hsp 256 * 257 * @param inputPath input hap/hsp path 258 * @param appPackInfo app scope pack.info 259 * @param outPath output dir 260 * @param compressLevel compress level 261 * @return the hap/hsp path 262 * @throws BundleException bundle exception 263 * @throws IOException IO exception 264 */ pack(Path inputPath, Path appPackInfo, Path outPath, int compressLevel)265 public static Path pack(Path inputPath, Path appPackInfo, Path outPath, int compressLevel) 266 throws BundleException, IOException { 267 if (!Files.exists(inputPath)) { 268 throw new BundleException("pack err, input path not exists."); 269 } 270 if (!Files.exists(appPackInfo)) { 271 throw new BundleException("pack err, app pack.info not exists."); 272 } 273 if (Files.isDirectory(inputPath)) { 274 return packDir(inputPath, appPackInfo, outPath, compressLevel); 275 } else if (Files.isRegularFile(inputPath) && 276 inputPath.getFileName().toString().endsWith(Constants.HSP_SUFFIX)) { 277 return repackHsp(inputPath, appPackInfo, outPath, compressLevel); 278 } 279 throw new BundleException("pack err, not support: " + inputPath); 280 } 281 282 /** 283 * rm dir 284 * 285 * @param dir input path to rm 286 * @return true if rm dir success 287 */ rmdir(Path dir)288 public static boolean rmdir(Path dir) { 289 try { 290 Files.walkFileTree(dir, new SimpleFileVisitor<Path>() { 291 @Override 292 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { 293 Files.delete(file); 294 return FileVisitResult.CONTINUE; 295 } 296 297 @Override 298 public FileVisitResult postVisitDirectory(Path dir, IOException ex) throws IOException { 299 Files.delete(dir); 300 return FileVisitResult.CONTINUE; 301 } 302 }); 303 return true; 304 } catch (IOException ex) { 305 LOG.warning("rmdir err: " + ex.getMessage()); 306 } 307 return false; 308 } 309 hash(Path path)310 private static String hash(Path path) { 311 try (Stream<Path> pathStream = Files.walk(path)) { 312 MessageDigest md = MessageDigest.getInstance(Constants.SHA_256); 313 pathStream.filter(Files::isRegularFile) 314 .sorted() 315 .forEach(file -> { 316 try { 317 byte[] fileBytes = Files.readAllBytes(file); 318 md.update(fileBytes); 319 } catch (IOException e) { 320 LOG.warning("calc hash err: " + e.getMessage()); 321 } 322 }); 323 byte[] hashBytes = md.digest(); 324 StringBuilder sb = new StringBuilder(); 325 for (byte b : hashBytes) { 326 sb.append(String.format("%02x", b)); 327 } 328 return sb.toString(); 329 } catch (NoSuchAlgorithmException | IOException e) { 330 LOG.warning("calc hash err: " + e.getMessage()); 331 } 332 return ""; 333 } 334 repackHsp(Path inputPath, Path appPackInfo, Path outPath, int compressLevel)335 private static Path repackHsp(Path inputPath, Path appPackInfo, Path outPath, int compressLevel) 336 throws BundleException, IOException { 337 Path outHsp = Files.createFile(outPath.resolve(inputPath.getFileName())); 338 try (ZipFile hspFile = new ZipFile(inputPath.toFile()); 339 ZipArchiveOutputStream zipOut = new ZipArchiveOutputStream( 340 new CheckedOutputStream(Files.newOutputStream(outHsp), new CRC32()))) { 341 int cores = Runtime.getRuntime().availableProcessors(); 342 ThreadPoolExecutor executorService = new ThreadPoolExecutor(cores, cores, 60L, 343 TimeUnit.SECONDS, new LinkedBlockingQueue<>()); 344 ParallelScatterZipCreator zipCreator = new ParallelScatterZipCreator( 345 executorService, new DefaultBackingStoreSupplier(null), compressLevel); 346 // pack.info 347 pathToZipEntry(appPackInfo, Constants.NULL_DIR, zipCreator, false); 348 // others 349 Enumeration<ZipArchiveEntry> entries = hspFile.getEntries(); 350 while (entries.hasMoreElements()) { 351 ZipArchiveEntry zipEntry = entries.nextElement(); 352 if (Constants.FILE_PACK_INFO.equals(zipEntry.getName())) { 353 continue; 354 } 355 InputStreamSupplier supplier = () -> { 356 try { 357 return hspFile.getInputStream(zipEntry); 358 } catch (IOException e) { 359 LOG.error(PackingToolErrMsg.IO_EXCEPTION.toString("Repack hsp exist IOException: " + 360 e.getMessage())); 361 return null; 362 } 363 }; 364 zipCreator.addArchiveEntry(zipEntry, supplier); 365 } 366 zipCreator.writeTo(zipOut); 367 } catch (InterruptedException | ExecutionException e) { 368 String errMsg = "Repack hsp exist Exception (InterruptedException | ExecutionException): " + 369 e.getMessage(); 370 LOG.error(PackingToolErrMsg.REPACK_HSP_EXCEPTION.toString(errMsg)); 371 throw new BundleException(errMsg); 372 } 373 return outHsp; 374 } 375 packDir(Path inputPath, Path appPackInfo, Path outPath, int compressLevel)376 private static Path packDir(Path inputPath, Path appPackInfo, Path outPath, int compressLevel) 377 throws BundleException, IOException { 378 List<String> packageNames = getPackageNameFromPath(inputPath.resolve(Constants.FILE_PACK_INFO)); 379 if (packageNames.size() != 1) { 380 throw new BundleException("pack err, pack.info format err"); 381 } 382 ModuleJsonInfo moduleJsonInfo = parseModuleJsonInfo(inputPath.resolve(Constants.FILE_MODULE_JSON)); 383 String pkgName = packageNames.get(0); 384 String suffix = moduleJsonInfo.isShared() ? Constants.HSP_SUFFIX : Constants.HAP_SUFFIX; 385 Path outHap = Files.createFile(outPath.resolve(pkgName + suffix)); 386 387 if (moduleJsonInfo.isCompressNativeLibs()) { 388 return packMultiThread(inputPath, appPackInfo, outHap, compressLevel, moduleJsonInfo); 389 } else { 390 return packSingleThread(inputPath, appPackInfo, outHap, moduleJsonInfo); 391 } 392 } 393 packSingleThread(Path inputPath, Path appPackInfo, Path outHap, ModuleJsonInfo moduleJsonInfo)394 private static Path packSingleThread(Path inputPath, Path appPackInfo, Path outHap, ModuleJsonInfo moduleJsonInfo) 395 throws BundleException, IOException { 396 File[] files = inputPath.toFile().listFiles(); 397 if (files == null || files.length == 0) { 398 throw new BundleException("pack err, dir is empty"); 399 } 400 try (ZipOutputStream zipOut = new ZipOutputStream( 401 new CheckedOutputStream(Files.newOutputStream(outHap), new CRC32()))) { 402 // pack.info 403 pathToZipEntry(appPackInfo, Constants.NULL_DIR, zipOut, false); 404 // module.json generateBuildHash 405 if (moduleJsonInfo.isGenerateBuildHash()) { 406 genBuildHash(inputPath, zipOut); 407 } 408 // others 409 filesToZipEntry(files, zipOut, moduleJsonInfo.isGenerateBuildHash(), 410 moduleJsonInfo.isCompressNativeLibs()); 411 } 412 return outHap; 413 } 414 packMultiThread(Path inputPath, Path appPackInfo, Path outHap, int compressLevel, ModuleJsonInfo moduleJsonInfo)415 private static Path packMultiThread(Path inputPath, Path appPackInfo, Path outHap, int compressLevel, 416 ModuleJsonInfo moduleJsonInfo)throws BundleException, IOException { 417 File[] files = inputPath.toFile().listFiles(); 418 if (files == null || files.length == 0) { 419 throw new BundleException("pack err, dir is empty"); 420 } 421 try (ZipArchiveOutputStream zipOut = new ZipArchiveOutputStream( 422 new CheckedOutputStream(Files.newOutputStream(outHap), new CRC32()))) { 423 int cores = Runtime.getRuntime().availableProcessors(); 424 ThreadPoolExecutor executorService = new ThreadPoolExecutor(cores, cores, 60L, 425 TimeUnit.SECONDS, new LinkedBlockingQueue<>()); 426 ParallelScatterZipCreator zipCreator = new ParallelScatterZipCreator( 427 executorService, new DefaultBackingStoreSupplier(null), compressLevel); 428 // pack.info 429 pathToZipEntry(appPackInfo, Constants.NULL_DIR, zipCreator, false); 430 // module.json generateBuildHash 431 if (moduleJsonInfo.isGenerateBuildHash()) { 432 genBuildHash(inputPath, zipCreator); 433 } 434 // others 435 filesToZipEntry(files, zipCreator, moduleJsonInfo.isGenerateBuildHash(), 436 moduleJsonInfo.isCompressNativeLibs()); 437 // write to zip 438 zipCreator.writeTo(zipOut); 439 } catch (InterruptedException | ExecutionException e) { 440 String errMsg = "Pack dir exist Exception (InterruptedException | ExecutionException): " + e.getMessage(); 441 LOG.error(PackingToolErrMsg.PACK_MULTI_THREAD_EXCEPTION.toString(errMsg)); 442 throw new BundleException(errMsg); 443 } 444 return outHap; 445 } 446 filesToZipEntry(File[] files, ParallelScatterZipCreator zipCreator, boolean genHash, boolean compress)447 private static void filesToZipEntry(File[] files, ParallelScatterZipCreator zipCreator, 448 boolean genHash, boolean compress) throws BundleException { 449 for (File file : files) { 450 if (file.isFile() && !file.getName().equals(Constants.FILE_PACK_INFO)) { 451 if (genHash && file.getName().equals(Constants.FILE_MODULE_JSON)) { 452 continue; 453 } 454 pathToZipEntry(file.toPath(), Constants.NULL_DIR, zipCreator, false); 455 } else if (file.isDirectory()) { 456 if (file.getName().equals(Constants.LIBS_DIR)) { 457 pathToZipEntry(file.toPath(), Constants.LIBS_DIR + Constants.SLASH, zipCreator, compress); 458 } else { 459 pathToZipEntry(file.toPath(), file.getName() + Constants.SLASH, zipCreator, false); 460 } 461 } 462 } 463 } 464 genBuildHash(Path path, ParallelScatterZipCreator zipCreator)465 private static void genBuildHash(Path path, ParallelScatterZipCreator zipCreator) { 466 String hash = hash(path); 467 if (hash.isEmpty()) { 468 return; 469 } 470 Path moduleJson = path.resolve(Constants.FILE_MODULE_JSON); 471 if (!Files.exists(moduleJson)) { 472 LOG.warning("module.json not found: " + path); 473 return; 474 } 475 try (FileInputStream input = new FileInputStream(moduleJson.toFile())) { 476 JSONObject jsonObject = JSON.parseObject(input, JSONObject.class); 477 if (jsonObject == null) { 478 LOG.warning("generateBuildHash: parse json is null."); 479 return; 480 } 481 JSONObject moduleObject = jsonObject.getJSONObject(Constants.MODULE); 482 if (moduleObject == null) { 483 LOG.warning("generateBuildHash: parse json[module] is null."); 484 return; 485 } 486 moduleObject.put(Constants.BUILD_HASH, hash); 487 byte[] data = JSON.toJSONBytes(jsonObject, SerializerFeature.WriteMapNullValue, 488 SerializerFeature.WriteDateUseDateFormat, SerializerFeature.SortField); 489 ZipArchiveEntry zipEntry = new ZipArchiveEntry(Constants.FILE_MODULE_JSON); 490 zipEntry.setMethod(ZipArchiveEntry.STORED); 491 InputStreamSupplier supplier = () -> new ByteArrayInputStream(data); 492 zipCreator.addArchiveEntry(zipEntry, supplier); 493 } catch (IOException ex) { 494 LOG.warning("genBuildHash err: " + ex.getMessage()); 495 } 496 } 497 pathToZipEntry(Path path, String baseDir, ParallelScatterZipCreator zipCreator, boolean compress)498 private static void pathToZipEntry(Path path, String baseDir, ParallelScatterZipCreator zipCreator, 499 boolean compress) throws BundleException { 500 try { 501 File file = path.toFile(); 502 if (file.isDirectory()) { 503 File[] files = file.listFiles(); 504 if (files == null) { 505 return; 506 } 507 for (File f : files) { 508 addArchiveEntry(f, baseDir, zipCreator, compress); 509 } 510 } else { 511 addArchiveEntry(file, baseDir, zipCreator, compress); 512 } 513 } catch (IOException e) { 514 String errMsg = "Path to zip entry exist IOException: " + e.getMessage(); 515 LOG.error(PackingToolErrMsg.IO_EXCEPTION.toString(errMsg)); 516 throw new BundleException(errMsg); 517 } 518 } 519 addArchiveEntry(File file, String baseDir, ParallelScatterZipCreator zipCreator, boolean compress)520 private static void addArchiveEntry(File file, String baseDir, ParallelScatterZipCreator zipCreator, 521 boolean compress) 522 throws IOException { 523 if (file.isDirectory()) { 524 File[] files = file.listFiles(); 525 if (files == null) { 526 LOG.error(PackingToolErrMsg.ADD_ARCHIVE_ENTRY_FAILED.toString("listFiles null, directory name is " + 527 file.getName() + ".")); 528 return; 529 } 530 if (files.length == 0) { 531 String entryName = (baseDir + file.getName() + File.separator) 532 .replace(File.separator, Constants.SLASH); 533 ZipArchiveEntry zipEntry = new ZipArchiveEntry(entryName); 534 zipEntry.setMethod(ZipArchiveEntry.STORED); 535 zipEntry.setSize(0); 536 zipEntry.setCrc(0); 537 InputStreamSupplier supplier = () -> new ByteArrayInputStream(new byte[0]); 538 zipCreator.addArchiveEntry(zipEntry, supplier); 539 } 540 for (File f : files) { 541 addArchiveEntry(f, baseDir + file.getName() + File.separator, zipCreator, compress); 542 } 543 } else { 544 String entryName = (baseDir + file.getName()).replace(File.separator, Constants.SLASH); 545 ZipArchiveEntry zipEntry = new ZipArchiveEntry(entryName); 546 if (compress) { 547 zipEntry.setMethod(ZipArchiveEntry.DEFLATED); 548 } else { 549 zipEntry.setMethod(ZipArchiveEntry.STORED); 550 } 551 InputStreamSupplier supplier = () -> { 552 try { 553 return getInputStream(entryName, file); 554 } catch (IOException e) { 555 LOG.error(PackingToolErrMsg.IO_EXCEPTION.toString("Add ArchiveEntry exist IOException: " 556 + e.getMessage())); 557 return null; 558 } 559 }; 560 zipCreator.addArchiveEntry(zipEntry, supplier); 561 } 562 } 563 getInputStream(String entryName, File file)564 private static InputStream getInputStream(String entryName, File file) throws IOException { 565 if (!entryName.contains(Constants.RAW_FILE_PATH) && !entryName.contains(Constants.RES_FILE_PATH) && 566 file.getName().toLowerCase(Locale.ENGLISH).endsWith(Constants.JSON_SUFFIX)) { 567 try { 568 Object jsonObject = JSON.parse(Files.readAllBytes(file.toPath())); 569 byte[] data = JSON.toJSONBytes(jsonObject, 570 SerializerFeature.WriteMapNullValue, SerializerFeature.WriteDateUseDateFormat); 571 return new ByteArrayInputStream(data); 572 } catch (JSONException ex) { 573 LOG.warning("json format err: " + file.getName()); 574 } 575 } 576 return Files.newInputStream(file.toPath()); 577 } 578 filesToZipEntry(File[] files, ZipOutputStream zipOut, boolean genHash, boolean compress)579 private static void filesToZipEntry(File[] files, ZipOutputStream zipOut, boolean genHash, boolean compress) 580 throws BundleException { 581 for (File file : files) { 582 if (file.isFile() && !file.getName().equals(Constants.FILE_PACK_INFO)) { 583 if (genHash && file.getName().equals(Constants.FILE_MODULE_JSON)) { 584 continue; 585 } 586 pathToZipEntry(file.toPath(), Constants.NULL_DIR, zipOut, false); 587 } else if (file.isDirectory()) { 588 if (file.getName().equals(Constants.LIBS_DIR)) { 589 pathToZipEntry(file.toPath(), Constants.LIBS_DIR + Constants.SLASH, zipOut, compress); 590 } else { 591 pathToZipEntry(file.toPath(), file.getName() + Constants.SLASH, zipOut, false); 592 } 593 } 594 } 595 } 596 genBuildHash(Path path, ZipOutputStream zipOut)597 private static void genBuildHash(Path path, ZipOutputStream zipOut) { 598 String hash = hash(path); 599 if (hash.isEmpty()) { 600 return; 601 } 602 Path moduleJson = path.resolve(Constants.FILE_MODULE_JSON); 603 if (!Files.exists(moduleJson)) { 604 LOG.warning("module.json not found: " + path); 605 return; 606 } 607 try (FileInputStream input = new FileInputStream(moduleJson.toFile())) { 608 JSONObject jsonObject = JSON.parseObject(input, JSONObject.class); 609 if (jsonObject == null) { 610 LOG.warning("generateBuildHash: parse json is null."); 611 return; 612 } 613 JSONObject moduleObject = jsonObject.getJSONObject(Constants.MODULE); 614 if (moduleObject == null) { 615 LOG.warning("generateBuildHash: parse json[module] is null."); 616 return; 617 } 618 moduleObject.put(Constants.BUILD_HASH, hash); 619 byte[] data = JSON.toJSONBytes(jsonObject, SerializerFeature.WriteMapNullValue, 620 SerializerFeature.WriteDateUseDateFormat, SerializerFeature.SortField); 621 CRC32 crc32 = new CRC32(); 622 crc32.update(data, 0, data.length); 623 ZipEntry zipEntry = new ZipEntry(Constants.FILE_MODULE_JSON); 624 zipEntry.setMethod(ZipEntry.STORED); 625 zipEntry.setCompressedSize(data.length); 626 zipEntry.setCrc(crc32.getValue()); 627 zipOut.putNextEntry(zipEntry); 628 try (InputStream inputStream = new ByteArrayInputStream(data)) { 629 IOUtils.copy(inputStream, zipOut, Constants.BUFFER_SIZE); 630 } 631 zipOut.closeEntry(); 632 } catch (IOException ex) { 633 LOG.warning("genBuildHash err: " + ex.getMessage()); 634 } 635 } 636 pathToZipEntry(Path path, String baseDir, ZipOutputStream zipOut, boolean compress)637 private static void pathToZipEntry(Path path, String baseDir, ZipOutputStream zipOut, 638 boolean compress) throws BundleException { 639 try { 640 File file = path.toFile(); 641 if (file.isDirectory()) { 642 File[] files = file.listFiles(); 643 if (files == null) { 644 return; 645 } 646 for (File f : files) { 647 addArchiveEntry(f, baseDir, zipOut, compress); 648 } 649 } else { 650 addArchiveEntry(file, baseDir, zipOut, compress); 651 } 652 } catch (IOException e) { 653 String errMsg = "Path to zip entry exist IOException: " + e.getMessage(); 654 LOG.error(PackingToolErrMsg.IO_EXCEPTION.toString(errMsg)); 655 throw new BundleException(errMsg); 656 } 657 } 658 addArchiveEntry(File file, String baseDir, ZipOutputStream zipOut, boolean compress)659 private static void addArchiveEntry(File file, String baseDir, ZipOutputStream zipOut, boolean compress) 660 throws IOException, BundleException { 661 if (file.isDirectory()) { 662 File[] files = file.listFiles(); 663 if (files == null) { 664 LOG.error(PackingToolErrMsg.ADD_ARCHIVE_ENTRY_FAILED.toString("listFiles null, directory name is " + 665 file.getName() + ".")); 666 return; 667 } 668 if (files.length == 0) { 669 String entryName = (baseDir + file.getName() + File.separator) 670 .replace(File.separator, Constants.SLASH); 671 ZipEntry zipEntry = new ZipEntry(entryName); 672 zipEntry.setMethod(ZipEntry.STORED); 673 zipEntry.setSize(0); 674 zipEntry.setCrc(0); 675 zipOut.putNextEntry(zipEntry); 676 zipOut.closeEntry(); 677 } 678 for (File f : files) { 679 addArchiveEntry(f, baseDir + file.getName() + File.separator, zipOut, compress); 680 } 681 } else { 682 String entryName = (baseDir + file.getName()).replace(File.separator, Constants.SLASH); 683 ZipEntry zipEntry = new ZipEntry(entryName); 684 if (compress) { 685 zipEntry.setMethod(ZipEntry.DEFLATED); 686 } else { 687 zipEntry.setMethod(ZipEntry.STORED); 688 CRC32 crc32 = PackageNormalize.getCrcFromFile(file); 689 zipEntry.setCrc(crc32.getValue()); 690 zipEntry.setCompressedSize(file.length()); 691 } 692 zipOut.putNextEntry(zipEntry); 693 try (InputStream input = Files.newInputStream(file.toPath())) { 694 IOUtils.copy(input, zipOut, Constants.BUFFER_SIZE); 695 } 696 zipOut.closeEntry(); 697 } 698 } 699 checkBundleTypeConsistency(List<String> hapPathList, List<String> hspPathList, Utility utility)700 private static boolean checkBundleTypeConsistency(List<String> hapPathList, List<String> hspPathList, 701 Utility utility) { 702 String bundleType = ""; 703 if (!hapPathList.isEmpty()) { 704 bundleType = getBundleTypeFromPath(Paths.get(hapPathList.get(0))); 705 } else if (!hspPathList.isEmpty()) { 706 bundleType = getBundleTypeFromPath(Paths.get(hspPathList.get(0))); 707 } 708 if (bundleType == null || bundleType.isEmpty()) { 709 return false; 710 } 711 for (String hapPath : hapPathList) { 712 if (!bundleType.equals(getBundleTypeFromPath(Paths.get(hapPath)))) { 713 LOG.error(PackingToolErrMsg.CHECK_BUNDLE_TYPE_CONSISTENCY.toString("Hap bundleType is not same.")); 714 return false; 715 } 716 } 717 for (String hspPath : hspPathList) { 718 if (!bundleType.equals(getBundleTypeFromPath(Paths.get(hspPath)))) { 719 LOG.error(PackingToolErrMsg.CHECK_BUNDLE_TYPE_CONSISTENCY.toString("Hsp bundleType is not same.")); 720 return false; 721 } 722 } 723 if (bundleType.equals(Constants.BUNDLE_TYPE_SHARED)) { 724 utility.setIsSharedApp(true); 725 } 726 return true; 727 } 728 moduleJsonAndPackInfoExists(List<String> hapPathList, List<String> hspPathList)729 private static boolean moduleJsonAndPackInfoExists(List<String> hapPathList, List<String> hspPathList) { 730 for (String hapPath : hapPathList) { 731 Path path = Paths.get(hapPath); 732 if (!Files.exists(path.resolve(Constants.FILE_MODULE_JSON))) { 733 LOG.error(PackingToolErrMsg.FILE_NOT_EXIST.toString("Not found module.json in the hap path: " + 734 path + ".")); 735 return false; 736 } 737 if (!Files.exists(path.resolve(Constants.FILE_PACK_INFO))) { 738 LOG.error(PackingToolErrMsg.FILE_NOT_EXIST.toString("Not found pack.info in the hap path: " + 739 path + ".")); 740 return false; 741 } 742 } 743 for (String hspPath : hspPathList) { 744 Path path = Paths.get(hspPath); 745 if (Files.isDirectory(path)) { 746 if (!Files.exists(path.resolve(Constants.FILE_MODULE_JSON))) { 747 LOG.error(PackingToolErrMsg.FILE_NOT_EXIST.toString("Not found module.json in the hsp path: " + 748 path + ".")); 749 return false; 750 } 751 if (!Files.exists(path.resolve(Constants.FILE_PACK_INFO))) { 752 LOG.error(PackingToolErrMsg.FILE_NOT_EXIST.toString("Not found pack.info in the hsp path: " + 753 path + ".")); 754 return false; 755 } 756 } 757 } 758 return true; 759 } 760 isFileValid(String filePath, String suffix)761 private static boolean isFileValid(String filePath, String suffix) { 762 Path path = Paths.get(filePath); 763 return Files.isRegularFile(path) && path.getFileName().toString().endsWith(suffix); 764 } 765 isFileMatch(String filePath, String matchFileName)766 private static boolean isFileMatch(String filePath, String matchFileName) { 767 Path path = Paths.get(filePath); 768 return Files.isRegularFile(path) && path.getFileName().toString().equals(matchFileName); 769 } 770 isDirValid(String filePath)771 private static boolean isDirValid(String filePath) { 772 return Files.isDirectory(Paths.get(filePath)); 773 } 774 775 /** 776 * verify input param 777 * 778 * @param utility common data 779 * @return true if verify ok 780 */ isVerifyValidInFastAppMode(Utility utility)781 public static boolean isVerifyValidInFastAppMode(Utility utility) { 782 if (!isVerifyValid(utility)) { 783 return false; 784 } 785 if (!utility.getHapPath().isEmpty() && 786 (!isFormatPathValid(utility.getHapPath(), utility.getFormattedHapPathList()) || 787 !isHapPathValid(utility.getFormattedHapPathList()))) { 788 LOG.error(PackingToolErrMsg.FAST_APP_MODE_ARGS_INVALID.toString("--hap-path is invalid.")); 789 return false; 790 } 791 if (!utility.getHspPath().isEmpty() && 792 (!isFormatPathValid(utility.getHspPath(), utility.getFormattedHspPathList()) || 793 !isHspPathValid(utility.getFormattedHspPathList()))) { 794 LOG.error(PackingToolErrMsg.FAST_APP_MODE_ARGS_INVALID.toString("--hsp-path is invalid.")); 795 return false; 796 } 797 if (utility.getHapPath().isEmpty() && utility.getHspPath().isEmpty()) { 798 LOG.error(PackingToolErrMsg.FAST_APP_MODE_ARGS_INVALID.toString("--hap-path and --hsp-path is empty.")); 799 return false; 800 } 801 if (!moduleJsonAndPackInfoExists(utility.getFormattedHapPathList(), utility.getFormattedHspPathList())) { 802 LOG.error(PackingToolErrMsg.FAST_APP_MODE_ARGS_INVALID.toString("--hap-path or --hsp-path is invalid.")); 803 return false; 804 } 805 if (!checkBundleTypeConsistency( 806 utility.getFormattedHapPathList(), utility.getFormattedHspPathList(), utility)) { 807 LOG.error(PackingToolErrMsg.FAST_APP_MODE_ARGS_INVALID.toString("The bundleType is inconsistent.")); 808 return false; 809 } 810 if (!isPackInfoValid(Paths.get(utility.getPackInfoPath()), 811 utility.getFormattedHapPathList(), utility.getFormattedHspPathList())) { 812 LOG.error(PackingToolErrMsg.FAST_APP_MODE_ARGS_INVALID.toString("pack.info is invalid.")); 813 return false; 814 } 815 if (!utility.getEncryptPath().isEmpty() 816 && !isFileValid(utility.getEncryptPath(), Constants.FILE_ENCRYPT_JSON)) { 817 LOG.error(PackingToolErrMsg.FAST_APP_MODE_ARGS_INVALID.toString("--encrypt-path is invalid.")); 818 return false; 819 } 820 Path outPath = Paths.get(utility.getOutPath()); 821 if (utility.getForceRewrite().equals(Constants.FALSE) && Files.exists(outPath)) { 822 LOG.error(PackingToolErrMsg.FAST_APP_MODE_ARGS_INVALID.toString("--out-path file already existed, but " + 823 "--force is not 'true'.")); 824 return false; 825 } 826 if (!outPath.getFileName().toString().endsWith(Constants.APP_SUFFIX)) { 827 LOG.error(PackingToolErrMsg.FAST_APP_MODE_ARGS_INVALID.toString("--out-path must end with .app.")); 828 return false; 829 } 830 return true; 831 } 832 isVerifyValid(Utility utility)833 private static boolean isVerifyValid(Utility utility) { 834 if (utility.getPackInfoPath().isEmpty()) { 835 LOG.error(PackingToolErrMsg.FAST_APP_MODE_ARGS_INVALID.toString("--pack-info-path is empty.")); 836 return false; 837 } 838 if (!isFileValid(utility.getPackInfoPath(), Constants.FILE_PACK_INFO)) { 839 LOG.error(PackingToolErrMsg.FAST_APP_MODE_ARGS_INVALID.toString("--pack-info-path is invalid.")); 840 return false; 841 } 842 if (!utility.getSignaturePath().isEmpty() && !isFileValid(utility.getSignaturePath(), "")) { 843 LOG.error(PackingToolErrMsg.FAST_APP_MODE_ARGS_INVALID.toString("--signature-path is invalid.")); 844 return false; 845 } 846 if (!utility.getCertificatePath().isEmpty() && 847 !isFileValid(utility.getCertificatePath(), "")) { 848 LOG.error(PackingToolErrMsg.FAST_APP_MODE_ARGS_INVALID.toString("--certificate-path is invalid.")); 849 return false; 850 } 851 if (!utility.getPackResPath().isEmpty() && !isFileValid(utility.getPackResPath(), Constants.FILE_PACK_RES)) { 852 LOG.error(PackingToolErrMsg.FAST_APP_MODE_ARGS_INVALID.toString("--pack-res-path is invalid.")); 853 return false; 854 } 855 if (!utility.getEntryCardPath().isEmpty() && 856 !CompressVerify.compatibleProcess(utility, utility.getEntryCardPath(), 857 utility.getformattedEntryCardPathList(), Constants.PNG_SUFFIX)) { 858 LOG.error(PackingToolErrMsg.FAST_APP_MODE_ARGS_INVALID.toString("--entrycard-path is invalid.")); 859 return false; 860 } 861 if (!utility.getPacJsonPath().isEmpty() 862 && !isFileMatch(utility.getPacJsonPath(), Constants.FILE_PAC_JSON)) { 863 LOG.error(PackingToolErrMsg.FAST_APP_MODE_ARGS_INVALID.toString("--pac-json-path is invalid.")); 864 return false; 865 } 866 if (utility.getOutPath().isEmpty()) { 867 LOG.error(PackingToolErrMsg.FAST_APP_MODE_ARGS_INVALID.toString("--out-path is empty.")); 868 return false; 869 } 870 return true; 871 } 872 isHapPathValid(List<String> formatPathList)873 private static boolean isHapPathValid(List<String> formatPathList) { 874 for (String path : formatPathList) { 875 if (!isDirValid(path)) { 876 return false; 877 } 878 } 879 return true; 880 } 881 isHspPathValid(List<String> formatPathList)882 private static boolean isHspPathValid(List<String> formatPathList) { 883 for (String path : formatPathList) { 884 if (!isDirValid(path) && !isFileValid(path, Constants.HSP_SUFFIX)) { 885 return false; 886 } 887 } 888 return true; 889 } 890 isFormatPathValid(String inputPath, List<String> formatPathList)891 private static boolean isFormatPathValid(String inputPath, List<String> formatPathList) { 892 Set<String> formatPathSet = new HashSet<>(); 893 for (String path : inputPath.split(Constants.COMMA)) { 894 try { 895 Path realpath = Paths.get(path).toRealPath(); 896 if (Files.exists(realpath)) { 897 formatPathSet.add(realpath.toString()); 898 } else { 899 LOG.error(PackingToolErrMsg.FILE_NOT_EXIST.toString("Format path not exists. Path: " + 900 realpath + ".")); 901 return false; 902 } 903 } catch (IOException ex) { 904 LOG.error(PackingToolErrMsg.IO_EXCEPTION.toString("Verify format path exist IOException: " 905 + ex.getMessage())); 906 return false; 907 } 908 } 909 formatPathList.addAll(formatPathSet); 910 return true; 911 } 912 isPackInfoValid(Path packInfo, List<String> hapPathList, List<String> hspPathList)913 private static boolean isPackInfoValid(Path packInfo, List<String> hapPathList, List<String> hspPathList) { 914 List<String> allPackages = getPackageNameFromPath(packInfo); 915 Set<String> allPackageSet = new HashSet<>(allPackages); 916 if (allPackages.size() > allPackageSet.size()) { 917 LOG.error(PackingToolErrMsg.PACK_INFO_INVALID.toString("Package name is redundant in app pack.info, " + 918 "the path is " + packInfo + ".")); 919 return false; 920 } 921 if (allPackages.isEmpty()) { 922 LOG.error(PackingToolErrMsg.PACK_INFO_INVALID.toString("App pack.info format error, the path is " + 923 packInfo + ".")); 924 return false; 925 } 926 Set<String> packages = new HashSet<>(); 927 for (String hapPath : hapPathList) { 928 List<String> list = getPackageNameFromPath(Paths.get(hapPath)); 929 if (list.size() != 1) { 930 LOG.error(PackingToolErrMsg.PACK_INFO_INVALID.toString("Module pack.info format error, the path is " + 931 hapPath + ".")); 932 return false; 933 } 934 String packageName = list.get(0); 935 if (!allPackages.contains(packageName)) { 936 LOG.error(PackingToolErrMsg.PACK_INFO_INVALID.toString("Module pack.info name does not exist in app " 937 + "pack.info name list, the path is " + hapPath + ".")); 938 return false; 939 } 940 if (packages.contains(packageName)) { 941 LOG.error(PackingToolErrMsg.PACK_INFO_INVALID.toString("Package name is redundant in " + hapPath + ".")); 942 return false; 943 } 944 packages.add(packageName); 945 } 946 for (String hspPath : hspPathList) { 947 List<String> list = getPackageNameFromPath(Paths.get(hspPath)); 948 if (list.size() != 1) { 949 LOG.error(PackingToolErrMsg.PACK_INFO_INVALID.toString("Module pack.info format err, the path is " + 950 hspPath + ".")); 951 return false; 952 } 953 String packageName = list.get(0); 954 if (!allPackages.contains(packageName)) { 955 LOG.error(PackingToolErrMsg.PACK_INFO_INVALID.toString("Module pack.info name does not exist in app " 956 + "pack.info name list, the path is " + hspPath + ".")); 957 return false; 958 } 959 if (packages.contains(packageName)) { 960 LOG.error(PackingToolErrMsg.PACK_INFO_INVALID.toString("Package name is redundant in " + 961 hspPath + ".")); 962 return false; 963 } 964 packages.add(packageName); 965 } 966 if (!allPackageSet.equals(packages)) { 967 LOG.error(PackingToolErrMsg.PACK_INFO_INVALID.toString("Package name is not same between module " 968 + "and app pack.info.")); 969 return false; 970 } 971 return true; 972 } 973 974 } 975