• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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