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