1 /* 2 * Copyright (c) 2024 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16 package ohos; 17 18 import com.alibaba.fastjson.JSON; 19 import com.alibaba.fastjson.JSONObject; 20 import com.alibaba.fastjson.serializer.SerializerFeature; 21 import org.apache.commons.compress.utils.IOUtils; 22 23 import java.io.File; 24 import java.io.FileInputStream; 25 import java.io.FileNotFoundException; 26 import java.io.IOException; 27 import java.io.InputStream; 28 import java.io.OutputStream; 29 import java.nio.file.FileVisitResult; 30 import java.nio.file.Files; 31 import java.nio.file.Path; 32 import java.nio.file.Paths; 33 import java.nio.file.SimpleFileVisitor; 34 import java.nio.file.StandardOpenOption; 35 import java.nio.file.attribute.BasicFileAttributes; 36 import java.util.Enumeration; 37 import java.util.zip.CRC32; 38 import java.util.zip.Deflater; 39 import java.util.zip.ZipEntry; 40 import java.util.zip.ZipFile; 41 import java.util.zip.ZipOutputStream; 42 43 /** 44 * PackageNormalize, normalize HSP bundleName and versionCode 45 * 46 * @since 2024-04-06 47 */ 48 public class PackageNormalize { 49 private static final Log LOG = new Log(PackageNormalize.class.toString()); 50 private static final int BUFFER_SIZE = 10 * 1024; 51 private static final String MODULE_JSON = "module.json"; 52 private static final String PACK_INFO = "pack.info"; 53 private static final String APP = "app"; 54 private static final String SUMMARY = "summary"; 55 private static final String VERSION = "version"; 56 private static final String CODE = "code"; 57 private static final String BUNDLE_NAME = "bundleName"; 58 private static final String VERSION_CODE = "versionCode"; 59 private static final String TMP = "tmp"; 60 61 /** 62 * normalize HSP bundleName and versionCode 63 * 64 * @param utility common data 65 * @return true if normalize succeed 66 */ normalize(Utility utility)67 public static boolean normalize(Utility utility) { 68 Path outPath = Paths.get(utility.getOutPath()); 69 for (String hspPath : utility.getFormattedHspPathList()) { 70 try { 71 normalize(Paths.get(hspPath), outPath, utility.getBundleName(), utility.getVersionCode()); 72 } catch (BundleException ex) { 73 LOG.error(PackingToolErrMsg.PACKAGE_NORMALIZE_FAILED.toString( 74 "Package normalize exist BundleException: " + ex.getMessage())); 75 return false; 76 } catch (IOException ex) { 77 LOG.error(PackingToolErrMsg.PACKAGE_NORMALIZE_FAILED.toString( 78 "Package normalize exist IOException: " + ex.getMessage())); 79 return false; 80 } 81 } 82 return true; 83 } 84 normalize(Path hspPath, Path outPath, String bundleName, int versionCode)85 private static void normalize(Path hspPath, Path outPath, String bundleName, int versionCode) 86 throws BundleException, IOException { 87 Path outHspPath = outPath.resolve(hspPath.getFileName()); 88 Path tmpDir = Files.createTempDirectory(outPath, TMP); 89 Path moduleJson = Files.createFile(tmpDir.resolve(MODULE_JSON)); 90 Path packInfo = Files.createFile(tmpDir.resolve(PACK_INFO)); 91 try (ZipFile hspFile = new ZipFile(hspPath.toFile()); 92 OutputStream output = Files.newOutputStream(outHspPath); 93 ZipOutputStream zipOut = new ZipOutputStream(output)) { 94 // 1.unzip module.json and pack.info, copy to tmp 95 ZipEntry moduleEntry = hspFile.getEntry(MODULE_JSON); 96 if (moduleEntry != null) { 97 try (OutputStream out = Files.newOutputStream(moduleJson)) { 98 IOUtils.copy(hspFile.getInputStream(moduleEntry), out); 99 } 100 } 101 ZipEntry packEntry = hspFile.getEntry(PACK_INFO); 102 if (moduleEntry != null) { 103 try (OutputStream out = Files.newOutputStream(packInfo)) { 104 IOUtils.copy(hspFile.getInputStream(packEntry), out); 105 } 106 } 107 108 // 2.update bundleName, versionCode to module.json, pack.info 109 updateModuleJson(moduleJson, bundleName, versionCode); 110 updatePackInfo(packInfo, bundleName, versionCode); 111 112 // 3.zip hsp, module.json, pack.info to outPath 113 copyHsp(hspFile, moduleJson, packInfo, zipOut); 114 } finally { 115 rmdir(tmpDir); 116 } 117 } 118 rmdir(Path dir)119 private static void rmdir(Path dir) throws IOException { 120 Files.walkFileTree(dir, new SimpleFileVisitor<Path>() { 121 @Override 122 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { 123 Files.delete(file); 124 return FileVisitResult.CONTINUE; 125 } 126 127 @Override 128 public FileVisitResult postVisitDirectory(Path dir, IOException ex) throws IOException { 129 Files.delete(dir); 130 return FileVisitResult.CONTINUE; 131 } 132 }); 133 } 134 updateModuleJson(Path moduleJson, String bundleName, int versionCode)135 private static void updateModuleJson(Path moduleJson, String bundleName, int versionCode) 136 throws BundleException, IOException { 137 try (FileInputStream input = new FileInputStream(moduleJson.toFile())) { 138 JSONObject jsonObject = JSON.parseObject(input, JSONObject.class); 139 if (jsonObject == null) { 140 LOG.error(PackingToolErrMsg.UPDATE_MODULE_JSON_FAILED.toString( 141 "Failed to parse module.json: jsonObject is null.")); 142 throw new BundleException("updateModuleJson failed, parse json is null."); 143 } 144 if (!jsonObject.containsKey(APP)) { 145 LOG.error(PackingToolErrMsg.UPDATE_MODULE_JSON_FAILED.toString( 146 "The module.json file does not contain 'app'.")); 147 throw new BundleException("updateModuleJson failed, json format invalid."); 148 } 149 JSONObject appObject = jsonObject.getJSONObject(APP); 150 appObject.put(BUNDLE_NAME, bundleName); 151 appObject.put(VERSION_CODE, versionCode); 152 Files.write(moduleJson, JSON.toJSONBytes(jsonObject, SerializerFeature.WriteMapNullValue, 153 SerializerFeature.WriteDateUseDateFormat, SerializerFeature.SortField), 154 StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE); 155 } 156 } 157 updatePackInfo(Path packInfo, String bundleName, int versionCode)158 private static void updatePackInfo(Path packInfo, String bundleName, int versionCode) 159 throws BundleException, IOException { 160 try (FileInputStream input = new FileInputStream(packInfo.toFile())) { 161 JSONObject jsonObject = JSON.parseObject(input, JSONObject.class); 162 if (jsonObject == null) { 163 LOG.error(PackingToolErrMsg.UPDATE_PACKINFO_FAILED.toString( 164 "Failed to parse pack.info: invalid json format.")); 165 throw new BundleException("updatePackInfo failed, json format invalid."); 166 } 167 JSONObject summaryObject = jsonObject.getJSONObject(SUMMARY); 168 if (summaryObject == null) { 169 LOG.error(PackingToolErrMsg.UPDATE_PACKINFO_FAILED.toString( 170 "The pack.info file does not contain 'summary'.")); 171 throw new BundleException("updatePackInfo failed, json format invalid."); 172 } 173 JSONObject appObject = summaryObject.getJSONObject(APP); 174 if (appObject == null) { 175 LOG.error(PackingToolErrMsg.UPDATE_PACKINFO_FAILED.toString( 176 "The pack.info file does not contain 'app'.")); 177 throw new BundleException("updatePackInfo failed, json format invalid."); 178 } 179 appObject.put(BUNDLE_NAME, bundleName); 180 JSONObject versionObject = appObject.getJSONObject(VERSION); 181 if (versionObject == null) { 182 LOG.error(PackingToolErrMsg.UPDATE_PACKINFO_FAILED.toString( 183 "The pack.info file does not contain 'version'.")); 184 throw new BundleException("updatePackInfo failed, json format invalid."); 185 } 186 versionObject.put(CODE, versionCode); 187 Files.write(packInfo, JSON.toJSONBytes(jsonObject, SerializerFeature.WriteMapNullValue, 188 SerializerFeature.WriteDateUseDateFormat, SerializerFeature.SortField), 189 StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE); 190 } 191 } 192 copyHsp(ZipFile hspFile, Path moduleJson, Path packInfo, ZipOutputStream zipOut)193 private static void copyHsp(ZipFile hspFile, Path moduleJson, Path packInfo, ZipOutputStream zipOut) 194 throws BundleException, IOException { 195 zipOut.setLevel(Deflater.BEST_SPEED); 196 boolean isStored = true; 197 Enumeration<? extends ZipEntry> entries = hspFile.entries(); 198 while (entries.hasMoreElements()) { 199 ZipEntry zipEntry = entries.nextElement(); 200 if (MODULE_JSON.equals(zipEntry.getName()) || PACK_INFO.equals(zipEntry.getName())) { 201 isStored = zipEntry.getMethod() == ZipEntry.STORED; 202 continue; 203 } 204 ZipEntry newEntry = zipEntry.getMethod() == 205 ZipEntry.STORED ? new ZipEntry(zipEntry) : new ZipEntry(zipEntry.getName()); 206 zipOut.putNextEntry(newEntry); 207 if (!zipEntry.isDirectory()) { 208 IOUtils.copy(hspFile.getInputStream(zipEntry), zipOut); 209 } 210 zipOut.closeEntry(); 211 } 212 compressFile(moduleJson, isStored, MODULE_JSON, zipOut); 213 compressFile(packInfo, isStored, PACK_INFO, zipOut); 214 } 215 compressFile(Path file, boolean isStored, String entryName, ZipOutputStream zipOut)216 private static void compressFile(Path file, boolean isStored, String entryName, ZipOutputStream zipOut) 217 throws BundleException, IOException { 218 try (InputStream input = Files.newInputStream(file)) { 219 if (isStored) { 220 ZipEntry newEntry = new ZipEntry(entryName); 221 newEntry.setMethod(ZipEntry.STORED); 222 File jsonFile = file.toFile(); 223 newEntry.setCompressedSize(jsonFile.length()); 224 CRC32 crc = getCrcFromFile(jsonFile); 225 newEntry.setCrc(crc.getValue()); 226 zipOut.putNextEntry(newEntry); 227 } else { 228 ZipEntry newEntry = new ZipEntry(entryName); 229 newEntry.setMethod(ZipEntry.DEFLATED); 230 zipOut.putNextEntry(newEntry); 231 } 232 IOUtils.copy(input, zipOut); 233 zipOut.closeEntry(); 234 } 235 } 236 237 /** 238 * get crc32 from file 239 * @param file input file 240 * @return cac32 value 241 * @throws BundleException bundle exception 242 */ getCrcFromFile(File file)243 public static CRC32 getCrcFromFile(File file) throws BundleException { 244 CRC32 crc = new CRC32(); 245 try (FileInputStream fileInputStream = new FileInputStream(file)) { 246 byte[] buffer = new byte[BUFFER_SIZE]; 247 int count = fileInputStream.read(buffer); 248 while (count > 0) { 249 crc.update(buffer, 0, count); 250 count = fileInputStream.read(buffer); 251 } 252 } catch (FileNotFoundException ignored) { 253 LOG.error(PackingToolErrMsg.FILE_NOT_FOUND.toString( 254 "Get Crc from file exist FileNotFoundException: " + ignored.getMessage())); 255 throw new BundleException("Get Crc from file failed."); 256 } catch (IOException exception) { 257 LOG.error(PackingToolErrMsg.IO_EXCEPTION.toString( 258 "Get Crc from file exist IOException: " + exception.getMessage())); 259 throw new BundleException("Get Crc from file failed, io exception."); 260 } 261 return crc; 262 } 263 } 264