/* * Copyright (c) 2021-2024 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package ohos; import java.io.BufferedInputStream; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Arrays; import java.util.concurrent.ExecutionException; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import java.util.List; import java.util.Locale; import java.util.Optional; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.UUID; import java.util.stream.Stream; import java.util.zip.CRC32; import java.util.zip.CheckedOutputStream; import java.util.zip.ZipInputStream; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import java.util.zip.ZipOutputStream; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONException; import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.serializer.SerializerFeature; import org.apache.commons.compress.archivers.zip.DefaultBackingStoreSupplier; import org.apache.commons.compress.archivers.zip.ParallelScatterZipCreator; import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream; import org.apache.commons.compress.parallel.InputStreamSupplier; /** * bundle compressor class, compress file and directory. * */ public class Compressor { private static final String HAP_SUFFIX = ".hap"; private static final String HSP_SUFFIX = ".hsp"; private static final String PNG_SUFFIX = ".png"; private static final String UPPERCASE_PNG_SUFFIX = ".PNG"; private static final String CONFIG_JSON = "config.json"; private static final String MODULE_JSON = "module.json"; private static final String PATCH_JSON = "patch.json"; private static final String ADDITION_JSON = "addition.json"; private static final String PKG_CONTEXT_INFO = "pkgContextInfo.json"; private static final String NAME = "name"; private static final String NULL_DIR_NAME = ""; private static final String RES_DIR_NAME = "res/"; private static final String RESOURCES_DIR_NAME = "resources/"; private static final String LIBS_DIR_NAME = "libs/"; private static final String AN_DIR_NAME = "an/"; private static final String AP_PATH_NAME = "ap/"; private static final String ASSETS_DIR_NAME = "assets/"; private static final String SO_DIR_NAME = "maple/"; private static final String SO_ARM64_DIR_NAME = "maple/arm64/"; private static final String LINUX_FILE_SEPARATOR = "/"; private static final String DISTRO = "distro"; private static final String FORMS = "forms"; private static final String MODULE_NAME = "module-name"; private static final String MODULE_NAME_NEW = "moduleName"; private static final String JSON_END = "}"; private static final String SEMICOLON = "\""; private static final String COMPRESS_NATIVE_LIBS = "compressNativeLibs"; private static final String SHARED_LIBS_DIR_NAME = "shared_libs/"; private static final String DEVICE_TYPE = "deviceType"; private static final String DEVICE_TYPE_FITNESSWATCH = "fitnessWatch"; private static final String DEVICE_TYPE_FITNESSWATCH_NEW = "liteWearable"; private static final String ENTRYCARD_NAME = "EntryCard/"; private static final String PACKINFO_NAME = "pack.info"; private static final String ENTRYCARD_BASE_NAME = "base"; private static final String ENTRYCARD_SNAPSHOT_NAME = "snapshot"; private static final String PIC_1X2 = "1x2"; private static final String PIC_2X2 = "2x2"; private static final String PIC_2X4 = "2x4"; private static final String PIC_4X4 = "4x4"; private static final String PIC_1X1 = "1x1"; private static final String PIC_6X4 = "6x4"; private static final String REGEX_LANGUAGE = "^[a-z]{2}$"; private static final String REGEX_SCRIPT = "^[A-Z][a-z]{3}$"; private static final String REGEX_COUNTRY = "^[A-Z]{2,3}|[0-9]{3}$"; private static final String REGEX_ORIENTATION = "^vertical|horizontal$"; private static final String REGEX_DEVICE_TYPE = "^phone|tablet|car|tv|wearable|liteWearable|2in1$"; private static final String REGEX_SCREEN_DENSITY = "^sdpi|mdpi|ldpi|xldpi|xxldpi$"; private static final String REGEX_COLOR_MODE = "^light|dark$"; private static final String REGEX_SHAPE = "^circle$"; private static final String JS_PATH = "js/"; private static final String ETS_PATH = "ets/"; private static final String HNP_PATH = "hnp/"; private static final String TEMP_HAP_DIR = "tempHapDir"; private static final String TEMP_HSP_DIR = "tempHspDir"; private static final String TEMP_SELECTED_HAP_DIR = "tempSelectedHapDir"; private static final String EMPTY_STRING = ""; private static final String RELEASE = "Release"; private static final String TYPE_SHARED = "shared"; private static final String APP = "app"; private static final String MODULE = "module"; private static final String GENERATE_BUILD_HASH = "generateBuildHash"; private static final String BUILD_HASH = "buildHash"; private static final String TEMP_DIR = "temp"; private static final String SHA_256 = "SHA-256"; private static final String JSON_SUFFIX = ".json"; private static final String ATOMIC_SERVICE = "atomicService"; private static final String RAW_FILE_PATH = "resources/rawfile"; private static final String RES_FILE_PATH = "resources/resfile"; private static final String SUMMARY = "summary"; private static final String VERSION_CODE = "versionCode"; private static final String VERSION_NAME = "versionName"; private static final String VERSION = "version"; private static final String CODE = "code"; private static final String VERSION_RECORD = "version_record.json"; private static final String RES_INDEX = "resources.index"; private static final String ETS_FILE_NAME = "ets"; private static final String HNP_FILE_NAME = "hnp"; private static final String DIR_FILE_NAME = "dir"; private static final String AN_FILE_NAME = "an"; private static final String AP_FILE_NAME = "ap"; private static final String RESOURCE_FILE_NAME = "resources"; private static final String JS_FILE_NAME = "js"; private static final String ASSETS_FILE_NAME = "assets"; private static final String MAPLE_FILE_NAME = "maple"; private static final String SHARED_LIBS_FILE_NAME = "shared_libs"; private static final String LIBS_DIR = "libs"; private static final String RPCID = "rpcid.sc"; private static final String HAPADDITION_FOLDER_NAME = "hapAddition"; private static final String TARGET_FILE_PATH = HAPADDITION_FOLDER_NAME + LINUX_FILE_SEPARATOR + "resources" + LINUX_FILE_SEPARATOR + "base" + LINUX_FILE_SEPARATOR + "profile"; private static final String BACKUP_PREFIX = "backup"; // set timestamp to get fixed MD5 private static final int ENTRY_FILE_LIMIT_DEFAULT = 2; private static final int NOT_ENTRY_FILE_LIMIT_DEFAULT = 2; private static final int TOTAL_FILE_LIMIT_DEFAULT = 10; private static final int FILE_LIMIT = 10; private static final int SHA256_BASE = 0xff; private static final int SHA256_OFFSET = 0x100; private static final int RADIX = 16; private static final int BEGIN_INDEX = 1; private static final int BUFFER_BYTE_SIZE = 1024; private static final int BUFFER_WRITE_SIZE = 1444; // set buffer size of each read private static final int BUFFER_SIZE = 40 * 1024; private static final Log LOG = new Log(Compressor.class.toString()); private static final int SHARED_APP_HSP_LIMIT = 1; private static int entryModuleSizeLimit = 2; private static int notEntryModuleSizeLimit = 2; private static int sumModuleSizeLimit = 10; private static final int INVALID_VERSION = -1; private static boolean isOverlay = false; private ZipArchiveOutputStream zipOut = null; private boolean mIsContain2x2EntryCard = true; private boolean isEntryOpen = false; private List list = new ArrayList(); private List formNamesList = new ArrayList(); private List fileNameList = new ArrayList(); private List supportDimensionsList = Arrays.asList(PIC_1X2, PIC_2X2, PIC_2X4, PIC_4X4, PIC_1X1, PIC_6X4); private HashMap hapVerifyInfoMap = new HashMap<>(); public static int getEntryModuleSizeLimit() { return entryModuleSizeLimit; } public static void setEntryModuleSizeLimit(int entry) { entryModuleSizeLimit = entry; } public static int getNotEntryModuleSizeLimit() { return notEntryModuleSizeLimit; } public static void setNotEntryModuleSizeLimit(int notEntry) { notEntryModuleSizeLimit = notEntry; } public static int getSumModuleSizeLimit() { return sumModuleSizeLimit; } public static void setSumModuleSizeLimit(int sumModule) { sumModuleSizeLimit = sumModule; } private static class VersionNormalizeUtil { private int originVersionCode = INVALID_VERSION; private String originVersionName = ""; private String moduleName = ""; public int getOriginVersionCode() { return originVersionCode; } public void setOriginVersionCode(int originVersionCode) { this.originVersionCode = originVersionCode; } public String getModuleName() { return moduleName; } public void setModuleName(String name) { moduleName = name; } public String getOriginVersionName() { return originVersionName; } public void setOriginVersionName(String originVersionName) { this.originVersionName = originVersionName; } } /** * parse file size limit from utility. * * @param utility Indicates the utility. */ public void parseFileSizeLimit(Utility utility) throws BundleException { int sumLimit = TOTAL_FILE_LIMIT_DEFAULT; String totalLimit = utility.getTotalLimit(); if (!totalLimit.isEmpty()) { try { sumLimit = Integer.parseInt(totalLimit); } catch (NumberFormatException e) { LOG.error("parseFileSizeLimit failed, input total-limit invalid."); throw new BundleException("parseFileSizeLimit failed, input total-limit invalid."); } if (sumLimit <= 0 || sumLimit > FILE_LIMIT) { LOG.error("parseFileSizeLimit failed, input total-limit invalid."); throw new BundleException("parseFileSizeLimit failed, input total-limit invalid."); } } String normalLimit = utility.getNormalModuleLimit(); int notEntry = NOT_ENTRY_FILE_LIMIT_DEFAULT; if (!normalLimit.isEmpty()) { try { notEntry = Integer.parseInt(normalLimit); } catch (NumberFormatException e) { LOG.error("parseFileSizeLimit failed, input normal-module-limit invalid."); throw new BundleException("parseFileSizeLimit failed, input normal-module-limit invalid."); } if (notEntry <= 0 || notEntry > sumLimit || notEntry > FILE_LIMIT) { LOG.error("parseFileSizeLimit failed, input normal-module-limit invalid."); throw new BundleException("parseFileSizeLimit failed, input normal-module-limit invalid."); } } String mainLimit = utility.getMainModuleLimit(); int entryLimit = ENTRY_FILE_LIMIT_DEFAULT; if (!mainLimit.isEmpty()) { try { entryLimit = Integer.parseInt(mainLimit); } catch (NumberFormatException e) { LOG.error("parseFileSizeLimit failed, input main-module-limit invalid."); throw new BundleException("parseFileSizeLimit failed, input main-module-limit invalid."); } if (entryLimit <= 0 || entryLimit > sumLimit || entryLimit > FILE_LIMIT) { LOG.error("parseFileSizeLimit failed, input main-module-limit invalid."); throw new BundleException("parseFileSizeLimit failed, input main-module-limit invalid."); } } setEntryModuleSizeLimit(entryLimit); setNotEntryModuleSizeLimit(notEntry); setSumModuleSizeLimit(sumLimit); } /** * check path if is a module.json file * * @param path path input * @return true if path is a module file */ private static boolean isModuleJSON(String path) { File file = new File(path); if ((file.isFile()) && MODULE_JSON.equals(file.getName())) { return true; } return false; } /** * start compress. * file orders as follows: * for hap: 1.config.json 2.lib 3.res 4.assets 5.*.so 6.*.dex 7.*.apk 8.resources.index * for app: 1.certificate 2.signature 3.pack.info 4.hap (1 and 2 may not be used) * * @param utility common data * @return compressProcess if compress succeed */ public boolean compressProcess(Utility utility) { switch (utility.getMode()) { case Utility.VERSION_NORMALIZE: versionNormalize(utility); return true; case Utility.MODE_HAPADDITION: hapAddition(utility); return true; case Utility.PACKAGE_NORMALIZE: return PackageNormalize.normalize(utility); default: return defaultProcess(utility); } } private boolean defaultProcess(Utility utility) { File destFile = new File(utility.getOutPath()); // if out file directory not exist, mkdirs. File outParentFile = destFile.getParentFile(); if ((outParentFile != null) && (!outParentFile.exists())) { if (!outParentFile.mkdirs()) { LOG.error("Compressor::compressProcess create out file parent directory failed."); return false; } } boolean compressResult = true; FileOutputStream fileOut = null; CheckedOutputStream checkedOut = null; try { fileOut = new FileOutputStream(destFile); checkedOut = new CheckedOutputStream(fileOut, new CRC32()); zipOut = new ZipArchiveOutputStream(checkedOut); zipOut.setLevel(utility.getCompressLevel()); compressExcute(utility); } catch (FileNotFoundException exception) { compressResult = false; LOG.error("Compressor::compressProcess file not found exception: " + exception.getMessage()); } catch (BundleException ex) { compressResult = false; LOG.error("Compressor::compressProcess Bundle exception: " + ex.getMessage()); } finally { closeZipOutputStream(); Utility.closeStream(zipOut); Utility.closeStream(checkedOut); Utility.closeStream(fileOut); } if (compressResult && !checkAppAtomicServiceCompressedSizeValid(utility)) { compressResult = false; LOG.error("Compressor::compressProcess check atomic service size fail."); } // if compress failed, delete out file. if (!compressResult) { LOG.error("Compressor::compressProcess compress failed."); if (!destFile.delete()) { LOG.error("Compressor::compressProcess delete dest file failed."); } } return compressResult; } private void compressExcute(Utility utility) throws BundleException { switch (utility.getMode()) { case Utility.MODE_HAP: compressHap(utility); break; case Utility.MODE_HAR: compressHarMode(utility); break; case Utility.MODE_APP: compressAppMode(utility); break; case Utility.MODE_FAST_APP: compressFastAppMode(utility); break; case Utility.MODE_MULTI_APP: compressAppModeForMultiProject(utility); break; case Utility.MODE_HQF: compressHQFMode(utility); break; case Utility.MODE_APPQF: compressAPPQFMode(utility); break; case Utility.MODE_HSP: compressHsp(utility); break; default: compressPackResMode(utility); } } private void compressHsp(Utility utility) throws BundleException { setGenerateBuildHash(utility); if (isModuleJSON(utility.getJsonPath())) { Optional optional = FileUtils.getFileContent(utility.getJsonPath()); String jsonString = optional.get(); if (!checkStageAsanTsanEnabledValid(jsonString)) { LOG.error("checkStageAsanTsanEnabledValid failed."); throw new BundleException("compressHsp failed."); } if (!checkStageHwasanEnabledValid(jsonString)) { LOG.error("checkStageHwasanEnabledValid failed."); throw new BundleException("compressHsp failed."); } if (!checkStageUbsanEnabledValid(jsonString)) { LOG.error("checkStageUbsanEnabledValid failed."); throw new BundleException("compressHsp failed."); } if (!checkStageAtomicService(jsonString)) { LOG.error("checkStageAtomicService failed."); throw new BundleException("checkStageAtomicService failed."); } // check continueBundleName in module.json if (!checkContinueBundleNameIsValid(jsonString)) { LOG.error("checkContinueBundleNameIsValid failed."); throw new BundleException("compressHsp failed."); } // check whether is an overlay hsp or not if (!checkStageOverlayCfg(jsonString)) { LOG.error("checkStageOverlayCfg failed."); throw new BundleException("checkStageOverlayCfg failed."); } String moduleType = ModuleJsonUtil.parseModuleType(jsonString); if (!TYPE_SHARED.equals(moduleType)) { LOG.error("module type must be shared."); throw new BundleException("compressHsp failed."); } } compressHSPMode(utility); buildHash(utility); } private void compressHap(Utility utility) throws BundleException { if (utility.getJsonPath().isEmpty() && !utility.getBinPath().isEmpty()) { // only for slim device compressHapMode(utility); return; } setGenerateBuildHash(utility); if (isModuleJSON(utility.getJsonPath())) { if (!checkStageHap(utility)) { LOG.error("checkStageHap failed."); throw new BundleException("checkStageHap failed."); } Optional optional = FileUtils.getFileContent(utility.getJsonPath()); String jsonString = optional.get(); String moduleType = ModuleJsonUtil.parseModuleType(jsonString); if (TYPE_SHARED.equals(moduleType)) { LOG.warning("Compress mode is hap, but module type is shared."); } String bundleType = ModuleJsonUtil.parseStageBundleType(jsonString); if (TYPE_SHARED.equals(bundleType)) { LOG.warning("Compress mode is hap, but app type is shared."); } compressHapModeForModule(utility); buildHash(utility); } else { compressHapMode(utility); buildHash(utility); } } private static boolean hasGenerateBuildHash(Utility utility) throws BundleException { File file = new File(utility.getJsonPath()); if (!file.exists()) { LOG.error("Compressor::hasGenerateBuildHash failed for json file not exist"); throw new BundleException("Compressor::hasGenerateBuildHash failed for json file not exist"); } InputStream json = null; boolean res = false; try { json = new FileInputStream(file); JSONObject jsonObject = JSON.parseObject(json, JSONObject.class); if (jsonObject == null || !jsonObject.containsKey(APP) || !jsonObject.containsKey(MODULE)) { LOG.error("json file is invalid."); throw new BundleException("json file is invalid."); } JSONObject appJson = jsonObject.getJSONObject(APP); JSONObject moduleJson = jsonObject.getJSONObject(MODULE); if (appJson.containsKey(GENERATE_BUILD_HASH) || moduleJson.containsKey(GENERATE_BUILD_HASH)) { res = true; } } catch (BundleException exception) { LOG.error("Compressor::hasGenerateBuildHash failed."); throw new BundleException("Compressor::hasGenerateBuildHash failed."); } catch (JSONException | IOException e) { LOG.error("Compressor::hasGenerateBuildHash failed for json file is invalid." + e.getMessage()); throw new BundleException("Compressor::hasGenerateBuildHash failed."); } finally { FileUtils.closeStream(json); } return res; } private static void setGenerateBuildHash(Utility utility) throws BundleException { if (utility.isBuildHashFinish() || !hasGenerateBuildHash(utility)) { return; } copyFileToTempDir(utility); File file = new File(utility.getJsonPath()); if (!file.exists()) { LOG.error("Compressor::setGenerateBuildHash failed for json file not exist"); throw new BundleException("Compressor::setGenerateBuildHash failed for json file not exist"); } InputStream json = null; BufferedWriter bw = null; try { json = new FileInputStream(file); JSONObject jsonObject = JSON.parseObject(json, JSONObject.class); if (!jsonObject.containsKey(APP) || !jsonObject.containsKey(MODULE)) { LOG.error("json file is invalid."); throw new BundleException("json file is invalid."); } JSONObject appJson = jsonObject.getJSONObject(APP); JSONObject moduleJson = jsonObject.getJSONObject(MODULE); if (appJson.containsKey(GENERATE_BUILD_HASH) && appJson.getBoolean(GENERATE_BUILD_HASH)) { utility.setGenerateBuildHash(true); } else { if (moduleJson.containsKey(GENERATE_BUILD_HASH) && moduleJson.getBoolean(GENERATE_BUILD_HASH)) { utility.setGenerateBuildHash(true); } } appJson.remove(GENERATE_BUILD_HASH); moduleJson.remove(GENERATE_BUILD_HASH); String pretty = JSON.toJSONString(jsonObject, new SerializerFeature[]{ SerializerFeature.PrettyFormat, SerializerFeature.WriteMapNullValue, SerializerFeature.WriteDateUseDateFormat}); bw = new BufferedWriter(new OutputStreamWriter( new FileOutputStream(utility.getJsonPath()), StandardCharsets.UTF_8)); bw.write(pretty); } catch (BundleException exception) { LOG.error("Compressor::setGenerateBuildHash failed."); throw new BundleException("Compressor::setGenerateBuildHash failed."); } catch (NullPointerException | IOException | JSONException e) { LOG.error("Compressor::setGenerateBuildHash failed, json data err: " + e.getMessage()); throw new BundleException("Compressor::setGenerateBuildHash failed, json data err."); } finally { FileUtils.closeStream(json); if (bw != null) { try { bw.flush(); bw.close(); } catch (IOException e) { LOG.error("Compressor::setGenerateBuildHash failed for IOException " + e.getMessage()); throw new BundleException("Compressor::setGenerateBuildHash failed."); } } } } private static void copyFileToTempDir(Utility utility) throws BundleException { String jsonPath = utility.getJsonPath(); File oldfile = new File(jsonPath); if (!oldfile.exists()) { LOG.error("Compressor::copyFileToTempDir failed for json file not found."); throw new BundleException("Compressor::copyFileToTempDir failed for json file not found."); } String oldFileParent = oldfile.getParent(); String tempDir = TEMP_DIR + File.separator + UUID.randomUUID(); mkdir(new File(oldFileParent + File.separator + tempDir)); String fileName; if (isModuleJSON(utility.getJsonPath())) { fileName = MODULE_JSON; } else { fileName = CONFIG_JSON; } String tempPath = oldFileParent + File.separator + tempDir + File.separator + fileName; try (InputStream inStream = new FileInputStream(jsonPath); FileOutputStream fs = new FileOutputStream(tempPath)) { byte[] buffer = new byte[BUFFER_WRITE_SIZE]; int byteread; while ((byteread = inStream.read(buffer)) != -1) { fs.write(buffer, 0, byteread); } utility.setJsonPath(tempPath); } catch (IOException e) { LOG.error("Compressor::copyFileToTempDir failed, IOException: " + e.getMessage()); throw new BundleException("Compressor::copyFileToTempDir failed."); } } private static void mkdir(File file) { if (null != file && !file.exists()) { mkdir(file.getParentFile()); file.mkdir(); } } private static void buildHash(Utility utility) throws BundleException { if (utility.isBuildHashFinish() || (!utility.getGenerateBuildHash())) { return; } String filePath = utility.getOutPath(); String hash = getSHA256(filePath); try { putBuildHash(utility, hash); } catch (IOException e) { LOG.error("Compressor::putBuildHash failed, " + e.getMessage()); throw new BundleException("Compressor::putBuildHash failed."); } } private static byte[] checkSum(String filename) throws BundleException { try (InputStream fis = new FileInputStream(filename)) { byte[] buffer = new byte[BUFFER_BYTE_SIZE]; MessageDigest complete = MessageDigest.getInstance(SHA_256); int numRead; do { numRead = fis.read(buffer); if (numRead > 0) { complete.update(buffer, 0, numRead); } } while (numRead != -1); return complete.digest(); } catch (IOException | NoSuchAlgorithmException e) { LOG.error("Compressor::checkSum failed, IOException or NoSuchAlgorithmException: " + e.getMessage()); throw new BundleException("Compressor::checkSum failed."); } } /** * get SHA256 of hap or hsp * * @param filePath the path of hap or hsp. */ public static String getSHA256(String filePath) throws BundleException { byte[] byteSum = checkSum(filePath); StringBuilder temp = new StringBuilder(); for (int i = 0; i < byteSum.length; i++) { temp.append( Integer.toString((byteSum[i] & SHA256_BASE) + SHA256_OFFSET, RADIX).substring(BEGIN_INDEX)); } return temp.toString(); } private static void putBuildHash(Utility utility, String hash) throws BundleException, IOException { if (utility.isBuildHashFinish()) { return; } String jsonPath = utility.getJsonPath(); File file = new File(jsonPath); if (!file.exists()) { LOG.error("Compressor::putBuildHash failed for json file not exist"); throw new BundleException("Compressor::putBuildHash failed for json file not exist"); } InputStream json = null; BufferedWriter bw = null; try { json = new FileInputStream(file); JSONObject jsonObject = JSON.parseObject(json, JSONObject.class); JSONObject moduleJson = jsonObject.getJSONObject(MODULE); moduleJson.put(BUILD_HASH, hash); String pretty = JSON.toJSONString(jsonObject, SerializerFeature.PrettyFormat, SerializerFeature.WriteMapNullValue, SerializerFeature.WriteDateUseDateFormat); bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(jsonPath), StandardCharsets.UTF_8)); bw.write(pretty); } catch (IOException e) { LOG.error("Compressor::putBuildHash failed, IOException: " + e.getMessage()); throw new BundleException("Compressor::putBuildHash failed."); } catch (NullPointerException e) { LOG.error("Compressor::putBuildHash failed, json data err: " + e.getMessage()); throw new BundleException("Compressor::putBuildHash failed, json data err."); } finally { FileUtils.closeStream(json); if (bw != null) { bw.flush(); bw.close(); } } utility.setBuildHashFinish(true); } private boolean checkAppAtomicServiceCompressedSizeValid(Utility utility) { if (!utility.getMode().equals(Utility.MODE_APP) && !utility.getMode().equals(Utility.MODE_FAST_APP)) { return true; } File destFile = new File(utility.getOutPath()); List hapVerifyInfos = new ArrayList<>(); try (ZipFile zipApp = new ZipFile(destFile)) { Enumeration entries = zipApp.entries(); while (entries.hasMoreElements()) { ZipEntry entry = entries.nextElement(); if (entry != null && hapVerifyInfoMap.containsKey(entry.getName())) { hapVerifyInfoMap.get(entry.getName()).setFileLength(entry.getCompressedSize()); hapVerifyInfos.add(hapVerifyInfoMap.get(entry.getName())); } } if (hapVerifyInfos.isEmpty()) { LOG.error("Compressor::checkAppAtomicServiceCompressedSizeValid no hapVerifyInfo."); return false; } String bundleType = hapVerifyInfos.get(0).getBundleType(); if (!bundleType.equals(ATOMIC_SERVICE)) { return true; } boolean isStage = hapVerifyInfos.get(0).isStageModule(); if (!isStage) { return true; } return HapVerify.checkFileSizeIsValid(hapVerifyInfos); } catch (IOException exception) { LOG.error("Compressor::checkAppAtomicServiceCompressedSizeValid file not found exception: " + exception.getMessage()); return false; } catch (BundleException ignored) { LOG.error("Compressor::checkAppAtomicServiceCompressedSizeValid Bundle exception."); return false; } } private static boolean checkStageHap(Utility utility) throws BundleException { Optional optional = FileUtils.getFileContent(utility.getJsonPath()); String jsonString = optional.get(); if (!checkStageAsanTsanEnabledValid(jsonString)) { LOG.error("checkStageAsanTsanEnabledValid failed."); return false; } if (!checkStageHwasanEnabledValid(jsonString)) { LOG.error("checkStageHwasanEnabledValid failed."); return false; } if (!checkStageUbsanEnabledValid(jsonString)) { LOG.error("checkStageUbsanEnabledValid failed."); return false; } // check atomicService in module.json if (!checkStageAtomicService(jsonString)) { LOG.error("checkStageAtomicService failed."); return false; } // check continueBundleName in module.json if (!checkContinueBundleNameIsValid(jsonString)) { LOG.error("checkContinueBundleNameIsValid failed."); return false; } return true; } private static boolean checkStageAsanTsanEnabledValid(String jsonString) throws BundleException { boolean asanEnabled = ModuleJsonUtil.getStageAsanEnabled(jsonString); boolean tsanEnabled = ModuleJsonUtil.getStageTsanEnabled(jsonString); if (asanEnabled && tsanEnabled) { LOG.error("asanEnabled and tsanEnabled cannot be true at the same time."); return false; } return true; } private static boolean checkStageHwasanEnabledValid(String jsonString) throws BundleException { boolean asanEnabled = ModuleJsonUtil.getStageAsanEnabled(jsonString); boolean tsanEnabled = ModuleJsonUtil.getStageTsanEnabled(jsonString); boolean gwpAsanEnabled = ModuleJsonUtil.getStageGwpAsanEnabled(jsonString); boolean hwasanEnabled = ModuleJsonUtil.getStageHwasanEnabled(jsonString); if (hwasanEnabled && asanEnabled) { LOG.error("hwasanEnabled and asanEnabled cannot be true at the same time."); return false; } if (hwasanEnabled && tsanEnabled) { LOG.error("hwasanEnabled and tsanEnabled cannot be true at the same time."); return false; } if (hwasanEnabled && gwpAsanEnabled) { LOG.error("hwasanEnabled and GWPAsanEnabled cannot be true at the same time."); return false; } return true; } private static boolean checkContinueBundleNameIsValid(String jsonString) throws BundleException { Map> continueBundleNameMap = ModuleJsonUtil.getAbilityContinueBundleNameMap(jsonString); String bundleName = ModuleJsonUtil.parseBundleName(jsonString); for (Map.Entry> entry : continueBundleNameMap.entrySet()) { List continueBundleNameList = entry.getValue(); if (continueBundleNameList == null) { continue; } for (int i = 0; i < continueBundleNameList.size(); i++) { if (bundleName.equals(continueBundleNameList.get(i))) { LOG.error("continueBundleName cannot include self."); return false; } } } return true; } private static boolean checkStageUbsanEnabledValid(String jsonString) throws BundleException { boolean asanEnabled = ModuleJsonUtil.getStageAsanEnabled(jsonString); boolean tsanEnabled = ModuleJsonUtil.getStageTsanEnabled(jsonString); boolean hwasanEnabled = ModuleJsonUtil.getStageHwasanEnabled(jsonString); boolean ubsanEnabled = ModuleJsonUtil.getStageUbsanEnabled(jsonString); if(ubsanEnabled && asanEnabled) { LOG.error("ubsanEnabled and asanEnabled cannot be true at the same time."); return false; } if(ubsanEnabled && tsanEnabled) { LOG.error("ubsanEnabled and tsanEnabled cannot be true at the same time."); return false; } if(ubsanEnabled && hwasanEnabled) { LOG.error("ubsanEnabled and hwasanEnabled cannot be true at the same time."); return false; } return true; } private static boolean checkStageAtomicService(String jsonString) throws BundleException { // check consistency of atomicService if (!ModuleJsonUtil.isModuleAtomicServiceValid(jsonString)) { LOG.error("check module atomicService failed."); return false; } // check entry module must have ability if (!ModuleJsonUtil.checkEntryInAtomicService(jsonString)) { LOG.error("checkEntryInAtomicService failed."); return false; } // check installationFree if (!ModuleJsonUtil.checkAtomicServiceInstallationFree(jsonString)) { LOG.error("check atomic service installationFree failed."); return false; } return true; } private static boolean checkStageOverlayCfg(String jsonString) throws BundleException { // check module String targetModuleName = ModuleJsonUtil.getStageTargetModuleName(jsonString); if (!targetModuleName.isEmpty()) { // check targetModuleName and requestPermission if (ModuleJsonUtil.isExistedStageRequestPermissions(jsonString)) { LOG.error("targetModuleName cannot be existed with requestPermissions."); return false; } // check targetModuleName and name if (targetModuleName.equals(ModuleJsonUtil.parseStageModuleName(jsonString))) { LOG.error("targetModuleName cannot be same with name in the overlay module."); return false; } } else { if (ModuleJsonUtil.isExistedStageModuleTargetPriority(jsonString)) { LOG.error("targetPriority cannot be existed without the targetModuleName in module.json."); return false; } } // check app String targetBundleName = ModuleJsonUtil.getStageTargetBundleName(jsonString); if (!targetBundleName.isEmpty()) { if (targetModuleName.isEmpty()) { LOG.error("targetModuleName is necessary in the overlay bundle."); return false; } if (targetBundleName.equals(ModuleJsonUtil.parseBundleName(jsonString))) { LOG.error("targetBundleName cannot be same with the bundleName."); return false; } } else { if (ModuleJsonUtil.isExistedStageAppTargetPriority(jsonString)) { LOG.error("targetPriority cannot be existed without the targetBundleName in app.json."); return false; } } return true; } private static boolean checkFAHap(Utility utility) throws BundleException { Optional optional = FileUtils.getFileContent(utility.getJsonPath()); String jsonString = optional.get(); return checkFAAsanEnabledValid(jsonString); } private static boolean checkFAAsanEnabledValid(String jsonString) throws BundleException { boolean asanEnabled = ModuleJsonUtil.getFAAsanEnabled(jsonString); boolean debug = ModuleJsonUtil.getFADebug(jsonString); if (asanEnabled && !debug) { LOG.error("asanEnabled is not supported for Release."); return false; } return true; } /** * compress in hap mode. * * @param utility common data * @throws BundleException FileNotFoundException|IOException. */ private void compressHapMode(Utility utility) throws BundleException { pathToFile(utility, utility.getJsonPath(), NULL_DIR_NAME, false); pathToFile(utility, utility.getProfilePath(), NULL_DIR_NAME, false); if (!utility.getIndexPath().isEmpty() && !utility.getModuleName().isEmpty()) { String assetsPath = ASSETS_DIR_NAME + utility.getModuleName() + LINUX_FILE_SEPARATOR; pathToFile(utility, utility.getIndexPath(), assetsPath, false); } if (!utility.getLibPath().isEmpty()) { pathToFile(utility, utility.getLibPath(), LIBS_DIR_NAME, utility.isCompressNativeLibs()); } if (!utility.getFilePath().isEmpty()) { pathToFile(utility, utility.getFilePath(), NULL_DIR_NAME, false); } if (!utility.getResPath().isEmpty() && !utility.getModuleName().isEmpty()) { String resPath = ASSETS_DIR_NAME + utility.getModuleName() + LINUX_FILE_SEPARATOR + RESOURCES_DIR_NAME; String deviceTypes = utility.getDeviceType().replace("\"", "").trim(); if (DEVICE_TYPE_FITNESSWATCH.equals(deviceTypes) || DEVICE_TYPE_FITNESSWATCH_NEW.equals(deviceTypes)) { resPath = RES_DIR_NAME; } pathToFile(utility, utility.getResPath(), resPath, false); } if (!utility.getResourcesPath().isEmpty() && !utility.getModuleName().isEmpty()) { String resourcesPath = ASSETS_DIR_NAME + utility.getModuleName() + LINUX_FILE_SEPARATOR + RESOURCES_DIR_NAME; pathToFile(utility, utility.getResourcesPath(), resourcesPath, false); } if (!utility.getRpcidPath().isEmpty()) { String rpcidPath = NULL_DIR_NAME; pathToFile(utility, utility.getRpcidPath(), rpcidPath, false); } if (!utility.getPackInfoPath().isEmpty()) { String packInfoPath = NULL_DIR_NAME; pathToFile(utility, utility.getPackInfoPath(), packInfoPath, false); } if (!utility.getAssetsPath().isEmpty()) { pathToFile(utility, utility.getAssetsPath(), ASSETS_DIR_NAME, false); } if (!utility.getBinPath().isEmpty()) { pathToFile(utility, utility.getBinPath(), NULL_DIR_NAME, false); } // pack --dir-list if (!utility.getFormatedDirList().isEmpty()) { for (int i = 0; i < utility.getFormatedDirList().size(); ++i) { String baseDir = new File(utility.getFormatedDirList().get(i)).getName() + File.separator; pathToFile(utility, utility.getFormatedDirList().get(i), baseDir, false); } } compressHapModeMultiple(utility); } /** * compress in hap mode for module.json. * * @param utility common data * @throws BundleException FileNotFoundException|IOException. */ private void compressHapModeForModule(Utility utility) throws BundleException { pathToFile(utility, utility.getJsonPath(), NULL_DIR_NAME, false); pathToFile(utility, utility.getProfilePath(), NULL_DIR_NAME, false); if (!utility.getIndexPath().isEmpty() && isModuleJSON(utility.getJsonPath())) { String assetsPath = NULL_DIR_NAME; pathToFile(utility, utility.getIndexPath(), assetsPath, false); } if (!utility.getLibPath().isEmpty()) { pathToFile(utility, utility.getLibPath(), LIBS_DIR_NAME, utility.isCompressNativeLibs()); } if (!utility.getANPath().isEmpty()) { pathToFile(utility, utility.getANPath(), AN_DIR_NAME, false); } if (!utility.getAPPath().isEmpty()) { pathToFile(utility, utility.getAPPath(), AP_PATH_NAME, false); } if (!utility.getFilePath().isEmpty()) { pathToFile(utility, utility.getFilePath(), NULL_DIR_NAME, false); } if (!utility.getResPath().isEmpty() && !utility.getModuleName().isEmpty()) { String resPath = ASSETS_DIR_NAME + utility.getModuleName() + LINUX_FILE_SEPARATOR + RESOURCES_DIR_NAME; String deviceTypes = utility.getDeviceType().replace("\"", "").trim(); if (DEVICE_TYPE_FITNESSWATCH.equals(deviceTypes) || DEVICE_TYPE_FITNESSWATCH_NEW.equals(deviceTypes)) { resPath = RES_DIR_NAME; } pathToFile(utility, utility.getResPath(), resPath, false); } if (!utility.getResourcesPath().isEmpty() && isModuleJSON(utility.getJsonPath())) { String resourcesPath = RESOURCES_DIR_NAME; pathToFile(utility, utility.getResourcesPath(), resourcesPath, false); } if (!utility.getJsPath().isEmpty() && isModuleJSON(utility.getJsonPath())) { String jsPath = JS_PATH; pathToFile(utility, utility.getJsPath(), jsPath, false); } if (!utility.getEtsPath().isEmpty() && isModuleJSON(utility.getJsonPath())) { String etsPath = ETS_PATH; pathToFile(utility, utility.getEtsPath(), etsPath, false); } if (!utility.getHnpPath().isEmpty() && isModuleJSON(utility.getJsonPath())) { String hnpPath = HNP_PATH; pathToFile(utility, utility.getHnpPath(), hnpPath, false); } if (!utility.getRpcidPath().isEmpty()) { String rpcidPath = NULL_DIR_NAME; pathToFile(utility, utility.getRpcidPath(), rpcidPath, false); } if (!utility.getAssetsPath().isEmpty()) { pathToFile(utility, utility.getAssetsPath(), ASSETS_DIR_NAME, false); } if (!utility.getBinPath().isEmpty()) { pathToFile(utility, utility.getBinPath(), NULL_DIR_NAME, false); } if (!utility.getPackInfoPath().isEmpty()) { pathToFile(utility, utility.getPackInfoPath(), NULL_DIR_NAME, false); } // pack --dir-list if (!utility.getFormatedDirList().isEmpty()) { for (int i = 0; i < utility.getFormatedDirList().size(); ++i) { String baseDir = new File(utility.getFormatedDirList().get(i)).getName() + File.separator; pathToFile(utility, utility.getFormatedDirList().get(i), baseDir, false); } } if (!utility.getPkgContextPath().isEmpty()) { pathToFile(utility, utility.getPkgContextPath(), NULL_DIR_NAME, false); } compressHapModeMultiple(utility); } /** * compress in hap mode multiple path. * * @param utility common data * @throws BundleException FileNotFoundException|IOException. */ private void compressHapModeMultiple(Utility utility) throws BundleException { for (String soPathItem : utility.getFormattedSoPathList()) { pathToFile(utility, soPathItem, SO_ARM64_DIR_NAME, false); } if (utility.getFormattedSoPathList().size() == 0 && !utility.getSoDir().isEmpty()) { pathToFile(utility, utility.getSoDir(), SO_DIR_NAME, false); } for (String soPathItem : utility.getFormattedAbilitySoPathList()) { pathToFile(utility, soPathItem, NULL_DIR_NAME, false); } for (String dexPathItem : utility.getFormattedDexPathList()) { pathToFile(utility, dexPathItem, NULL_DIR_NAME, false); } for (String abcPathItem : utility.getFormattedAbcPathList()) { pathToFile(utility, abcPathItem, NULL_DIR_NAME, false); } for (String apkPathItem : utility.getFormattedApkPathList()) { pathToFile(utility, apkPathItem, NULL_DIR_NAME, false); } for (String jarPathItem : utility.getFormattedJarPathList()) { pathToFile(utility, jarPathItem, NULL_DIR_NAME, false); } for (String txtPathItem : utility.getFormattedTxtPathList()) { pathToFile(utility, txtPathItem, NULL_DIR_NAME, false); } if (!utility.getSharedLibsPath().isEmpty()) { pathToFile(utility, utility.getSharedLibsPath(), SHARED_LIBS_DIR_NAME, utility.isCompressNativeLibs()); } } /** * compress in har mode. * * @param utility common data * @throws BundleException FileNotFoundException|IOException. */ private void compressHarMode(Utility utility) throws BundleException { pathToFile(utility, utility.getJsonPath(), NULL_DIR_NAME, false); if (!utility.getLibPath().isEmpty()) { pathToFile(utility, utility.getLibPath(), LIBS_DIR_NAME, utility.isCompressNativeLibs()); } if (!utility.getResPath().isEmpty()) { pathToFile(utility, utility.getResPath(), RESOURCES_DIR_NAME, false); } if (!utility.getResourcesPath().isEmpty()) { pathToFile(utility, utility.getResourcesPath(), RESOURCES_DIR_NAME, false); } if (!utility.getAssetsPath().isEmpty()) { pathToFile(utility, utility.getAssetsPath(), ASSETS_DIR_NAME, false); } for (String jarPathItem : utility.getFormattedJarPathList()) { pathToFile(utility, jarPathItem, NULL_DIR_NAME, false); } for (String txtPathItem : utility.getFormattedTxtPathList()) { pathToFile(utility, txtPathItem, NULL_DIR_NAME, false); } } /** * compress in app mode. * * @param utility common data * @throws BundleException FileNotFoundException|IOException. */ private void compressAppMode(Utility utility) throws BundleException { List fileList = new ArrayList<>(); File appOutputFile = new File(utility.getOutPath().trim()); String tempPath = appOutputFile.getParentFile().getParent() + File.separator + TEMP_HAP_DIR; String hspTempDirPath = appOutputFile.getParentFile().getParent() + File.separator + TEMP_HSP_DIR; try { pathToFile(utility, utility.getJsonPath(), NULL_DIR_NAME, false); if (!utility.getCertificatePath().isEmpty()) { pathToFile(utility, utility.getCertificatePath(), NULL_DIR_NAME, false); } if (!utility.getSignaturePath().isEmpty()) { pathToFile(utility, utility.getSignaturePath(), NULL_DIR_NAME, false); } File tempDir = new File(tempPath); if (!tempDir.exists()) { tempDir.mkdirs(); } for (String hapPathItem : utility.getFormattedHapPathList()) { File hapFile = new File(hapPathItem.trim()); String hapTempPath = tempDir + File.separator + hapFile.getName(); fileList.add(hapTempPath); try { compressPackinfoIntoHap(hapPathItem, hapTempPath, utility.getPackInfoPath(), utility.getCompressLevel()); } catch (IOException e) { LOG.error("Compressor::compressAppMode compress pack.info into hap failed: " + e.getMessage()); throw new BundleException("Compressor::compressAppMode compress pack.info into hap failed."); } } File hspTempDir = new File(hspTempDirPath); if (!hspTempDir.exists()) { hspTempDir.mkdirs(); } for (String hspPathItem : utility.getFormattedHspPathList()) { File hspFile = new File(hspPathItem.trim()); String hspTempPath = hspTempDir + File.separator + hspFile.getName(); fileList.add(hspTempPath); try { compressPackinfoIntoHap(hspPathItem, hspTempPath, utility.getPackInfoPath(), utility.getCompressLevel()); } catch (IOException e) { LOG.error("Compressor::compressAppMode compress pack.info into hsp failed: " + e.getMessage()); throw new BundleException("Compressor::compressAppMode compress pack.info into hsp failed."); } } // check hap is valid if (!checkHapIsValid(fileList, utility.getSharedApp())) { LOG.error("Compressor::compressFile verify failed, check version, " + "apiVersion,moduleName,packageName."); throw new BundleException("Compressor::compressFile verify failed, check version, " + "apiVersion,moduleName,packageName."); } for (String hapPath : fileList) { HapVerifyInfo hapVerifyInfo = hapVerifyInfoMap.get(getFileNameByPath(hapPath)); if (hapVerifyInfo != null && !hapVerifyInfo.isDebug()) { pathToFile(utility, hapPath, NULL_DIR_NAME, true); } else { pathToFile(utility, hapPath, NULL_DIR_NAME, false); } } if (!utility.getEntryCardPath().isEmpty()) { String entryCardPath = ENTRYCARD_NAME + utility.getModuleName() + LINUX_FILE_SEPARATOR + ENTRYCARD_BASE_NAME + ENTRYCARD_SNAPSHOT_NAME; for (String entryCardPathItem : utility.getformattedEntryCardPathList()) { pathToFile(utility, entryCardPathItem, entryCardPath, true); } } if (!utility.getPackResPath().isEmpty()) { pathToFile(utility, utility.getPackResPath(), NULL_DIR_NAME, false); } File file = new File(utility.getPackInfoPath()); compressFile(utility, file, NULL_DIR_NAME, false); //pack encrypt.json file packEncryptJsonFile(utility); } catch (BundleException e) { LOG.error("Compressor::compressAppMode compress failed. msg: " + e.getMessage()); throw new BundleException("Compressor::compressAppMode compress failed."); } finally { // delete temp file for (String path : fileList) { deleteFile(path); } deleteFile(tempPath); deleteFile(hspTempDirPath); } } private void compressFastAppMode(Utility utility) throws BundleException { Path appOutPath = Paths.get(utility.getOutPath().trim()); Path tmpDir = null; try { tmpDir = Files.createTempDirectory(appOutPath.getParent(), TEMP_DIR); Path appPackInfo = Paths.get(utility.getPackInfoPath()); List fileList = new ArrayList<>(); for (String hapPath : utility.getFormattedHapPathList()) { Path path = Paths.get(hapPath); Path hap = PackageUtil.pack(path, appPackInfo, tmpDir, utility.getCompressLevel()); if (hap != null) { fileList.add(hap.toString()); } } for (String hspPath : utility.getFormattedHspPathList()) { Path path = Paths.get(hspPath); Path hsp = PackageUtil.pack(path, appPackInfo, tmpDir, utility.getCompressLevel()); if (hsp != null) { fileList.add(hsp.toString()); } } // check hap is valid if (!checkHapIsValid(fileList, utility.getSharedApp())) { String msg = "Compressor::checkHapIsValid verify failed, check version, " + "apiVersion, moduleName, packageName."; LOG.error(msg); throw new BundleException(msg); } // packApp packFastApp(utility, fileList); } catch (IOException ex) { LOG.error("Compressor::compressAppMode compress failed: " + ex.getMessage()); throw new BundleException("Compressor::compressAppMode compress failed."); } finally { if (tmpDir != null) { PackageUtil.rmdir(tmpDir); } } } private void packFastApp(Utility utility, List fileList) throws BundleException { // pack.info pathToFile(utility, utility.getPackInfoPath(), NULL_DIR_NAME, false); // pack encrypt.json file packEncryptJsonFile(utility); // hap/hsp for (String hapPath : fileList) { HapVerifyInfo hapVerifyInfo = hapVerifyInfoMap.get(getFileNameByPath(hapPath)); if (hapVerifyInfo != null && !hapVerifyInfo.isDebug()) { pathToFile(utility, hapPath, NULL_DIR_NAME, true); } else { pathToFile(utility, hapPath, NULL_DIR_NAME, false); } } // form/card if (!utility.getEntryCardPath().isEmpty()) { String entryCardPath = ENTRYCARD_NAME + utility.getModuleName() + LINUX_FILE_SEPARATOR + ENTRYCARD_BASE_NAME + ENTRYCARD_SNAPSHOT_NAME; for (String entryCardPathItem : utility.getformattedEntryCardPathList()) { pathToFile(utility, entryCardPathItem, entryCardPath, true); } } if (!utility.getPackResPath().isEmpty()) { pathToFile(utility, utility.getPackResPath(), NULL_DIR_NAME, false); } // others if (!utility.getCertificatePath().isEmpty()) { pathToFile(utility, utility.getCertificatePath(), NULL_DIR_NAME, false); } if (!utility.getSignaturePath().isEmpty()) { pathToFile(utility, utility.getSignaturePath(), NULL_DIR_NAME, false); } } /** * compress in app mode for multi project. * * @param utility common data * @throws BundleException FileNotFoundException|IOException. */ private void compressAppModeForMultiProject(Utility utility) throws BundleException { List fileList = new ArrayList<>(); File appOutputFile = new File(utility.getOutPath().trim()); String tempPath = appOutputFile.getParentFile().getParent() + File.separator + TEMP_HAP_DIR; String tempSelectedHapPath = appOutputFile.getParentFile().getParent() +File.separator + TEMP_SELECTED_HAP_DIR; try { File tempSelectedHapDir = new File(tempSelectedHapPath); FileUtils.makeDir(tempSelectedHapDir); File tempHapDir = new File(tempPath); FileUtils.makeDir(tempHapDir); // pack app and dispose conflict // save hap name into list List seletedHaps = new ArrayList<>(); String finalPackInfoStr = disposeApp(utility, seletedHaps, tempSelectedHapPath); // pack hap and dispose conflict finalPackInfoStr = disposeHap(utility, seletedHaps, tempSelectedHapPath, finalPackInfoStr); // save final pack.info file String finalPackInfoPath = tempSelectedHapDir.getPath() + File.separator + PACKINFO_NAME; writePackInfo(finalPackInfoPath, finalPackInfoStr); // pack haps for (String selectedHapName : seletedHaps) { String hapPathItem = tempSelectedHapDir.getPath() + File.separator + selectedHapName; File hapFile = new File(hapPathItem.trim()); String hapTempPath = tempHapDir.getPath() + File.separator + hapFile.getName(); fileList.add(hapTempPath); compressPackinfoIntoHap(hapPathItem, hapTempPath, finalPackInfoPath, utility.getCompressLevel()); } // check hap is valid if (!checkHapIsValid(fileList, false)) { String errMsg = "Compressor::compressAppModeForMultiProject There are some " + "haps with different version code or duplicated moduleName or packageName."; throw new BundleException(errMsg); } for (String hapPath : fileList) { pathToFile(utility, hapPath, NULL_DIR_NAME, false); } File file = new File(finalPackInfoPath); compressFile(utility, file, NULL_DIR_NAME, false); //pack encrypt.json file packEncryptJsonFile(utility); } catch (BundleException | IOException exception) { String errMsg = "Compressor::compressAppModeForMultiProject file failed: " + exception.getMessage(); LOG.error(errMsg); throw new BundleException(errMsg); } finally { deleteFile(tempPath); deleteFile(tempSelectedHapPath); } } /** * compress in hapAddition mode. * * @param utility common data */ private void hapAddition(Utility utility) { File hapPath = new File(utility.getAbsoluteHapPath()); String hapFileName = hapPath.getName(); File destFile = new File(utility.getOutPath() + LINUX_FILE_SEPARATOR + hapFileName); File outParentFile = destFile.getParentFile(); if ((outParentFile != null) && (!outParentFile.exists())) { if (!outParentFile.mkdirs()) { LOG.error("Compressor::hapAddition create out file parent directory failed."); } } FileOutputStream fileOut = null; CheckedOutputStream checkedOut = null; String currentDir = System.getProperty("user.dir"); String hapAdditionPath = currentDir + LINUX_FILE_SEPARATOR + HAPADDITION_FOLDER_NAME; String backName = BACKUP_PREFIX + hapFileName; String hapPathOri = utility.getHapPath(); try { copyHapFile(utility, backName); fileOut = new FileOutputStream(destFile); checkedOut = new CheckedOutputStream(fileOut, new CRC32()); zipOut = new ZipArchiveOutputStream(checkedOut); compressHapAddition(utility, hapAdditionPath); } catch (BundleException | IOException exception) { LOG.error("Compressor::HapAddition hapFile not found exception" + exception.getMessage()); copyFileSafely(backName, hapPathOri); } finally { closeZipOutputStream(); Utility.closeStream(zipOut); Utility.closeStream(checkedOut); Utility.closeStream(fileOut); // delete packaging and unpacking process files deleteFile(backName); deleteFile(hapAdditionPath); } } private void copyHapFile(Utility utility, String backName) throws IOException, BundleException { File hapFile = new File(utility.getAbsoluteHapPath()); String currentDir = System.getProperty("user.dir"); String backupPath = currentDir + LINUX_FILE_SEPARATOR + backName; File backupFile = new File(backupPath); FileUtils.copyFile(hapFile, backupFile); utility.setHapPath(backName); } private void copyFileSafely(String source, String dest) { try { File sourceFile = new File(source); File destFile = new File(dest); FileUtils.copyFile(sourceFile, destFile); } catch (IOException | BundleException e) { LOG.error("copyFileSafely failed: " + e.getMessage()); } } private static String readerFile(String jsonPath) throws IOException { byte[] bytes = Files.readAllBytes(Paths.get(jsonPath)); return new String(bytes, StandardCharsets.UTF_8); } private static void writeJsonFile(String dataJson, String targetPath) throws BundleException { try (FileWriter fileWriter = new FileWriter(targetPath)) { Object object = JSON.parse(dataJson); String jsonString = new String(); if (object instanceof JSONArray) { JSONArray jsonArray = JSONArray.parseArray(dataJson); jsonString = JSON.toJSONString( jsonArray, SerializerFeature.PrettyFormat, SerializerFeature.WriteMapNullValue); } else { JSONObject jsonObject = JSONObject.parseObject(dataJson); jsonString = JSON.toJSONString( jsonObject, SerializerFeature.PrettyFormat, SerializerFeature.WriteMapNullValue); } fileWriter.write(jsonString); fileWriter.flush(); } catch (IOException exception) { LOG.error("writeJsonFile failed, " + exception.getMessage()); throw new BundleException(exception.getMessage()); } catch (JSONException e) { LOG.error("json file is invalid: " + e.getMessage()); throw new BundleException(e.getMessage()); } } private static void setUtilityParameter(String hapAdditionPath, Utility utility) throws IOException { Path basePath = Paths.get(hapAdditionPath); try (Stream pathStream = Files.walk(basePath, 1)) { pathStream.forEach(path -> { String fileName = path.getFileName().toString(); String filePath = path.toString(); switch (fileName) { case ETS_FILE_NAME: utility.setEtsPath(filePath); break; case DIR_FILE_NAME: utility.setLibPath(filePath); break; case AN_FILE_NAME: utility.setANPath(filePath); break; case AP_FILE_NAME: utility.setAPPath(filePath); break; case RESOURCE_FILE_NAME: utility.setResourcesPath(filePath); break; case JS_FILE_NAME: utility.setJsPath(filePath); break; case ASSETS_FILE_NAME: utility.setAssetsPath(filePath); break; case MAPLE_FILE_NAME: utility.setSoDir(filePath); break; case SHARED_LIBS_FILE_NAME: utility.setSharedLibsPath(filePath); break; case MODULE_JSON: utility.setJsonPath(filePath); break; case RES_INDEX: utility.setIndexPath(filePath); break; case PACKINFO_NAME: utility.setPackInfoPath(filePath); break; } }); } } private void compressHapAddition(Utility utility, String hapAdditionPath) throws BundleException { // decompression hap file to hapAddition unpackHap(utility.getHapPath(), hapAdditionPath); // generate addition.json file try { String data = readerFile(utility.getJsonPath()); String currentDir = System.getProperty("user.dir"); String targetParentPath = currentDir + LINUX_FILE_SEPARATOR + TARGET_FILE_PATH; File targetParent = new File(targetParentPath); if (!targetParent.exists()) { if (!targetParent.mkdirs()) { LOG.error("Compressor::compressHapAddition create target file parent directory failed."); } } String targetPath = targetParentPath + LINUX_FILE_SEPARATOR + ADDITION_JSON; writeJsonFile(data, targetPath); } catch (IOException | JSONException | BundleException e) { String errMsg = "Compressor::compressHapAddition generate addition.json file failed, " + e.getMessage(); LOG.error(errMsg); throw new BundleException(errMsg); } // package a new hap file try { setUtilityParameter(hapAdditionPath, utility); } catch (IOException e) { String errMsg = "Compressor::compressHapAddition setUtilityParameter failed."; LOG.error(errMsg); throw new BundleException(errMsg); } if (utility.getHapPath().endsWith(HAP_SUFFIX)) { compressHap(utility); } else if (utility.getHapPath().endsWith(HSP_SUFFIX)) { compressHsp(utility); } else { String errMsg = "Compressor::compressHapAddition compressFile failed."; LOG.error(errMsg); throw new BundleException(errMsg); } } /** * pack hap in app to selectedHaps * * @param utility is common data * @param seletedHaps is seleted haps should be pack into app * @return the final pack.info string after dispose app * @throws BundleException FileNotFoundException|IOException. */ private static String disposeApp(Utility utility, List seletedHaps, String tempDir) throws BundleException { // dispose app conflict if (utility.getFormattedAppList().isEmpty()) { return ""; } String finalAppPackInfo = ""; try { for (String appPath : utility.getFormattedAppList()) { // select hap in app finalAppPackInfo = selectHapInApp(appPath, seletedHaps, tempDir, finalAppPackInfo); } } catch (BundleException | IOException e) { String errMsg = "Compressor:disposeApp disposeApp failed."; LOG.error(errMsg); throw new BundleException(errMsg); } return finalAppPackInfo; } /** * select hap from app file list * * @param appPath is common data * @param selectedHaps is list of packInfos * @throws BundleException FileNotFoundException|IOException. */ private static String selectHapInApp(String appPath, List selectedHaps, String tempDir, String finalAppPackInfo) throws BundleException, IOException { List selectedHapsInApp = new ArrayList<>(); // classify hap in app copyHapAndHspFromApp(appPath, selectedHapsInApp, selectedHaps, tempDir); // rebuild pack.info String packInfoStr = FileUtils.getJsonInZips(new File(appPath), PACKINFO_NAME); if (packInfoStr.isEmpty()) { String errorMsg = "Compressor:selectHapInApp failed, app has no pack.info."; LOG.error(errorMsg); throw new BundleException(errorMsg); } if (finalAppPackInfo.isEmpty()) { finalAppPackInfo = packInfoStr; return finalAppPackInfo; } // read selected module in temp hap HashMap packagePair = new HashMap<>(); for (String hapName : selectedHapsInApp) { packagePair.put(hapName, readModlueNameFromHap(tempDir + File.separator + hapName)); } return ModuleJsonUtil.mergeTwoPackInfoByPackagePair(finalAppPackInfo, packInfoStr, packagePair); } /** * copy hap from app file * * @param appPath is common data * @param selectedHapsInApp is list of haps and hsps selected in app file * @param selectedHaps is the list of haps and hsps selected in input * @throws BundleException FileNotFoundException|IOException. */ private static void copyHapAndHspFromApp(String appPath, List selectedHapsInApp, List selectedHaps, String tempDir) throws BundleException { ZipInputStream zipInput = null; ZipFile zipFile = null; OutputStream outputStream = null; InputStream inputStream = null; ZipEntry zipEntry = null; try { zipInput = new ZipInputStream(new FileInputStream(appPath)); zipFile = new ZipFile(appPath); while ((zipEntry = zipInput.getNextEntry()) != null) { File file = null; if (!zipEntry.getName().endsWith(HAP_SUFFIX) && !zipEntry.getName().endsWith(HSP_SUFFIX)) { continue; } // copy duplicated hap to duplicated dir and get moduleName of duplicated hap if (selectedHaps.contains(zipEntry.getName())) { LOG.error("Compressor::copyHapFromApp file duplicated, file is " + zipEntry.getName() + "."); throw new BundleException("Compressor::copyHapFromApp file duplicated, file is " + zipEntry.getName() + "."); } else { // copy selectedHap to tempDir file = new File(tempDir + File.separator + zipEntry.getName()); selectedHaps.add(file.getName()); selectedHapsInApp.add(file.getName()); } outputStream = new FileOutputStream(file); inputStream = zipFile.getInputStream(zipEntry); int len; byte[] buf = new byte[BUFFER_SIZE]; while ((len = inputStream.read(buf)) != -1) { outputStream.write(buf, 0, len); } outputStream.close(); inputStream.close(); } } catch (IOException e) { String errMsg = "Compressor:copyHapFromApp app path not found."; LOG.error(errMsg); throw new BundleException(errMsg); } finally { Utility.closeStream(zipInput); Utility.closeStream(zipFile); Utility.closeStream(outputStream); Utility.closeStream(inputStream); } } /** * read moduleName in hap * * @param hapPath is path of hap file * @throws BundleException FileNotFoundException|IOException. */ private static String readModlueNameFromHap(String hapPath) throws BundleException { String moduleName = ""; File hapFile = new File(hapPath); if (isModuleHap(hapPath)) { String jsonString = FileUtils.getJsonInZips(hapFile, MODULE_JSON); moduleName = ModuleJsonUtil.parseStageModuleName(jsonString); } else { String jsonString = FileUtils.getJsonInZips(hapFile, CONFIG_JSON); moduleName = ModuleJsonUtil.parseFaModuleName(jsonString); } return moduleName; } /** * dispose input of hap * * @param utility is common data * @param seletedHaps is the selected haps of all input * @param tempDir is the path of temp directory * @param finalPackInfoStr is the pack.info of the final app * @throws BundleException FileNotFoundException|IOException. */ private static String disposeHap(Utility utility, List seletedHaps, String tempDir, String finalPackInfoStr) throws BundleException, IOException { // dispose hap conflict if (utility.getFormattedHapList().isEmpty()) { return finalPackInfoStr; } for (String hapPath : utility.getFormattedHapList()) { if (seletedHaps.contains(new File(hapPath).getName())) { LOG.error("Compressor::disposeHap file duplicated, file is " + new File(hapPath).getName() + "."); throw new BundleException("Compressor::disposeHap file duplicated, file is " + new File(hapPath).getName() + "."); } File hapFile = new File(hapPath); seletedHaps.add(hapFile.getName()); // copy hap to tempDir FileUtils.copyFile(hapFile, new File((tempDir +File.separator + hapFile.getName()))); String packInfo = FileUtils.getJsonInZips(hapFile, PACKINFO_NAME); if (packInfo.isEmpty()) { String errMsg = "Compressor::disposeHap failed, hap has no pack.info."; LOG.error(errMsg); throw new BundleException(errMsg); } if (finalPackInfoStr.isEmpty()) { finalPackInfoStr = packInfo; } else { finalPackInfoStr = ModuleJsonUtil.mergeTwoPackInfo(finalPackInfoStr, packInfo); } } return finalPackInfoStr; } /** * write string to pack.info * * @param filePath pack.info file path * @param packInfoStr is the string of pack.info * @throws BundleException FileNotFoundException|IOException. */ private void writePackInfo(String filePath, String packInfoStr) throws BundleException, IOException { FileWriter fwriter = null; try { fwriter = new FileWriter(filePath); fwriter.write(packInfoStr); } catch (IOException e) { String errMsg = "Compressor:writePackInfo failed."; LOG.error(errMsg); throw new BundleException(errMsg); } finally { if (fwriter != null) { fwriter.flush(); fwriter.close(); } } } private void copy(InputStream input, OutputStream output) throws IOException { int bytesRead; byte[] data = new byte[BUFFER_SIZE]; while ((bytesRead = input.read(data, 0, BUFFER_SIZE)) != -1) { output.write(data, 0, bytesRead); } } private void compressPackinfoIntoHap(String hapPathItem, String outPathString, String packInfo, int compressLevel) throws IOException, BundleException { ZipFile sourceHapFile = new ZipFile(hapPathItem); ZipOutputStream append = new ZipOutputStream(new FileOutputStream(outPathString)); append.setLevel(compressLevel); try { Enumeration entries = sourceHapFile.entries(); while (entries.hasMoreElements()) { ZipEntry zipEntry = new ZipEntry(entries.nextElement()); if (PACKINFO_NAME.equals(zipEntry.getName())) { continue; } ZipEntry newEntry = zipEntry.getMethod() == ZipEntry.STORED ? new ZipEntry(zipEntry) : new ZipEntry(zipEntry.getName()); append.putNextEntry(newEntry); if (!zipEntry.isDirectory()) { copy(sourceHapFile.getInputStream(zipEntry), append); } append.closeEntry(); } File packInfoFile = new File(packInfo); ZipEntry zipEntry = getStoredZipEntry(packInfoFile, PACKINFO_NAME); append.putNextEntry(zipEntry); FileInputStream in = new FileInputStream(packInfoFile); try { byte[] buf = new byte[BUFFER_SIZE]; int len; while ((len = in.read(buf)) != -1) { append.write(buf, 0, len); } } finally { in.close(); } append.closeEntry(); } catch (IOException exception) { LOG.error("Compressor::compressPackinfoIntoHap io exception."); throw new BundleException("Compressor::compressPackinfoIntoHap io exception."); } finally { sourceHapFile.close(); append.close(); } } /** * delete file * * @param path file path which will be deleted */ private static void deleteFile(final String path) { File file = new File(path); if (file.exists()) { if (file.isDirectory()) { File[] files = file.listFiles(); for (int i = 0; i < files.length; i++) { deleteFile(files[i].toString()); } } file.delete(); } } /** * compress in res mode. * * @param utility common data * @throws BundleException FileNotFoundException|IOException. */ private void compressPackResMode(Utility utility) throws BundleException { if (!utility.getPackInfoPath().isEmpty()) { parsePackInfoJsonFile(utility, utility.getPackInfoPath()); File file = new File(utility.getPackInfoPath()); infoSpecialProcess(utility, file); } if (!utility.getEntryCardPath().isEmpty()) { getFileList(utility.getEntryCardPath()); List snapshotNameList = new ArrayList<>(); for (String fileName : fileNameList) { if (fileName.endsWith(PNG_SUFFIX) || fileName.endsWith(UPPERCASE_PNG_SUFFIX)) { String fName = fileName.trim(); String[] temp = fName.replace("\\", "/").split("/"); if (temp.length < 4) { LOG.error("Compressor::compressPackResMode the hap file path is invalid, length: " + temp.length + "."); continue; } String fileLanguageCountryName = temp[temp.length - 3]; if (!isThirdLevelDirectoryNameValid(fileLanguageCountryName)) { LOG.error("Compressor::compressProcess compress failed third level directory name: " + fileLanguageCountryName + " is invalid, please check it with reference to this example: " + "zh_Hani_CN-vertical-car-mdpi-dark or zh_Hani_CN-vertical-car-mdpi."); throw new BundleException("Compress failed third level directory name Error."); } String filePicturingName = temp[temp.length - 1]; if (!isPicturing(filePicturingName, utility)) { LOG.error("Compressor::compressProcess Compress pack.res failed, Invalid resource file" + " name: " + filePicturingName + ", correct format example is formName-2x2.png."); throw new BundleException("Compress pack.res failed, Invalid resource file name: " + filePicturingName + ", correct format example is formName-2x2.png."); } String fullSnapshotName = temp[temp.length - 4] + "/" + filePicturingName.substring(0, filePicturingName.lastIndexOf(".")); snapshotNameList.add(fullSnapshotName); } else { LOG.error("Compressor::compressProcess compress failed No image in PNG format is found."); throw new BundleException("Compress pack.res failed, compress failed No image in" + " PNG format is found."); } } for (String formName : formNamesList) { if (!snapshotNameList.contains(formName)) { LOG.error("Compressor::compressProcess compress failed entryCard " + formName + " has no related snapshot " + snapshotNameList.toString() + "."); throw new BundleException("Compress pack.res failed, compress failed entryCard has no related snapshot."); } } pathToFileResMode(utility, utility.getEntryCardPath(), ENTRYCARD_NAME, false); } } /** * Check whether modelname meets specifications. * * @param name modelName * @return false and true */ private boolean isModelName(String name) { for (String listName : list) { if (name.equals(listName)) { return true; } } return false; } private boolean isThirdLevelDirectoryNameValid(String thirdLevelDirectoryName) { if (thirdLevelDirectoryName == null || thirdLevelDirectoryName.isEmpty()) { return false; } if (ENTRYCARD_BASE_NAME.equals(thirdLevelDirectoryName)) { return true; } // example: zh_Hani_CN-vertical-car-mdpi-dark or zh_Hani_CN-vertical-car-mdpi int firstDelimiterIndex = thirdLevelDirectoryName.indexOf("_"); if (firstDelimiterIndex < 0) { return false; } String language = thirdLevelDirectoryName.substring(0, firstDelimiterIndex); int secondDelimiterIndex = thirdLevelDirectoryName.indexOf("_", firstDelimiterIndex + 1); if (secondDelimiterIndex < 0) { return false; } String script = thirdLevelDirectoryName.substring(firstDelimiterIndex + 1, secondDelimiterIndex); int thirdDelimiterIndex = thirdLevelDirectoryName.indexOf("-", secondDelimiterIndex + 1); if (thirdDelimiterIndex < 0) { return false; } String country = thirdLevelDirectoryName.substring(secondDelimiterIndex + 1, thirdDelimiterIndex); if (!checkLanguage(language) || !checkScript(script) || !checkCountry(country)) { return false; } int forthDelimiterIndex = thirdLevelDirectoryName.indexOf("-", thirdDelimiterIndex + 1); if (forthDelimiterIndex < 0) { return false; } String orientation = thirdLevelDirectoryName.substring(thirdDelimiterIndex + 1, forthDelimiterIndex); int fifthDelimiterIndex = thirdLevelDirectoryName.indexOf("-", forthDelimiterIndex + 1); if (fifthDelimiterIndex < 0) { return false; } String deviceType = thirdLevelDirectoryName.substring(forthDelimiterIndex + 1, fifthDelimiterIndex); if (!checkOrientation(orientation) || !checkDeviceType(deviceType)) { return false; } int sixthDelimiterIndex = thirdLevelDirectoryName.indexOf("-", fifthDelimiterIndex + 1); if (sixthDelimiterIndex < 0) { String screenDensity = thirdLevelDirectoryName.substring(fifthDelimiterIndex + 1, thirdLevelDirectoryName.length()); return checkScreenDensity(screenDensity); } else { String screenDensity = thirdLevelDirectoryName.substring(fifthDelimiterIndex + 1, sixthDelimiterIndex); if (!checkScreenDensity(screenDensity)) { return false; } } int seventhDelimiterIndex = thirdLevelDirectoryName.indexOf("-", sixthDelimiterIndex + 1); if (seventhDelimiterIndex < 0) { String tmp = thirdLevelDirectoryName.substring(sixthDelimiterIndex + 1, thirdLevelDirectoryName.length()); return checkColorModeOrShape(tmp); } if (!checkColorMode(thirdLevelDirectoryName.substring(sixthDelimiterIndex + 1, seventhDelimiterIndex))) { return false; } String shape = thirdLevelDirectoryName.substring(seventhDelimiterIndex + 1, thirdLevelDirectoryName.length()); return checkShape(shape); } private boolean checkLanguage(String language) { if (!Pattern.compile(REGEX_LANGUAGE).matcher(language).matches()) { LOG.error("Compressor::compressProcess language " + language + " is not in ISO 639-1 list."); return false; } return true; } private boolean checkScript(String script) { if (!Pattern.compile(REGEX_SCRIPT).matcher(script).matches()) { LOG.error("Compressor::compressProcess script " + script + " is not in ISO 15924 list."); return false; } return true; } private boolean checkCountry(String country) { if (!Pattern.compile(REGEX_COUNTRY).matcher(country).matches()) { LOG.error("Compressor::compressProcess country " + country + " is not in ISO 3166-1 list."); return false; } return true; } private boolean checkOrientation(String orientation) { if (!Pattern.compile(REGEX_ORIENTATION).matcher(orientation).matches()) { LOG.error("Compressor::compressProcess orientation " + orientation + " is not in {vertical, horizontal} list."); return false; } return true; } private boolean checkDeviceType(String deviceType) { if (!Pattern.compile(REGEX_DEVICE_TYPE).matcher(deviceType).matches()) { LOG.error("Compressor::compressProcess deviceType " + deviceType + " is not in {phone, tablet, car, tv, wearable, liteWearable, 2in1} list."); return false; } return true; } private boolean checkScreenDensity(String screenDensity) { if (!Pattern.compile(REGEX_SCREEN_DENSITY).matcher(screenDensity).matches()) { LOG.error("Compressor::compressProcess screenDensity " + screenDensity + " is not in {sdpi, mdpi, ldpi, xldpi, xxldpi} list."); return false; } return true; } private boolean checkColorMode(String colorMode) { if (!Pattern.compile(REGEX_COLOR_MODE).matcher(colorMode).matches()) { LOG.error("Compressor::compressProcess colorMode " + colorMode + " is not in {light, dark} list."); return false; } return true; } private boolean checkColorModeOrShape(String tmp) { if (Pattern.compile(REGEX_COLOR_MODE).matcher(tmp).matches() || Pattern.compile(REGEX_SHAPE).matcher(tmp).matches()) { return true; } LOG.error("Compressor::compressProcess " + tmp + " is neither in colorMode list {light, dark} nor in shape list {circle}."); return false; } private boolean checkShape(String shape) { if (Pattern.compile(REGEX_SHAPE).matcher(shape).matches()) { return true; } LOG.error("Compressor::compressProcess shape" + shape + " is not in {circle} list."); return false; } /** * Check whether picturingName meets specifications. * * @param name picturingName * @param utility common data * @return false and true */ private boolean isPicturing(String name, Utility utility) { boolean isSpecifications = false; if (name == null || name.isEmpty()) { return isSpecifications; } if (!name.endsWith(PNG_SUFFIX) && !name.endsWith(UPPERCASE_PNG_SUFFIX)) { LOG.error("isPicturing: the suffix is not .png or .PNG."); return false; } int delimiterIndex = name.lastIndexOf("-"); if (delimiterIndex < 0) { LOG.error("isPicturing: the entry card naming format is invalid and should be separated by '-'."); return false; } String formName = name.substring(0, delimiterIndex); if (!utility.getFormNameList().contains(formName)) { LOG.error("isPicturing: the name is not same as formName, name: " + formName + " is not in " + utility.getFormNameList().toString() + "."); return false; } String dimension = name.substring(delimiterIndex + 1, name.lastIndexOf(".")); if (!supportDimensionsList.contains(dimension)) { LOG.error("isPicturing: the dimension: " + dimension + " is invalid, is not in the following list: " + "{1X2, 2X2, 2X4, 4X4, 1X1, 6X4}."); return false; } return true; } private void getFileList(final String filePath) throws BundleException { File file = new File(filePath); if (!file.exists()) { LOG.error("getFileList: file is not exists."); return; } File[] files = file.listFiles(); if (files == null) { LOG.error("getFileList: no file in this file path."); return; } for (File f : files) { try { if (f.isFile()) { if (f.getName().endsWith(".DS_Store")) { deleteFile(f.getCanonicalPath()); continue; } String snapshotDirectoryName = f.getParentFile().getName(); if (!ENTRYCARD_SNAPSHOT_NAME.equals(snapshotDirectoryName)) { LOG.error("The level-4 directory of EntryCard must be named as snapshot" + ", but current is: " + snapshotDirectoryName + "."); throw new BundleException("The level-4 directory of EntryCard must be named as snapshot" + ", but current is: " + snapshotDirectoryName + "."); } fileNameList.add(f.getCanonicalPath()); } else if (f.isDirectory()) { getFileList(f.getCanonicalPath()); } else { LOG.error("It's not file or directory."); } } catch (IOException msg) { LOG.error("IOException error: " + msg.getMessage()); return; } } } private void checkContain2x2EntryCard(final File snapshotDirectory) throws IOException, BundleException { if (!snapshotDirectory.exists()) { LOG.error("checkContain2x2EntryCard: file is not exist: " + snapshotDirectory.getName() + "."); throw new BundleException("checkContain2x2EntryCard: file is not exist."); } File[] files = snapshotDirectory.listFiles(); if (files == null) { LOG.error("checkContain2x2EntryCard: no file in this file path."); throw new BundleException("checkContain2x2EntryCard: no file in this file path."); } for (File entryCardFile : files) { if (entryCardFile.isFile() && entryCardFile.getName().contains(PIC_2X2)) { return; } } mIsContain2x2EntryCard = false; LOG.error("checkContain2x2EntryCard: must contain 2x2 entryCard, please check it in " + snapshotDirectory.getCanonicalPath() + "."); throw new BundleException("checkContain2x2EntryCard: must contain 2x2 entryCard, please check it in " + snapshotDirectory.getCanonicalPath() + "."); } /** * compress file or directory. * * @param utility common data * @param path create new file by path * @param baseDir base path for file * @param isCompression if need compression * @throws BundleException FileNotFoundException|IOException. */ private void pathToFile(Utility utility, String path, String baseDir, boolean isCompression) throws BundleException { if (path.isEmpty()) { return; } if (isCompression && LIBS_DIR_NAME.equals(baseDir)) { compressNativeLibsParallel(path, baseDir, utility.getCompressLevel()); return; } File fileItem = new File(path); if (fileItem.isDirectory()) { File[] files = fileItem.listFiles(); if (files == null) { return; } for (File file : files) { if (file.isDirectory()) { compressDirectory(utility, file, baseDir, isCompression); } else if (isCompression) { compressFile(utility, file, baseDir, isCompression); } else { compressFile(utility, file, baseDir, isCompression); } } } else { compressFile(utility, fileItem, baseDir, isCompression); } } private void compressNativeLibsParallel(String path, String baseDir, int compressLevel) throws BundleException { try { int cores = Runtime.getRuntime().availableProcessors(); ThreadPoolExecutor executorService = new ThreadPoolExecutor(cores, cores, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>()); ParallelScatterZipCreator zipCreator = new ParallelScatterZipCreator( executorService, new DefaultBackingStoreSupplier(null), compressLevel); File file = new File(path); if (file.isDirectory()) { File[] files = file.listFiles(); if (files == null) { return; } for (File f : files) { addArchiveEntry(f, baseDir, zipCreator); } } else { addArchiveEntry(file, baseDir, zipCreator); } zipCreator.writeTo(zipOut); } catch (IOException | InterruptedException | ExecutionException e) { String errMsg = "Compressor::compressNativeLibsParallel exception: " + e.getMessage(); LOG.error(errMsg); throw new BundleException(errMsg); } } private void addArchiveEntry(File file, String baseDir, ParallelScatterZipCreator zipCreator) throws IOException { if (file.isDirectory()) { File[] files = file.listFiles(); if (files == null) { return; } for (File f : files) { addArchiveEntry(f, baseDir + file.getName() + File.separator, zipCreator); } } else { String entryName = (baseDir + file.getName()).replace(File.separator, LINUX_FILE_SEPARATOR); ZipArchiveEntry zipEntry = new ZipArchiveEntry(entryName); zipEntry.setMethod(ZipArchiveEntry.DEFLATED); InputStreamSupplier supplier = () -> { try { return Files.newInputStream(file.toPath()); } catch (IOException e) { LOG.error("Compressor::compressNativeLibsParallel exception: " + e.getMessage()); return null; } }; zipCreator.addArchiveEntry(zipEntry, supplier); } } /** * compress file or directory, res mode * * @param utility common data * @param path create new file by path * @param baseDir base path for file * @param isCompression if need compression * @throws BundleException FileNotFoundException|IOException. */ private void pathToFileResMode(Utility utility, String path, String baseDir, boolean isCompression) throws BundleException { if (path.isEmpty()) { return; } File fileItem = new File(path); if (fileItem.isDirectory()) { File[] files = fileItem.listFiles(); if (files == null) { return; } for (File file : files) { if (!list.contains(file.getName())) { // moduleName not in pack.info continue; } if (file.isDirectory()) { compressDirectory(utility, file, baseDir, isCompression); } else if (isCompression) { compressFile(utility, file, baseDir, isCompression); } else { compressFile(utility, file, baseDir, isCompression); } } } else { compressFile(utility, fileItem, baseDir, isCompression); } } /** * compress file directory. * * @param utility common data * @param dir file directory * @param baseDir current directory name * @param isCompression if need compression * @throws BundleException FileNotFoundException|IOException. */ private void compressDirectory(Utility utility, File dir, String baseDir, boolean isCompression) throws BundleException { File[] files = dir.listFiles(); if (files == null) { return; } for (File file : files) { if (file.isDirectory()) { compressDirectory(utility, file, baseDir + dir.getName() + File.separator, isCompression); } else { compressFile(utility, file, baseDir + dir.getName() + File.separator, isCompression); } } } /** * compress pack.info * * @param sourceFile source * @param zipOutputStream ZipOutputStream * @param name filename * @param keepDirStructure Empty File */ private void compress(File sourceFile, ZipOutputStream zipOutputStream, String name, boolean keepDirStructure) { FileInputStream in = null; try { byte[] buf = new byte[BUFFER_SIZE]; if (sourceFile.isFile()) { ZipEntry zipEntry = getStoredZipEntry(sourceFile, name); zipOutputStream.putNextEntry(zipEntry); in = new FileInputStream(sourceFile); int len; while ((len = in.read(buf)) != -1) { zipOutputStream.write(buf, 0, len); } zipOutputStream.closeEntry(); } else { File[] listFiles = sourceFile.listFiles(); if (listFiles == null || listFiles.length == 0) { if (keepDirStructure) { if (!name.isEmpty()) { ZipEntry zipEntry = getStoredZipEntry(sourceFile, name + "/"); zipOutputStream.putNextEntry(zipEntry); } else { ZipEntry zipEntry = getStoredZipEntry(sourceFile, name); zipOutputStream.putNextEntry(zipEntry); } zipOutputStream.closeEntry(); } } else { for (File file : listFiles) { if (keepDirStructure) { isNameEmpty(zipOutputStream, name, keepDirStructure, file); } else { compress(file, zipOutputStream, file.getName(), keepDirStructure); } } } } } catch (FileNotFoundException ignored) { LOG.error("Compressor::compressFile file not found exception."); } catch (IOException exception) { LOG.error("Compressor::compressFile io exception: " + exception.getMessage()); } catch (BundleException bundleException) { LOG.error("Compressor::compressFile bundle exception" + bundleException.getMessage()); } finally { Utility.closeStream(in); } } private ZipEntry getStoredZipEntry(File sourceFile, String name) throws BundleException { ZipEntry zipEntry = new ZipEntry(name); zipEntry.setMethod(ZipEntry.STORED); zipEntry.setCompressedSize(sourceFile.length()); zipEntry.setSize(sourceFile.length()); CRC32 crc = getCrcFromFile(sourceFile); zipEntry.setCrc(crc.getValue()); return zipEntry; } private CRC32 getCrcFromFile(File file) throws BundleException { FileInputStream fileInputStream = null; CRC32 crc = new CRC32(); try { fileInputStream = new FileInputStream(file); byte[] buffer = new byte[BUFFER_SIZE]; int count = fileInputStream.read(buffer); while (count > 0) { crc.update(buffer, 0, count); count = fileInputStream.read(buffer); } } catch (FileNotFoundException ignored) { LOG.error("Uncompressor::getCrcFromFile file not found exception."); throw new BundleException("Get Crc from file failed."); } catch (IOException exception) { LOG.error("Uncompressor::getCrcFromFile io exception: " + exception.getMessage()); throw new BundleException("Get Crc from file failed."); } finally { Utility.closeStream(fileInputStream); } return crc; } /** * isNameEmpty * * @param zipOutputStream ZipOutputStream * @param name filename * @param keepDirStructure KeepDirStructure * @param file file */ private void isNameEmpty(ZipOutputStream zipOutputStream, String name, boolean keepDirStructure, File file) { if (!name.isEmpty()) { compress(file, zipOutputStream, name + "/" + file.getName(), keepDirStructure); } else { compress(file, zipOutputStream, file.getName(), keepDirStructure); } } /** * compress process. * * @param utility common data * @param srcFile source file to zip * @param baseDir current directory name of file * @param isCompression if need compression * @throws BundleException FileNotFoundException|IOException. */ private void compressFile(Utility utility, File srcFile, String baseDir, boolean isCompression) throws BundleException { BufferedInputStream bufferedInputStream = null; FileInputStream fileInputStream = null; try { String entryName = (baseDir + srcFile.getName()).replace(File.separator, LINUX_FILE_SEPARATOR); ZipArchiveEntry zipEntry = new ZipArchiveEntry(entryName); isEntryOpen = true; if (!entryName.contains(RAW_FILE_PATH) && !entryName.contains(RES_FILE_PATH) && srcFile.getName().toLowerCase(Locale.ENGLISH).endsWith(JSON_SUFFIX) && !entryName.equals(Constants.FILE_ENCRYPT_JSON)) { zipEntry.setMethod(ZipEntry.STORED); if (jsonSpecialProcess(utility, srcFile, zipEntry)) { return; } } if (isCompression) { zipEntry.setMethod(ZipEntry.DEFLATED); } else { zipEntry.setMethod(ZipEntry.STORED); // update size zipEntry.setCompressedSize(srcFile.length()); zipEntry.setSize(srcFile.length()); // update crc CRC32 crc = getCrcFromFile(utility, srcFile); zipEntry.setCrc(crc.getValue()); } zipOut.putArchiveEntry(zipEntry); byte[] data = new byte[BUFFER_SIZE]; fileInputStream = new FileInputStream(srcFile); bufferedInputStream = new BufferedInputStream(fileInputStream); int count = bufferedInputStream.read(data); while (count > 0) { zipOut.write(data, 0, count); count = bufferedInputStream.read(data); } } catch (FileNotFoundException ignored) { throw new BundleException("CoompressFile failed."); } catch (IOException exception) { LOG.error("Compressor::compressFile io exception: " + exception.getMessage()); throw new BundleException("CoompressFile failed."); } finally { Utility.closeStream(bufferedInputStream); Utility.closeStream(fileInputStream); } } /** * check hap type for pack app. * * @param hapPath source file to zip * @return true is for is stage type and false is for FA type * @throws BundleException FileNotFoundException|IOException. */ public static boolean isModuleHap(String hapPath) throws BundleException { if (!hapPath.toLowerCase(Locale.ENGLISH).endsWith(HAP_SUFFIX)) { return true; } FileInputStream zipInput = null; ZipInputStream zin = null; ZipEntry entry = null; try { zipInput = new FileInputStream(hapPath); zin = new ZipInputStream(zipInput); while ((entry = zin.getNextEntry()) != null) { if (MODULE_JSON.equals(entry.getName().toLowerCase())) { return true; } } } catch (IOException exception) { LOG.error("Compressor::isModuleHap io exception: " + exception.getMessage()); throw new BundleException("Compressor::isModuleHap failed."); } finally { Utility.closeStream(zipInput); Utility.closeStream(zin); } return false; } /** * get CRC32 from file. * * @param utility common data * @param file source file * @return CRC32 * @throws BundleException FileNotFoundException|IOException. */ private CRC32 getCrcFromFile(Utility utility, File file) throws BundleException { FileInputStream fileInputStream = null; CRC32 crc = new CRC32(); try { fileInputStream = new FileInputStream(file); byte[] buffer = new byte[BUFFER_SIZE]; int count = fileInputStream.read(buffer); while (count > 0) { crc.update(buffer, 0, count); count = fileInputStream.read(buffer); } } catch (FileNotFoundException ignored) { LOG.error("Compressor::getCrcFromFile FileNotFoundException : " + ignored.getMessage()); throw new BundleException("Get Crc from file failed: " + file.getName()); } catch (IOException exception) { LOG.error("Compressor::getCrcFromFile io exception: " + exception.getMessage()); throw new BundleException("Get Crc from file failed."); } finally { Utility.closeStream(fileInputStream); } return crc; } private void parsePackInfoJsonFile(Utility utility, String jsonPath) throws BundleException { try { String jsonString = FileUtils.getFileContent(jsonPath).get(); List nameList = new ArrayList<>(); ModuleJsonUtil.parsePackInfoFormsName(jsonString, nameList, formNamesList); for (String formName : nameList) { if (formName.isEmpty()) { LOG.error("Compressor::infoSpecialJsonFileProcess form name is empty."); continue; } utility.addFormNameList(formName); } } catch (JSONException e) { LOG.error("Compressor::infoSpecialJsonFileProcess json Path: " + jsonPath + "json exception: " + e.getMessage()); } } private void infoSpecialProcess(Utility utility, File srcFile) throws BundleException { FileInputStream fileInputStream = null; BufferedReader bufferedReader = null; InputStreamReader inputStreamReader = null; try { fileInputStream = new FileInputStream(srcFile); inputStreamReader = new InputStreamReader(fileInputStream, StandardCharsets.UTF_8); bufferedReader = new BufferedReader(inputStreamReader); bufferedReader.mark((int) srcFile.length() + 1); // parse moduleName from pack.info parsePackModuleName(bufferedReader, utility); bufferedReader.reset(); parseDeviceType(bufferedReader, utility); bufferedReader.reset(); Pattern pattern = Pattern.compile(System.lineSeparator()); String str = bufferedReader.readLine(); StringBuilder builder = new StringBuilder(); while (str != null) { Matcher matcher = pattern.matcher(str.trim()); String dest = matcher.replaceAll(""); builder.append(dest); str = bufferedReader.readLine(); } } catch (IOException exception) { LOG.error("Compressor::jsonSpecialProcess io exception: " + exception.getMessage()); throw new BundleException("Json special process failed."); } finally { Utility.closeStream(bufferedReader); Utility.closeStream(inputStreamReader); Utility.closeStream(fileInputStream); } } /** * trim and remove "\r\n" in *.json file. * * @param utility common data * @param srcFile file input * @param entry zip file entry * @return true if process success */ private boolean jsonSpecialProcess(Utility utility, File srcFile, ZipArchiveEntry entry) { FileInputStream fileInputStream = null; BufferedReader bufferedReader = null; InputStreamReader inputStreamReader = null; try { fileInputStream = new FileInputStream(srcFile); inputStreamReader = new InputStreamReader(fileInputStream, StandardCharsets.UTF_8); bufferedReader = new BufferedReader(inputStreamReader); bufferedReader.mark((int) srcFile.length() + 1); bufferedReader.reset(); Optional optional = FileUtils.getFileContent(utility.getJsonPath()); String jsonString = optional.get(); String jsonName = new File(utility.getJsonPath()).getName().toLowerCase(Locale.ENGLISH); if (CONFIG_JSON.equals(jsonName)) { parseCompressNativeLibs(bufferedReader, utility); utility.setModuleName(ModuleJsonUtil.parseFaModuleName(jsonString)); } else if (MODULE_JSON.equals(jsonName)) { utility.setIsCompressNativeLibs(ModuleJsonUtil.stageIsCompressNativeLibs(jsonString)); utility.setModuleName(ModuleJsonUtil.parseStageModuleName(jsonString)); } else if (PATCH_JSON.equals(jsonName)) { utility.setModuleName(ModuleJsonUtil.parsePatchModuleName(jsonString)); } bufferedReader.reset(); parseDeviceType(bufferedReader, utility); bufferedReader.reset(); Pattern pattern = Pattern.compile(System.lineSeparator()); String str = bufferedReader.readLine(); StringBuilder builder = new StringBuilder(); while (str != null) { Matcher matcher = pattern.matcher(str.trim()); String dest = matcher.replaceAll(""); builder.append(dest); str = bufferedReader.readLine(); } Object jsonObject = JSON.parse(builder.toString()); byte[] trimJson = JSON.toJSONBytes(jsonObject, SerializerFeature.WriteMapNullValue, SerializerFeature.WriteDateUseDateFormat); // update crc CRC32 crc = new CRC32(); crc.update(trimJson); entry.setCrc(crc.getValue()); // update size entry.setSize(trimJson.length); entry.setCompressedSize(trimJson.length); // compress data zipOut.putArchiveEntry(entry); zipOut.write(trimJson); } catch (Exception exception) { LOG.error("Compressor::jsonSpecialProcess io exception: " + exception.getMessage()); LOG.warning("Json format err: " + srcFile.getAbsolutePath()); return false; } finally { Utility.closeStream(bufferedReader); Utility.closeStream(inputStreamReader); Utility.closeStream(fileInputStream); } return true; } /** * Parse module name from pack.info * * @param bufferedReader pack.info buffered Reader * @param utility common data * @throws BundleException IOException */ private void parsePackModuleName(BufferedReader bufferedReader, Utility utility) throws BundleException { String lineStr = null; try { while ((lineStr = bufferedReader.readLine()) != null) { if (lineStr.contains(DISTRO)) { continue; } if (lineStr.contains(JSON_END)) { continue; } if (lineStr.contains(MODULE_NAME_NEW) || lineStr.contains(MODULE_NAME)) { getModuleNameFromString(lineStr, utility); } } } catch (IOException exception) { LOG.error("Compressor::parseModuleName io exception: " + exception.getMessage()); throw new BundleException("Parse module name failed."); } } /** * Parse Forms name from pack.info * * @param bufferedReader pack.info buffered Reader * @param utility common data * @throws BundleException IOException */ private void parsePackFormName(BufferedReader bufferedReader, Utility utility) throws BundleException { String lineStr = null; try { while ((lineStr = bufferedReader.readLine()) != null) { if (lineStr.contains("abilities")) { continue; } if (lineStr.contains(FORMS)) { continue; } if (lineStr.contains(JSON_END)) { continue; } if (lineStr.contains(NAME)) { getNameFromString(lineStr, utility); } } } catch (IOException exception) { LOG.error("Compressor::parseModuleName io exception: " + exception.getMessage()); throw new BundleException("Parse module name failed."); } } /** * Get name from line string * * @param lineStr line string * @param utility common data * @throws BundleException StringIndexOutOfBoundsException */ private void getNameFromString(String lineStr, Utility utility) throws BundleException { try { int endIndex = lineStr.lastIndexOf(SEMICOLON); if (endIndex <= 0) { LOG.error("Compressor::getModuleNameFromString field the json is not standard."); throw new BundleException("Parse module name failed, module-name is invalid."); } int startIndex = lineStr.lastIndexOf(SEMICOLON, endIndex - 1) + 1; String formName = lineStr.substring(startIndex, endIndex); if (formName == null || formName.isEmpty()) { LOG.error("Compressor::getModuleNameFromString field module-name is empty."); throw new BundleException("Parse module name failed, module-name is empty."); } String[] nameList = formName.split("\\."); if (nameList.length <= 1) { formNamesList.add(formName); utility.addFormNameList(formName); } } catch (StringIndexOutOfBoundsException exception) { LOG.error("Compressor::parseModuleName field module-name is fault: " + exception.getMessage()); throw new BundleException("Parse module name failed, module-name is invalid."); } } /** * Get module name from line string * * @param lineStr line string * @param utility common data * @throws BundleException StringIndexOutOfBoundsException */ private void getModuleNameFromString(String lineStr, Utility utility) throws BundleException { try { int endIndex = lineStr.lastIndexOf(SEMICOLON); if (endIndex <= 0) { LOG.error("Compressor::getModuleNameFromString field the json is not standard."); throw new BundleException("Parse module name failed, module-name is invalid."); } int startIndex = lineStr.lastIndexOf(SEMICOLON, endIndex - 1) + 1; String moduleName = lineStr.substring(startIndex, endIndex); list.add(moduleName); if (moduleName == null || moduleName.isEmpty()) { LOG.error("Compressor::getModuleNameFromString field module-name is empty."); throw new BundleException("Parse module name failed, module-name is empty."); } utility.setModuleName(moduleName); } catch (StringIndexOutOfBoundsException exception) { LOG.error("Compressor::parseModuleName field module-name is fault: " + exception.getMessage()); throw new BundleException("Parse module name failed, module-name is invalid."); } } private void parseCompressNativeLibs(BufferedReader bufferedReader, Utility utility) throws BundleException { String lineStr = null; try { while ((lineStr = bufferedReader.readLine()) != null) { if (lineStr.contains(COMPRESS_NATIVE_LIBS)) { if (lineStr.contains(Utility.TRUE_STRING)) { utility.setIsCompressNativeLibs(true); break; } } } } catch (IOException exception) { LOG.error("Compressor::parseCompressNativeLibs io exception: " + exception.getMessage()); throw new BundleException("Parse compress native libs failed."); } } /** * ZipOutputStream flush, closeEntry and finish. */ private void closeZipOutputStream() { try { if (zipOut != null) { zipOut.flush(); } } catch (IOException exception) { LOG.error("Compressor::closeZipOutputStream flush exception " + exception.getMessage()); } try { if (zipOut != null && isEntryOpen) { zipOut.closeArchiveEntry(); } } catch (IOException exception) { LOG.error("Compressor::closeZipOutputStream close entry io exception " + exception.getMessage()); } try { if (zipOut != null) { zipOut.finish(); } } catch (IOException exception) { LOG.error("Compressor::closeZipOutputStream finish exception " + exception.getMessage()); } } /** * Parse device type from config.json * * @param bufferedReader config.json buffered Reader * @param utility common data * @throws BundleException IOException */ private void parseDeviceType(BufferedReader bufferedReader, Utility utility) throws BundleException { String lineStr = null; boolean isDeviceTypeStart = false; try { while ((lineStr = bufferedReader.readLine()) != null) { if (!isDeviceTypeStart) { if (lineStr.contains(DEVICE_TYPE)) { isDeviceTypeStart = true; } continue; } if (lineStr.contains(JSON_END)) { break; } utility.setDeviceType(lineStr); break; } } catch (IOException exception) { LOG.error("Compressor::parseDeviceType io exception: " + exception.getMessage()); throw new BundleException("Parse device type failed."); } } /** * check hap and hsp is valid in haps when pack app, check type has bundleName, * vendor, version, apiVersion moduleName, packageName. * * @param fileLists is the list of hapPath. * @return true is for successful and false is for failed * @throws BundleException FileNotFoundException|IOException. */ private boolean checkHapIsValid(List fileLists, boolean isSharedApp) throws BundleException { List hapVerifyInfos = new ArrayList<>(); for (String hapPath : fileLists) { if (hapPath.isEmpty()) { LOG.error("Compressor::checkHapIsValid input wrong hap file."); throw new BundleException("Compressor::checkHapIsValid input wrong hap file."); } File srcFile = new File(hapPath); String fileStr = srcFile.getName(); if (fileStr.isEmpty()) { LOG.error("Compressor::checkHapIsValid get file name failed."); throw new BundleException("Compressor::checkHapIsValid get file name failed."); } if (!fileStr.toLowerCase(Locale.ENGLISH).endsWith(HAP_SUFFIX) && !fileStr.toLowerCase(Locale.ENGLISH).endsWith(HSP_SUFFIX)) { LOG.error("Compressor::checkHapIsValid input wrong hap file."); throw new BundleException("Compressor::checkHapIsValid input wrong hap file."); } if (isModuleHap(hapPath)) { hapVerifyInfos.add(parseStageHapVerifyInfo(hapPath)); } else { hapVerifyInfos.add(parseFAHapVerifyInfo(hapPath)); } if (!hapVerifyInfos.isEmpty()) { hapVerifyInfoMap.put(getFileNameByPath(hapPath), hapVerifyInfos.get(hapVerifyInfos.size() - 1)); } } if (isSharedApp) { boolean res = checkSharedAppIsValid(hapVerifyInfos); if (!res) { return false; } if (!isOverlay) { return true; } } else { for (HapVerifyInfo hapVerifyInfo : hapVerifyInfos) { String bundleType = hapVerifyInfo.getBundleType(); if (TYPE_SHARED.equals(bundleType)) { LOG.error("Compressor::checkHapIsValid only one item can be entered in the -hsp-path" + " when bundleType is shared."); return false; } } } setAtomicServiceFileSizeLimit(hapVerifyInfos); if (!HapVerify.checkHapIsValid(hapVerifyInfos)) { return false; } return true; } /** * parse stage file to hap verify info from hap path. * * @param filePath is the hap path * @return hapVerifyInfo */ public static HapVerifyInfo parseStageHapVerifyInfo(String filePath) throws BundleException { HapVerifyInfo hapVerifyInfo = readStageHapVerifyInfo(filePath); hapVerifyInfo.setStageModule(true); ModuleJsonUtil.parseStageHapVerifyInfo(hapVerifyInfo); hapVerifyInfo.setFileLength(FileUtils.getFileSize(filePath)); return hapVerifyInfo; } /** * set file size limit for each HapVerifyInfo. * * @param hapVerifyInfos Indicates hapVerifyInfo list. */ public static void setAtomicServiceFileSizeLimit(ListhapVerifyInfos) { for (HapVerifyInfo hapVerifyInfo : hapVerifyInfos) { if (!hapVerifyInfo.getBundleType().equals(ATOMIC_SERVICE)) { continue; } hapVerifyInfo.setEntrySizeLimit(getEntryModuleSizeLimit()); hapVerifyInfo.setNotEntrySizeLimit(getNotEntryModuleSizeLimit()); hapVerifyInfo.setSumSizeLimit(getSumModuleSizeLimit()); } } /** * parse fa file to hap verify info from hap path. * * @param filePath is the hap path * @return hapVerifyInfo */ public static HapVerifyInfo parseFAHapVerifyInfo(String filePath) throws BundleException { HapVerifyInfo hapVerifyInfo = readFAHapVerifyInfo(filePath); hapVerifyInfo.setStageModule(false); hapVerifyInfo.setFileLength(FileUtils.getFileSize(filePath)); ModuleJsonUtil.parseFAHapVerifyInfo(hapVerifyInfo); return hapVerifyInfo; } /** * read stage hap verify info from hap file. * * @param srcPath source file to zip * @return HapVerifyInfo of parse result * @throws BundleException FileNotFoundException|IOException. */ public static HapVerifyInfo readStageHapVerifyInfo(String srcPath) throws BundleException { HapVerifyInfo hapVerifyInfo = new HapVerifyInfo(); ZipFile zipFile = null; try { File srcFile = new File(srcPath); zipFile = new ZipFile(srcFile); hapVerifyInfo.setResourceMap(FileUtils.getProfileJson(zipFile)); hapVerifyInfo.setProfileStr(FileUtils.getFileStringFromZip(MODULE_JSON, zipFile)); } catch (IOException e) { LOG.error("FileUtil::parseStageHapVerifyInfo file not available."); throw new BundleException("FileUtil::parseStageHapVerifyInfo file not available."); } finally { Utility.closeStream(zipFile); } return hapVerifyInfo; } /** * read fa hap verify info from hap file. * * @param srcPath source file to zip * @return HapVerifyInfo of parse result * @throws BundleException FileNotFoundException|IOException. */ public static HapVerifyInfo readFAHapVerifyInfo(String srcPath) throws BundleException { HapVerifyInfo hapVerifyInfo = new HapVerifyInfo(); ZipFile zipFile = null; try { File srcFile = new File(srcPath); zipFile = new ZipFile(srcFile); hapVerifyInfo.setProfileStr(FileUtils.getFileStringFromZip(CONFIG_JSON, zipFile)); } catch (IOException e) { LOG.error("FileUtil::parseStageHapVerifyInfo file not available."); throw new BundleException("FileUtil::parseStageHapVerifyInfo file not available."); } finally { Utility.closeStream(zipFile); } return hapVerifyInfo; } /** * compress in hqf mode. * * @param utility common data * @throws BundleException FileNotFoundException|IOException. */ private void compressHQFMode(Utility utility) throws BundleException { pathToFile(utility, utility.getJsonPath(), NULL_DIR_NAME, false); if (!utility.getEtsPath().isEmpty()) { pathToFile(utility, utility.getEtsPath(), ETS_PATH, false); } if (!utility.getLibPath().isEmpty()) { pathToFile(utility, utility.getLibPath(), LIBS_DIR_NAME, false); } if (!utility.getResourcesPath().isEmpty()) { pathToFile(utility, utility.getResourcesPath(), RESOURCES_DIR_NAME, false); } } /** * compress in appqf mode. * * @param utility common data * @throws BundleException FileNotFoundException|IOException. */ private void compressAPPQFMode(Utility utility) throws BundleException { List fileList = utility.getFormatedHQFList(); if (!checkHQFIsValid(fileList)) { LOG.error("checkHQFIsValid failed when pack appqf file."); throw new BundleException("checkHQFIsValid failed when pack appqf file."); } for (String hapPath : fileList) { pathToFile(utility, hapPath, NULL_DIR_NAME, false); } } /** * check input hqf is valid. * * @param fileList is input path of hqf files * @throws BundleException FileNotFoundException|IOException. */ private boolean checkHQFIsValid(List fileList) throws BundleException { List hqfVerifyInfos = new ArrayList<>(); for (String file : fileList) { hqfVerifyInfos.add(ModuleJsonUtil.parseHQFInfo(file)); } if (!HQFVerify.checkHQFIsValid(hqfVerifyInfos)) { LOG.error("input hqf is invalid."); return false; } return true; } private void compressHSPMode(Utility utility) throws BundleException { pathToFile(utility, utility.getJsonPath(), NULL_DIR_NAME, false); pathToFile(utility, utility.getProfilePath(), NULL_DIR_NAME, false); if (!utility.getIndexPath().isEmpty() && isModuleJSON(utility.getJsonPath())) { String assetsPath = NULL_DIR_NAME; pathToFile(utility, utility.getIndexPath(), assetsPath, false); } if (!utility.getLibPath().isEmpty()) { pathToFile(utility, utility.getLibPath(), LIBS_DIR_NAME, utility.isCompressNativeLibs()); } if (!utility.getANPath().isEmpty()) { pathToFile(utility, utility.getANPath(), AN_DIR_NAME, false); } if (!utility.getAPPath().isEmpty()) { pathToFile(utility, utility.getAPPath(), AP_PATH_NAME, false); } if (!utility.getFilePath().isEmpty()) { pathToFile(utility, utility.getFilePath(), NULL_DIR_NAME, false); } if (!utility.getResPath().isEmpty() && !utility.getModuleName().isEmpty()) { String resPath = ASSETS_DIR_NAME + utility.getModuleName() + LINUX_FILE_SEPARATOR + RESOURCES_DIR_NAME; if (DEVICE_TYPE_FITNESSWATCH.equals( utility.getDeviceType().replace(SEMICOLON, EMPTY_STRING).trim()) || DEVICE_TYPE_FITNESSWATCH_NEW.equals( utility.getDeviceType().replace(SEMICOLON, EMPTY_STRING).trim())) { resPath = RES_DIR_NAME; } pathToFile(utility, utility.getResPath(), resPath, false); } if (!utility.getResourcesPath().isEmpty() && isModuleJSON(utility.getJsonPath())) { String resourcesPath = RESOURCES_DIR_NAME; pathToFile(utility, utility.getResourcesPath(), resourcesPath, false); } if (!utility.getJsPath().isEmpty() && isModuleJSON(utility.getJsonPath())) { String jsPath = JS_PATH; pathToFile(utility, utility.getJsPath(), jsPath, false); } if (!utility.getEtsPath().isEmpty() && isModuleJSON(utility.getJsonPath())) { String etsPath = ETS_PATH; pathToFile(utility, utility.getEtsPath(), etsPath, false); } if (!utility.getRpcidPath().isEmpty()) { String rpcidPath = NULL_DIR_NAME; pathToFile(utility, utility.getRpcidPath(), rpcidPath, false); } if (!utility.getAssetsPath().isEmpty()) { pathToFile(utility, utility.getAssetsPath(), ASSETS_DIR_NAME, false); } if (!utility.getBinPath().isEmpty()) { pathToFile(utility, utility.getBinPath(), NULL_DIR_NAME, false); } if (!utility.getPackInfoPath().isEmpty()) { pathToFile(utility, utility.getPackInfoPath(), NULL_DIR_NAME, false); } // pack --dir-list if (!utility.getFormatedDirList().isEmpty()) { for (int i = 0; i < utility.getFormatedDirList().size(); ++i) { String baseDir = new File(utility.getFormatedDirList().get(i)).getName() + File.separator; pathToFile(utility, utility.getFormatedDirList().get(i), baseDir, false); } } if (!utility.getPkgContextPath().isEmpty()) { pathToFile(utility, utility.getPkgContextPath(), NULL_DIR_NAME, false); } compressHapModeMultiple(utility); } private static boolean checkSharedAppIsValid(List hapVerifyInfos) throws BundleException { if (hapVerifyInfos.isEmpty()) { LOG.error("no module included"); return false; } if (hapVerifyInfos.size() > SHARED_APP_HSP_LIMIT) { LOG.error("Shared app only can contain one module"); return false; } for (HapVerifyInfo hapVerifyInfo : hapVerifyInfos) { if (!hapVerifyInfo.getTargetBundleName().isEmpty()) { isOverlay = true; return true; } } return HapVerify.checkSharedApppIsValid(hapVerifyInfos); } private void versionNormalize(Utility utility) { List utils = new ArrayList<>(); Path tempDir = null; for (String hapPath : utility.getFormattedHapList()) { try { tempDir = Files.createTempDirectory(Paths.get(utility.getOutPath()), "temp"); unpackHap(hapPath, tempDir.toAbsolutePath().toString()); VersionNormalizeUtil util = new VersionNormalizeUtil(); File moduleFile = new File( tempDir.toAbsolutePath() + LINUX_FILE_SEPARATOR + MODULE_JSON); File configFile = new File( tempDir.toAbsolutePath() + LINUX_FILE_SEPARATOR + CONFIG_JSON); File packInfoFile = new File( tempDir.toAbsolutePath() + LINUX_FILE_SEPARATOR + PACKINFO_NAME); if (moduleFile.exists() && configFile.exists()) { LOG.error("versionNormalize failed, invalid hap structure."); throw new BundleException("versionNormalize failed, invalid hap structure."); } if (moduleFile.exists()) { String moduleJsonPath = tempDir.resolve(MODULE_JSON).toString(); util = parseAndModifyModuleJson(moduleJsonPath, utility); } else if (configFile.exists()) { String configJsonPath = tempDir.resolve(CONFIG_JSON).toString(); util = parseAndModifyConfigJson(configJsonPath, utility); } else { LOG.error("versionNormalize failed, invalid hap structure."); throw new BundleException("versionNormalize failed, invalid hap structure."); } if (packInfoFile.exists()) { String packInfoPath = tempDir.resolve(PACKINFO_NAME).toString(); parseAndModifyPackInfo(packInfoPath, utility); } verifyModuleVersion(util, utility); utils.add(util); String modifiedHapPath = Paths.get(utility.getOutPath()) + LINUX_FILE_SEPARATOR + Paths.get(hapPath).getFileName().toString(); compressDirToHap(tempDir, modifiedHapPath); } catch (IOException | BundleException e) { LOG.error("versionNormalize failed " + e.getMessage()); } finally { if (tempDir != null) { deleteDirectory(tempDir.toFile()); } } } writeVersionRecord(utils, utility.getOutPath()); } private VersionNormalizeUtil parseAndModifyModuleJson(String jsonFilePath, Utility utility) throws BundleException { VersionNormalizeUtil util = new VersionNormalizeUtil(); try (FileInputStream jsonStream = new FileInputStream(jsonFilePath)) { JSONObject jsonObject = JSON.parseObject(jsonStream, JSONObject.class); if (!jsonObject.containsKey(APP)) { LOG.error("parseAndModifyJson failed, json file not valid."); throw new BundleException("parseAndModifyJson failed, json file not valid."); } JSONObject appObject = jsonObject.getJSONObject(APP); if (!appObject.containsKey(VERSION_CODE)) { LOG.error("parseAndModifyJson failed, json file not valid."); throw new BundleException("parseAndModifyJson failed, json file not valid."); } if (!appObject.containsKey(VERSION_NAME)) { LOG.error("parseAndModifyJson failed, json file not valid."); throw new BundleException("parseAndModifyJson failed, json file not valid."); } util.setOriginVersionCode(appObject.getIntValue(VERSION_CODE)); util.setOriginVersionName(appObject.getString(VERSION_NAME)); JSONObject moduleObject = jsonObject.getJSONObject(MODULE); if (!moduleObject.containsKey(NAME)) { LOG.error("parseAndModifyModuleJson failed, json file not valid."); throw new BundleException("parseAndModifyModuleJson failed, json file not valid."); } util.setModuleName(moduleObject.getString(NAME)); appObject.put(VERSION_CODE, utility.getVersionCode()); appObject.put(VERSION_NAME, utility.getVersionName()); writeJson(jsonFilePath, jsonObject); } catch (IOException e) { LOG.error("parseAndModifyModuleJson failed, IOException." + e.getMessage()); throw new BundleException("parseAndModifyModuleJson failed, IOException." + e.getMessage()); } return util; } private void parseAndModifyPackInfo(String packInfoPath, Utility utility) throws BundleException { try (FileInputStream jsonStream = new FileInputStream(packInfoPath)) { JSONObject jsonObject = JSON.parseObject(jsonStream, JSONObject.class); if (jsonObject == null) { LOG.warning("parseAndModifyPackInfo failed, json format invalid."); return; } JSONObject summaryObject = jsonObject.getJSONObject(SUMMARY); if (summaryObject == null) { LOG.warning("parseAndModifyPackInfo failed, summary invalid."); return; } JSONObject appObject = summaryObject.getJSONObject(APP); if (appObject == null) { LOG.warning("parseAndModifyPackInfo failed, app invalid."); return; } JSONObject versionObject = appObject.getJSONObject(VERSION); if (versionObject == null) { LOG.warning("parseAndModifyPackInfo failed, version invalid."); return; } versionObject.put(CODE, utility.getVersionCode()); versionObject.put(NAME, utility.getVersionName()); writeJson(packInfoPath, jsonObject); } catch (IOException e) { LOG.warning("parseAndModifyPackInfo failed, IOException." + e.getMessage()); } } private void writeJson(String jsonFilePath, JSONObject jsonObject) throws IOException, BundleException { BufferedWriter bw = null; try { String pretty = JSON.toJSONString(jsonObject, SerializerFeature.PrettyFormat, SerializerFeature.WriteMapNullValue, SerializerFeature.WriteDateUseDateFormat); bw = new BufferedWriter(new OutputStreamWriter( new FileOutputStream(jsonFilePath), StandardCharsets.UTF_8)); bw.write(pretty); } catch (IOException exception) { LOG.error("Compressor::writeJson failed for IOException " + exception.getMessage()); throw new BundleException("Compressor::writeJson failed for IOException"); } finally { if (bw != null) { bw.flush(); bw.close(); } } } private VersionNormalizeUtil parseAndModifyConfigJson(String jsonFilePath, Utility utility) throws BundleException { VersionNormalizeUtil util = new VersionNormalizeUtil(); try (FileInputStream jsonStream = new FileInputStream(jsonFilePath)) { JSONObject jsonObject = JSON.parseObject(jsonStream, JSONObject.class); if (!jsonObject.containsKey(APP)) { LOG.error("parseAndModifyJson failed, json file not valid."); throw new BundleException("parseAndModifyJson failed, json file not valid."); } JSONObject appObject = jsonObject.getJSONObject(APP); if (!appObject.containsKey(VERSION)) { LOG.error("parseAndModifyModuleJson failed, json file not valid."); throw new BundleException("parseAndModifyModuleJson failed, json file not valid."); } JSONObject versionObj = appObject.getJSONObject(VERSION); if (!versionObj.containsKey(CODE)) { LOG.error("parseAndModifyModuleJson failed, json file not valid."); throw new BundleException("parseAndModifyModuleJson failed, json file not valid."); } util.setOriginVersionCode(versionObj.getIntValue(CODE)); if (!versionObj.containsKey(NAME)) { LOG.error("parseAndModifyModuleJson failed, json file not valid."); throw new BundleException("parseAndModifyModuleJson failed, json file not valid."); } util.setOriginVersionName(versionObj.getString(NAME)); JSONObject moduleObject = jsonObject.getJSONObject(MODULE); if (!moduleObject.containsKey(NAME)) { LOG.error("parseAndModifyModuleJson failed, json file not valid."); throw new BundleException("parseAndModifyModuleJson failed, json file not valid."); } util.setModuleName(moduleObject.getString(NAME)); versionObj.put(CODE, utility.getVersionCode()); versionObj.put(NAME, utility.getVersionName()); writeJson(jsonFilePath, jsonObject); } catch (IOException e) { LOG.error("parseAndModifyModuleJson IOException." + e.getMessage()); throw new BundleException("parseAndModifyModuleJson IOException." + e.getMessage()); } return util; } private void compressDirToHap(Path sourceDir, String zipFilePath) throws IOException, BundleException { Utility utility = new Utility(); utility.setOutPath(zipFilePath); if (zipFilePath.endsWith(HAP_SUFFIX)) { utility.setMode(Utility.MODE_HAP); } else if (zipFilePath.endsWith(HSP_SUFFIX)) { utility.setMode(Utility.MODE_HSP); } try (Stream pathStream = Files.walk(sourceDir, 1)) { pathStream.forEach(path -> { String fileName = path.getFileName().toString(); String filePath = path.toString(); switch (fileName) { case ETS_FILE_NAME: utility.setEtsPath(filePath); break; case HNP_FILE_NAME: utility.setHnpPath(filePath); break; case LIBS_DIR: utility.setLibPath(filePath); break; case AN_FILE_NAME: utility.setANPath(filePath); break; case AP_FILE_NAME: utility.setAPPath(filePath); break; case RESOURCE_FILE_NAME: utility.setResourcesPath(filePath); break; case JS_FILE_NAME: utility.setJsPath(filePath); break; case ASSETS_FILE_NAME: utility.setAssetsPath(filePath); break; case MAPLE_FILE_NAME: utility.setSoDir(filePath); break; case SHARED_LIBS_FILE_NAME: utility.setSharedLibsPath(filePath); break; case CONFIG_JSON: utility.setJsonPath(filePath); break; case MODULE_JSON: utility.setJsonPath(filePath); break; case RES_INDEX: utility.setIndexPath(filePath); break; case PACKINFO_NAME: utility.setPackInfoPath(filePath); break; case RPCID: utility.setRpcid(filePath); break; case PKG_CONTEXT_INFO: utility.setPkgContextPath(filePath); break; } }); } compressProcess(utility); } private static void deleteDirectory(File dir) { if (dir.isDirectory()) { File[] children = dir.listFiles(); if (children != null) { for (File child : children) { deleteDirectory(child); } } } dir.delete(); } private static void writeVersionRecord(List utils, String outPath) { String jsonString = JSON.toJSONString(utils); try (FileWriter fileWriter = new FileWriter(outPath + LINUX_FILE_SEPARATOR + VERSION_RECORD)) { fileWriter.write(jsonString); } catch (IOException e) { LOG.error("writeVersionRecord failed " + e.getMessage()); } } private static void verifyModuleVersion(VersionNormalizeUtil versionNormalizeUtil, Utility utility) throws BundleException { if (versionNormalizeUtil.getOriginVersionCode() > utility.getVersionCode()) { String errorMsg = "versionNormalize failed, module " + versionNormalizeUtil.getModuleName() + " version code less than input version code"; LOG.error(errorMsg); throw new BundleException(errorMsg); } else if (versionNormalizeUtil.getOriginVersionCode() == utility.getVersionCode()) { LOG.warning("versionNormalize warning: module " + versionNormalizeUtil.getModuleName() + " version code not changed"); } if (versionNormalizeUtil.getOriginVersionName().equals(utility.getVersionName())) { LOG.warning("versionNormalize warning: module " + versionNormalizeUtil.getModuleName() + " version name not changed"); } } private static void unpackHap(String srcPath, String outPath) throws BundleException { try (FileInputStream fis = new FileInputStream(srcPath); ZipInputStream zipInputStream = new ZipInputStream(new BufferedInputStream(fis))) { File destDir = new File(outPath); if (!destDir.exists()) { destDir.mkdirs(); } ZipEntry entry; while ((entry = zipInputStream.getNextEntry()) != null) { String entryName = entry.getName(); File entryFile = new File(outPath, entryName); if (entry.isDirectory()) { entryFile.mkdirs(); zipInputStream.closeEntry(); continue; } File parent = entryFile.getParentFile(); if (!parent.exists()) { parent.mkdirs(); } writeToFile(zipInputStream, entryFile); zipInputStream.closeEntry(); } } catch (IOException e) { LOG.error("unpack hap failed IOException " + e.getMessage()); throw new BundleException("unpack hap failed IOException " + e.getMessage()); } } private static void writeToFile(ZipInputStream zipInputStream, File entryFile) throws IOException { try (FileOutputStream fos = new FileOutputStream(entryFile)) { byte[] buffer = new byte[BUFFER_SIZE]; int bytesRead; while ((bytesRead = zipInputStream.read(buffer)) != -1) { fos.write(buffer, 0, bytesRead); } } } private static String getFileNameByPath(String path) { Path filePath = Paths.get(path); return filePath.getFileName().toString(); } private void packEncryptJsonFile(Utility utility) throws BundleException { if (!utility.getEncryptPath().isEmpty()) { pathToFile(utility, utility.getEncryptPath(), NULL_DIR_NAME, false); } else { LOG.info("Compressor::packEncryptJsonFile has no encrypt.json"); } } }