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("addArchiveEntry err: " + e.getMessage()); 360 return null; 361 } 362 }; 363 zipCreator.addArchiveEntry(zipEntry, supplier); 364 } 365 zipCreator.writeTo(zipOut); 366 } catch (InterruptedException | ExecutionException e) { 367 String errMsg = "repackHsp err: " + e.getMessage(); 368 LOG.error(errMsg); 369 throw new BundleException(errMsg); 370 } 371 return outHsp; 372 } 373 packDir(Path inputPath, Path appPackInfo, Path outPath, int compressLevel)374 private static Path packDir(Path inputPath, Path appPackInfo, Path outPath, int compressLevel) 375 throws BundleException, IOException { 376 List<String> packageNames = getPackageNameFromPath(inputPath.resolve(Constants.FILE_PACK_INFO)); 377 if (packageNames.size() != 1) { 378 throw new BundleException("pack err, pack.info format err"); 379 } 380 ModuleJsonInfo moduleJsonInfo = parseModuleJsonInfo(inputPath.resolve(Constants.FILE_MODULE_JSON)); 381 String pkgName = packageNames.get(0); 382 String suffix = moduleJsonInfo.isShared() ? Constants.HSP_SUFFIX : Constants.HAP_SUFFIX; 383 Path outHap = Files.createFile(outPath.resolve(pkgName + suffix)); 384 385 if (moduleJsonInfo.isCompressNativeLibs()) { 386 return packMultiThread(inputPath, appPackInfo, outHap, compressLevel, moduleJsonInfo); 387 } else { 388 return packSingleThread(inputPath, appPackInfo, outHap, moduleJsonInfo); 389 } 390 } 391 packSingleThread(Path inputPath, Path appPackInfo, Path outHap, ModuleJsonInfo moduleJsonInfo)392 private static Path packSingleThread(Path inputPath, Path appPackInfo, Path outHap, ModuleJsonInfo moduleJsonInfo) 393 throws BundleException, IOException { 394 File[] files = inputPath.toFile().listFiles(); 395 if (files == null || files.length == 0) { 396 throw new BundleException("pack err, dir is empty"); 397 } 398 try (ZipOutputStream zipOut = new ZipOutputStream( 399 new CheckedOutputStream(Files.newOutputStream(outHap), new CRC32()))) { 400 // pack.info 401 pathToZipEntry(appPackInfo, Constants.NULL_DIR, zipOut, false); 402 // module.json generateBuildHash 403 if (moduleJsonInfo.isGenerateBuildHash()) { 404 genBuildHash(inputPath, zipOut); 405 } 406 // others 407 filesToZipEntry(files, zipOut, moduleJsonInfo.isGenerateBuildHash(), 408 moduleJsonInfo.isCompressNativeLibs()); 409 } 410 return outHap; 411 } 412 packMultiThread(Path inputPath, Path appPackInfo, Path outHap, int compressLevel, ModuleJsonInfo moduleJsonInfo)413 private static Path packMultiThread(Path inputPath, Path appPackInfo, Path outHap, int compressLevel, 414 ModuleJsonInfo moduleJsonInfo)throws BundleException, IOException { 415 File[] files = inputPath.toFile().listFiles(); 416 if (files == null || files.length == 0) { 417 throw new BundleException("pack err, dir is empty"); 418 } 419 try (ZipArchiveOutputStream zipOut = new ZipArchiveOutputStream( 420 new CheckedOutputStream(Files.newOutputStream(outHap), new CRC32()))) { 421 int cores = Runtime.getRuntime().availableProcessors(); 422 ThreadPoolExecutor executorService = new ThreadPoolExecutor(cores, cores, 60L, 423 TimeUnit.SECONDS, new LinkedBlockingQueue<>()); 424 ParallelScatterZipCreator zipCreator = new ParallelScatterZipCreator( 425 executorService, new DefaultBackingStoreSupplier(null), compressLevel); 426 // pack.info 427 pathToZipEntry(appPackInfo, Constants.NULL_DIR, zipCreator, false); 428 // module.json generateBuildHash 429 if (moduleJsonInfo.isGenerateBuildHash()) { 430 genBuildHash(inputPath, zipCreator); 431 } 432 // others 433 filesToZipEntry(files, zipCreator, moduleJsonInfo.isGenerateBuildHash(), 434 moduleJsonInfo.isCompressNativeLibs()); 435 // write to zip 436 zipCreator.writeTo(zipOut); 437 } catch (InterruptedException | ExecutionException e) { 438 String errMsg = "packDir err: " + e.getMessage(); 439 LOG.error(errMsg); 440 throw new BundleException(errMsg); 441 } 442 return outHap; 443 } 444 filesToZipEntry(File[] files, ParallelScatterZipCreator zipCreator, boolean genHash, boolean compress)445 private static void filesToZipEntry(File[] files, ParallelScatterZipCreator zipCreator, 446 boolean genHash, boolean compress) throws BundleException { 447 for (File file : files) { 448 if (file.isFile() && !file.getName().equals(Constants.FILE_PACK_INFO)) { 449 if (genHash && file.getName().equals(Constants.FILE_MODULE_JSON)) { 450 continue; 451 } 452 pathToZipEntry(file.toPath(), Constants.NULL_DIR, zipCreator, false); 453 } else if (file.isDirectory()) { 454 if (file.getName().equals(Constants.LIBS_DIR)) { 455 pathToZipEntry(file.toPath(), Constants.LIBS_DIR + Constants.SLASH, zipCreator, compress); 456 } else { 457 pathToZipEntry(file.toPath(), file.getName() + Constants.SLASH, zipCreator, false); 458 } 459 } 460 } 461 } 462 genBuildHash(Path path, ParallelScatterZipCreator zipCreator)463 private static void genBuildHash(Path path, ParallelScatterZipCreator zipCreator) { 464 String hash = hash(path); 465 if (hash.isEmpty()) { 466 return; 467 } 468 Path moduleJson = path.resolve(Constants.FILE_MODULE_JSON); 469 if (!Files.exists(moduleJson)) { 470 LOG.warning("module.json not found: " + path); 471 return; 472 } 473 try (FileInputStream input = new FileInputStream(moduleJson.toFile())) { 474 JSONObject jsonObject = JSON.parseObject(input, JSONObject.class); 475 if (jsonObject == null) { 476 LOG.warning("generateBuildHash: parse json is null."); 477 return; 478 } 479 JSONObject moduleObject = jsonObject.getJSONObject(Constants.MODULE); 480 if (moduleObject == null) { 481 LOG.warning("generateBuildHash: parse json[module] is null."); 482 return; 483 } 484 moduleObject.put(Constants.BUILD_HASH, hash); 485 byte[] data = JSON.toJSONBytes(jsonObject, SerializerFeature.WriteMapNullValue, 486 SerializerFeature.WriteDateUseDateFormat, SerializerFeature.SortField); 487 ZipArchiveEntry zipEntry = new ZipArchiveEntry(Constants.FILE_MODULE_JSON); 488 zipEntry.setMethod(ZipArchiveEntry.STORED); 489 InputStreamSupplier supplier = () -> new ByteArrayInputStream(data); 490 zipCreator.addArchiveEntry(zipEntry, supplier); 491 } catch (IOException ex) { 492 LOG.warning("genBuildHash err: " + ex.getMessage()); 493 } 494 } 495 pathToZipEntry(Path path, String baseDir, ParallelScatterZipCreator zipCreator, boolean compress)496 private static void pathToZipEntry(Path path, String baseDir, ParallelScatterZipCreator zipCreator, 497 boolean compress) throws BundleException { 498 try { 499 File file = path.toFile(); 500 if (file.isDirectory()) { 501 File[] files = file.listFiles(); 502 if (files == null) { 503 return; 504 } 505 for (File f : files) { 506 addArchiveEntry(f, baseDir, zipCreator, compress); 507 } 508 } else { 509 addArchiveEntry(file, baseDir, zipCreator, compress); 510 } 511 } catch (IOException e) { 512 String errMsg = "pathToZip err: " + e.getMessage(); 513 LOG.error(errMsg); 514 throw new BundleException(errMsg); 515 } 516 } 517 addArchiveEntry(File file, String baseDir, ParallelScatterZipCreator zipCreator, boolean compress)518 private static void addArchiveEntry(File file, String baseDir, ParallelScatterZipCreator zipCreator, 519 boolean compress) 520 throws IOException { 521 if (file.isDirectory()) { 522 File[] files = file.listFiles(); 523 if (files == null) { 524 LOG.error("listFiles null: " + file.getName()); 525 return; 526 } 527 if (files.length == 0) { 528 String entryName = (baseDir + file.getName() + File.separator) 529 .replace(File.separator, Constants.SLASH); 530 ZipArchiveEntry zipEntry = new ZipArchiveEntry(entryName); 531 zipEntry.setMethod(ZipArchiveEntry.STORED); 532 zipEntry.setSize(0); 533 zipEntry.setCrc(0); 534 InputStreamSupplier supplier = () -> new ByteArrayInputStream(new byte[0]); 535 zipCreator.addArchiveEntry(zipEntry, supplier); 536 } 537 for (File f : files) { 538 addArchiveEntry(f, baseDir + file.getName() + File.separator, zipCreator, compress); 539 } 540 } else { 541 String entryName = (baseDir + file.getName()).replace(File.separator, Constants.SLASH); 542 ZipArchiveEntry zipEntry = new ZipArchiveEntry(entryName); 543 if (compress) { 544 zipEntry.setMethod(ZipArchiveEntry.DEFLATED); 545 } else { 546 zipEntry.setMethod(ZipArchiveEntry.STORED); 547 } 548 InputStreamSupplier supplier = () -> { 549 try { 550 return getInputStream(entryName, file); 551 } catch (IOException e) { 552 LOG.error("addArchiveEntry err: " + e.getMessage()); 553 return null; 554 } 555 }; 556 zipCreator.addArchiveEntry(zipEntry, supplier); 557 } 558 } 559 getInputStream(String entryName, File file)560 private static InputStream getInputStream(String entryName, File file) throws IOException { 561 if (!entryName.contains(Constants.RAW_FILE_PATH) && !entryName.contains(Constants.RES_FILE_PATH) && 562 file.getName().toLowerCase(Locale.ENGLISH).endsWith(Constants.JSON_SUFFIX)) { 563 try { 564 Object jsonObject = JSON.parse(Files.readAllBytes(file.toPath())); 565 byte[] data = JSON.toJSONBytes(jsonObject, 566 SerializerFeature.WriteMapNullValue, SerializerFeature.WriteDateUseDateFormat); 567 return new ByteArrayInputStream(data); 568 } catch (JSONException ex) { 569 LOG.warning("json format err: " + file.getName()); 570 } 571 } 572 return Files.newInputStream(file.toPath()); 573 } 574 filesToZipEntry(File[] files, ZipOutputStream zipOut, boolean genHash, boolean compress)575 private static void filesToZipEntry(File[] files, ZipOutputStream zipOut, boolean genHash, boolean compress) 576 throws BundleException { 577 for (File file : files) { 578 if (file.isFile() && !file.getName().equals(Constants.FILE_PACK_INFO)) { 579 if (genHash && file.getName().equals(Constants.FILE_MODULE_JSON)) { 580 continue; 581 } 582 pathToZipEntry(file.toPath(), Constants.NULL_DIR, zipOut, false); 583 } else if (file.isDirectory()) { 584 if (file.getName().equals(Constants.LIBS_DIR)) { 585 pathToZipEntry(file.toPath(), Constants.LIBS_DIR + Constants.SLASH, zipOut, compress); 586 } else { 587 pathToZipEntry(file.toPath(), file.getName() + Constants.SLASH, zipOut, false); 588 } 589 } 590 } 591 } 592 genBuildHash(Path path, ZipOutputStream zipOut)593 private static void genBuildHash(Path path, ZipOutputStream zipOut) { 594 String hash = hash(path); 595 if (hash.isEmpty()) { 596 return; 597 } 598 Path moduleJson = path.resolve(Constants.FILE_MODULE_JSON); 599 if (!Files.exists(moduleJson)) { 600 LOG.warning("module.json not found: " + path); 601 return; 602 } 603 try (FileInputStream input = new FileInputStream(moduleJson.toFile())) { 604 JSONObject jsonObject = JSON.parseObject(input, JSONObject.class); 605 if (jsonObject == null) { 606 LOG.warning("generateBuildHash: parse json is null."); 607 return; 608 } 609 JSONObject moduleObject = jsonObject.getJSONObject(Constants.MODULE); 610 if (moduleObject == null) { 611 LOG.warning("generateBuildHash: parse json[module] is null."); 612 return; 613 } 614 moduleObject.put(Constants.BUILD_HASH, hash); 615 byte[] data = JSON.toJSONBytes(jsonObject, SerializerFeature.WriteMapNullValue, 616 SerializerFeature.WriteDateUseDateFormat, SerializerFeature.SortField); 617 CRC32 crc32 = new CRC32(); 618 crc32.update(data, 0, data.length); 619 ZipEntry zipEntry = new ZipEntry(Constants.FILE_MODULE_JSON); 620 zipEntry.setMethod(ZipEntry.STORED); 621 zipEntry.setCompressedSize(data.length); 622 zipEntry.setCrc(crc32.getValue()); 623 zipOut.putNextEntry(zipEntry); 624 try (InputStream inputStream = new ByteArrayInputStream(data)) { 625 IOUtils.copy(inputStream, zipOut, Constants.BUFFER_SIZE); 626 } 627 zipOut.closeEntry(); 628 } catch (IOException ex) { 629 LOG.warning("genBuildHash err: " + ex.getMessage()); 630 } 631 } 632 pathToZipEntry(Path path, String baseDir, ZipOutputStream zipOut, boolean compress)633 private static void pathToZipEntry(Path path, String baseDir, ZipOutputStream zipOut, 634 boolean compress) throws BundleException { 635 try { 636 File file = path.toFile(); 637 if (file.isDirectory()) { 638 File[] files = file.listFiles(); 639 if (files == null) { 640 return; 641 } 642 for (File f : files) { 643 addArchiveEntry(f, baseDir, zipOut, compress); 644 } 645 } else { 646 addArchiveEntry(file, baseDir, zipOut, compress); 647 } 648 } catch (IOException e) { 649 String errMsg = "pathToZip err: " + e.getMessage(); 650 LOG.error(errMsg); 651 throw new BundleException(errMsg); 652 } 653 } 654 addArchiveEntry(File file, String baseDir, ZipOutputStream zipOut, boolean compress)655 private static void addArchiveEntry(File file, String baseDir, ZipOutputStream zipOut, boolean compress) 656 throws IOException, BundleException { 657 if (file.isDirectory()) { 658 File[] files = file.listFiles(); 659 if (files == null) { 660 LOG.error("listFiles null: " + file.getName()); 661 return; 662 } 663 if (files.length == 0) { 664 String entryName = (baseDir + file.getName() + File.separator) 665 .replace(File.separator, Constants.SLASH); 666 ZipEntry zipEntry = new ZipEntry(entryName); 667 zipEntry.setMethod(ZipEntry.STORED); 668 zipEntry.setSize(0); 669 zipEntry.setCrc(0); 670 zipOut.putNextEntry(zipEntry); 671 zipOut.closeEntry(); 672 } 673 for (File f : files) { 674 addArchiveEntry(f, baseDir + file.getName() + File.separator, zipOut, compress); 675 } 676 } else { 677 String entryName = (baseDir + file.getName()).replace(File.separator, Constants.SLASH); 678 ZipEntry zipEntry = new ZipEntry(entryName); 679 if (compress) { 680 zipEntry.setMethod(ZipEntry.DEFLATED); 681 } else { 682 zipEntry.setMethod(ZipEntry.STORED); 683 CRC32 crc32 = PackageNormalize.getCrcFromFile(file); 684 zipEntry.setCrc(crc32.getValue()); 685 zipEntry.setCompressedSize(file.length()); 686 } 687 zipOut.putNextEntry(zipEntry); 688 try (InputStream input = Files.newInputStream(file.toPath())) { 689 IOUtils.copy(input, zipOut, Constants.BUFFER_SIZE); 690 } 691 zipOut.closeEntry(); 692 } 693 } 694 checkBundleTypeConsistency(List<String> hapPathList, List<String> hspPathList, Utility utility)695 private static boolean checkBundleTypeConsistency(List<String> hapPathList, List<String> hspPathList, 696 Utility utility) { 697 String bundleType = ""; 698 if (!hapPathList.isEmpty()) { 699 bundleType = getBundleTypeFromPath(Paths.get(hapPathList.get(0))); 700 } else if (!hspPathList.isEmpty()) { 701 bundleType = getBundleTypeFromPath(Paths.get(hspPathList.get(0))); 702 } 703 if (bundleType == null || bundleType.isEmpty()) { 704 return false; 705 } 706 for (String hapPath : hapPathList) { 707 if (!bundleType.equals(getBundleTypeFromPath(Paths.get(hapPath)))) { 708 LOG.error("bundleType is not same"); 709 return false; 710 } 711 } 712 for (String hspPath : hspPathList) { 713 if (!bundleType.equals(getBundleTypeFromPath(Paths.get(hspPath)))) { 714 LOG.error("bundleType is not same"); 715 return false; 716 } 717 } 718 if (bundleType.equals(Constants.BUNDLE_TYPE_SHARED)) { 719 utility.setIsSharedApp(true); 720 } 721 return true; 722 } 723 moduleJsonAndPackInfoExists(List<String> hapPathList, List<String> hspPathList)724 private static boolean moduleJsonAndPackInfoExists(List<String> hapPathList, List<String> hspPathList) { 725 for (String hapPath : hapPathList) { 726 Path path = Paths.get(hapPath); 727 if (!Files.exists(path.resolve(Constants.FILE_MODULE_JSON))) { 728 LOG.error("not found module.json in path: " + path); 729 return false; 730 } 731 if (!Files.exists(path.resolve(Constants.FILE_PACK_INFO))) { 732 LOG.error("not found pack.info in path: " + path); 733 return false; 734 } 735 } 736 for (String hspPath : hspPathList) { 737 Path path = Paths.get(hspPath); 738 if (Files.isDirectory(path)) { 739 if (!Files.exists(path.resolve(Constants.FILE_MODULE_JSON))) { 740 LOG.error("not found module.json in path: " + path); 741 return false; 742 } 743 if (!Files.exists(path.resolve(Constants.FILE_PACK_INFO))) { 744 LOG.error("not found pack.info in path: " + path); 745 return false; 746 } 747 } 748 } 749 return true; 750 } 751 isFileValid(String filePath, String suffix)752 private static boolean isFileValid(String filePath, String suffix) { 753 Path path = Paths.get(filePath); 754 return Files.isRegularFile(path) && path.getFileName().toString().endsWith(suffix); 755 } 756 isDirValid(String filePath)757 private static boolean isDirValid(String filePath) { 758 return Files.isDirectory(Paths.get(filePath)); 759 } 760 761 /** 762 * verify input param 763 * 764 * @param utility common data 765 * @return true if verify ok 766 */ isVerifyValidInFastAppMode(Utility utility)767 public static boolean isVerifyValidInFastAppMode(Utility utility) { 768 if (!isVerifyValid(utility)) { 769 return false; 770 } 771 if (!utility.getHapPath().isEmpty() && 772 (!isFormatPathValid(utility.getHapPath(), utility.getFormattedHapPathList()) || 773 !isHapPathValid(utility.getFormattedHapPathList()))) { 774 LOG.error("CompressVerify::isVerifyValidInFastAppMode hap-path is invalid."); 775 return false; 776 } 777 if (!utility.getHspPath().isEmpty() && 778 (!isFormatPathValid(utility.getHspPath(), utility.getFormattedHspPathList()) || 779 !isHspPathValid(utility.getFormattedHspPathList()))) { 780 LOG.error("CompressVerify::isVerifyValidInFastAppMode hsp-path is invalid."); 781 return false; 782 } 783 if (utility.getHapPath().isEmpty() && utility.getHspPath().isEmpty()) { 784 LOG.error("CompressVerify::isVerifyValidInFastAppMode hap-path and hsp-path is empty."); 785 return false; 786 } 787 if (!moduleJsonAndPackInfoExists(utility.getFormattedHapPathList(), utility.getFormattedHspPathList())) { 788 LOG.error("CompressVerify::isVerifyValidInFastAppMode hap-path or hsp-path is invalid."); 789 return false; 790 } 791 if (!checkBundleTypeConsistency( 792 utility.getFormattedHapPathList(), utility.getFormattedHspPathList(), utility)) { 793 LOG.error("CompressVerify::isVerifyValidInFastAppMode bundleType is inconsistent."); 794 return false; 795 } 796 if (!isPackInfoValid(Paths.get(utility.getPackInfoPath()), 797 utility.getFormattedHapPathList(), utility.getFormattedHspPathList())) { 798 LOG.error("CompressVerify::isVerifyValidInFastAppMode pack.info is invalid."); 799 return false; 800 } 801 if (!utility.getEncryptPath().isEmpty() 802 && !isFileValid(utility.getEncryptPath(), Constants.FILE_ENCRYPT_JSON)) { 803 LOG.error("CompressVerify::isVerifyValidInFastAppMode encrypt-path is invalid."); 804 return false; 805 } 806 Path outPath = Paths.get(utility.getOutPath()); 807 if (utility.getForceRewrite().equals(Constants.FALSE) && Files.exists(outPath)) { 808 LOG.error("CompressVerify::isVerifyValidInFastAppMode out file already existed."); 809 return false; 810 } 811 if (!outPath.getFileName().toString().endsWith(Constants.APP_SUFFIX)) { 812 LOG.error("CompressVerify::isVerifyValidInFastAppMode out-path must end with .app."); 813 return false; 814 } 815 return true; 816 } 817 isVerifyValid(Utility utility)818 private static boolean isVerifyValid(Utility utility) { 819 if (utility.getPackInfoPath().isEmpty()) { 820 LOG.error("CompressVerify::isArgsValidInAppMode pack-info-path is empty."); 821 return false; 822 } 823 if (!isFileValid(utility.getPackInfoPath(), Constants.FILE_PACK_INFO)) { 824 LOG.error("CompressVerify::isVerifyValidInFastAppMode pack-info-path is invalid."); 825 return false; 826 } 827 if (!utility.getSignaturePath().isEmpty() && !isFileValid(utility.getSignaturePath(), "")) { 828 LOG.error("CompressVerify::isVerifyValidInFastAppMode signature-path is invalid."); 829 return false; 830 } 831 if (!utility.getCertificatePath().isEmpty() && 832 !isFileValid(utility.getCertificatePath(), "")) { 833 LOG.error("CompressVerify::isVerifyValidInFastAppMode certificate-path is invalid."); 834 return false; 835 } 836 if (!utility.getPackResPath().isEmpty() && !isFileValid(utility.getPackResPath(), Constants.FILE_PACK_RES)) { 837 LOG.error("CompressVerify::isVerifyValidInFastAppMode pack-res-path is invalid."); 838 return false; 839 } 840 if (!utility.getEntryCardPath().isEmpty() && 841 !CompressVerify.compatibleProcess(utility, utility.getEntryCardPath(), 842 utility.getformattedEntryCardPathList(), Constants.PNG_SUFFIX)) { 843 LOG.error("CompressVerify::isVerifyValidInFastAppMode entrycard-path is invalid."); 844 return false; 845 } 846 if (utility.getOutPath().isEmpty()) { 847 LOG.error("CompressVerify::isVerifyValidInFastAppMode out-path is empty."); 848 return false; 849 } 850 return true; 851 } 852 isHapPathValid(List<String> formatPathList)853 private static boolean isHapPathValid(List<String> formatPathList) { 854 for (String path : formatPathList) { 855 if (!isDirValid(path)) { 856 return false; 857 } 858 } 859 return true; 860 } 861 isHspPathValid(List<String> formatPathList)862 private static boolean isHspPathValid(List<String> formatPathList) { 863 for (String path : formatPathList) { 864 if (!isDirValid(path) && !isFileValid(path, Constants.HSP_SUFFIX)) { 865 return false; 866 } 867 } 868 return true; 869 } 870 isFormatPathValid(String inputPath, List<String> formatPathList)871 private static boolean isFormatPathValid(String inputPath, List<String> formatPathList) { 872 Set<String> formatPathSet = new HashSet<>(); 873 for (String path : inputPath.split(Constants.COMMA)) { 874 try { 875 Path realpath = Paths.get(path).toRealPath(); 876 if (Files.exists(realpath)) { 877 formatPathSet.add(realpath.toString()); 878 } else { 879 LOG.error("PackageUtil::formatPath not exists: " + realpath); 880 return false; 881 } 882 } catch (IOException ex) { 883 LOG.error("PackageUtil::formatPath err: " + ex.getMessage()); 884 return false; 885 } 886 } 887 formatPathList.addAll(formatPathSet); 888 return true; 889 } 890 isPackInfoValid(Path packInfo, List<String> hapPathList, List<String> hspPathList)891 private static boolean isPackInfoValid(Path packInfo, List<String> hapPathList, List<String> hspPathList) { 892 List<String> allPackages = getPackageNameFromPath(packInfo); 893 Set<String> allPackageSet = new HashSet<>(allPackages); 894 if (allPackages.size() > allPackageSet.size()) { 895 LOG.error("package name is redundant in app pack.info: " + packInfo); 896 return false; 897 } 898 if (allPackages.isEmpty()) { 899 LOG.error("app pack.info format err: " + packInfo); 900 return false; 901 } 902 Set<String> packages = new HashSet<>(); 903 for (String hapPath : hapPathList) { 904 List<String> list = getPackageNameFromPath(Paths.get(hapPath)); 905 if (list.size() != 1) { 906 LOG.error("module pack.info format err: " + hapPath); 907 return false; 908 } 909 String packageName = list.get(0); 910 if (!allPackages.contains(packageName)) { 911 LOG.error("module pack.info name not exist in app pack.info name list: " + hapPath); 912 return false; 913 } 914 if (packages.contains(packageName)) { 915 LOG.error("package name is redundant in " + hapPath); 916 return false; 917 } 918 packages.add(packageName); 919 } 920 for (String hspPath : hspPathList) { 921 List<String> list = getPackageNameFromPath(Paths.get(hspPath)); 922 if (list.size() != 1) { 923 LOG.error("module pack.info format err: " + hspPath); 924 return false; 925 } 926 String packageName = list.get(0); 927 if (!allPackages.contains(packageName)) { 928 LOG.error("module pack.info name not exist in app pack.info name list: " + hspPath); 929 return false; 930 } 931 if (packages.contains(packageName)) { 932 LOG.error("package name is redundant in " + hspPath); 933 return false; 934 } 935 packages.add(packageName); 936 } 937 if (!allPackageSet.equals(packages)) { 938 LOG.error("package name not same between module and app pack.info."); 939 return false; 940 } 941 return true; 942 } 943 944 } 945