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("PackageNormalize::normalize BundleException: " + ex.getMessage()); 74 return false; 75 } catch (IOException ex) { 76 LOG.error("PackageNormalize::normalize IOException: " + ex.getMessage()); 77 return false; 78 } 79 } 80 return true; 81 } 82 normalize(Path hspPath, Path outPath, String bundleName, int versionCode)83 private static void normalize(Path hspPath, Path outPath, String bundleName, int versionCode) 84 throws BundleException, IOException { 85 Path outHspPath = outPath.resolve(hspPath.getFileName()); 86 Path tmpDir = Files.createTempDirectory(outPath, TMP); 87 Path moduleJson = Files.createFile(tmpDir.resolve(MODULE_JSON)); 88 Path packInfo = Files.createFile(tmpDir.resolve(PACK_INFO)); 89 try (ZipFile hspFile = new ZipFile(hspPath.toFile()); 90 OutputStream output = Files.newOutputStream(outHspPath); 91 ZipOutputStream zipOut = new ZipOutputStream(output)) { 92 // 1.unzip module.json and pack.info, copy to tmp 93 ZipEntry moduleEntry = hspFile.getEntry(MODULE_JSON); 94 if (moduleEntry != null) { 95 try (OutputStream out = Files.newOutputStream(moduleJson)) { 96 IOUtils.copy(hspFile.getInputStream(moduleEntry), out); 97 } 98 } 99 ZipEntry packEntry = hspFile.getEntry(PACK_INFO); 100 if (moduleEntry != null) { 101 try (OutputStream out = Files.newOutputStream(packInfo)) { 102 IOUtils.copy(hspFile.getInputStream(packEntry), out); 103 } 104 } 105 106 // 2.update bundleName, versionCode to module.json, pack.info 107 updateModuleJson(moduleJson, bundleName, versionCode); 108 updatePackInfo(packInfo, bundleName, versionCode); 109 110 // 3.zip hsp, module.json, pack.info to outPath 111 copyHsp(hspFile, moduleJson, packInfo, zipOut); 112 } finally { 113 rmdir(tmpDir); 114 } 115 } 116 rmdir(Path dir)117 private static void rmdir(Path dir) throws IOException { 118 Files.walkFileTree(dir, new SimpleFileVisitor<Path>() { 119 @Override 120 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { 121 Files.delete(file); 122 return FileVisitResult.CONTINUE; 123 } 124 125 @Override 126 public FileVisitResult postVisitDirectory(Path dir, IOException ex) throws IOException { 127 Files.delete(dir); 128 return FileVisitResult.CONTINUE; 129 } 130 }); 131 } 132 updateModuleJson(Path moduleJson, String bundleName, int versionCode)133 private static void updateModuleJson(Path moduleJson, String bundleName, int versionCode) 134 throws BundleException, IOException { 135 try (FileInputStream input = new FileInputStream(moduleJson.toFile())) { 136 JSONObject jsonObject = JSON.parseObject(input, JSONObject.class); 137 if (jsonObject == null) { 138 LOG.error("updateModuleJson failed, parse json is null."); 139 throw new BundleException("updateModuleJson failed, parse json is null."); 140 } 141 if (!jsonObject.containsKey(APP)) { 142 LOG.error("updateModuleJson failed, json format not invalid."); 143 throw new BundleException("updateModuleJson failed, json format invalid."); 144 } 145 JSONObject appObject = jsonObject.getJSONObject(APP); 146 appObject.put(BUNDLE_NAME, bundleName); 147 appObject.put(VERSION_CODE, versionCode); 148 Files.write(moduleJson, JSON.toJSONBytes(jsonObject, SerializerFeature.WriteMapNullValue, 149 SerializerFeature.WriteDateUseDateFormat, SerializerFeature.SortField), 150 StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE); 151 } 152 } 153 updatePackInfo(Path packInfo, String bundleName, int versionCode)154 private static void updatePackInfo(Path packInfo, String bundleName, int versionCode) 155 throws BundleException, IOException { 156 try (FileInputStream input = new FileInputStream(packInfo.toFile())) { 157 JSONObject jsonObject = JSON.parseObject(input, JSONObject.class); 158 if (jsonObject == null) { 159 LOG.error("updatePackInfo failed, json format invalid."); 160 throw new BundleException("updatePackInfo failed, json format invalid."); 161 } 162 JSONObject summaryObject = jsonObject.getJSONObject(SUMMARY); 163 if (summaryObject == null) { 164 LOG.error("updatePackInfo failed, json format invalid."); 165 throw new BundleException("updatePackInfo failed, json format invalid."); 166 } 167 JSONObject appObject = summaryObject.getJSONObject(APP); 168 if (appObject == null) { 169 LOG.error("updatePackInfo failed, json format invalid."); 170 throw new BundleException("updatePackInfo failed, json format invalid."); 171 } 172 appObject.put(BUNDLE_NAME, bundleName); 173 JSONObject versionObject = appObject.getJSONObject(VERSION); 174 if (versionObject == null) { 175 LOG.error("updatePackInfo failed, json format invalid."); 176 throw new BundleException("updatePackInfo failed, json format invalid."); 177 } 178 versionObject.put(CODE, versionCode); 179 Files.write(packInfo, JSON.toJSONBytes(jsonObject, SerializerFeature.WriteMapNullValue, 180 SerializerFeature.WriteDateUseDateFormat, SerializerFeature.SortField), 181 StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE); 182 } 183 } 184 copyHsp(ZipFile hspFile, Path moduleJson, Path packInfo, ZipOutputStream zipOut)185 private static void copyHsp(ZipFile hspFile, Path moduleJson, Path packInfo, ZipOutputStream zipOut) 186 throws BundleException, IOException { 187 zipOut.setLevel(Deflater.BEST_SPEED); 188 boolean isStored = true; 189 Enumeration<? extends ZipEntry> entries = hspFile.entries(); 190 while (entries.hasMoreElements()) { 191 ZipEntry zipEntry = entries.nextElement(); 192 if (MODULE_JSON.equals(zipEntry.getName()) || PACK_INFO.equals(zipEntry.getName())) { 193 isStored = zipEntry.getMethod() == ZipEntry.STORED; 194 continue; 195 } 196 ZipEntry newEntry = zipEntry.getMethod() == 197 ZipEntry.STORED ? new ZipEntry(zipEntry) : new ZipEntry(zipEntry.getName()); 198 zipOut.putNextEntry(newEntry); 199 if (!zipEntry.isDirectory()) { 200 IOUtils.copy(hspFile.getInputStream(zipEntry), zipOut); 201 } 202 zipOut.closeEntry(); 203 } 204 compressFile(moduleJson, isStored, MODULE_JSON, zipOut); 205 compressFile(packInfo, isStored, PACK_INFO, zipOut); 206 } 207 compressFile(Path file, boolean isStored, String entryName, ZipOutputStream zipOut)208 private static void compressFile(Path file, boolean isStored, String entryName, ZipOutputStream zipOut) 209 throws BundleException, IOException { 210 try (InputStream input = Files.newInputStream(file)) { 211 if (isStored) { 212 ZipEntry newEntry = new ZipEntry(entryName); 213 newEntry.setMethod(ZipEntry.STORED); 214 File jsonFile = file.toFile(); 215 newEntry.setCompressedSize(jsonFile.length()); 216 CRC32 crc = getCrcFromFile(jsonFile); 217 newEntry.setCrc(crc.getValue()); 218 zipOut.putNextEntry(newEntry); 219 } else { 220 ZipEntry newEntry = new ZipEntry(entryName); 221 newEntry.setMethod(ZipEntry.DEFLATED); 222 zipOut.putNextEntry(newEntry); 223 } 224 IOUtils.copy(input, zipOut); 225 zipOut.closeEntry(); 226 } 227 } 228 getCrcFromFile(File file)229 private static CRC32 getCrcFromFile(File file) throws BundleException { 230 CRC32 crc = new CRC32(); 231 try (FileInputStream fileInputStream = new FileInputStream(file)) { 232 byte[] buffer = new byte[BUFFER_SIZE]; 233 int count = fileInputStream.read(buffer); 234 while (count > 0) { 235 crc.update(buffer, 0, count); 236 count = fileInputStream.read(buffer); 237 } 238 } catch (FileNotFoundException ignored) { 239 LOG.error("PackageNormalize::getCrcFromFile file not found exception: " + ignored.getMessage()); 240 throw new BundleException("Get Crc from file failed."); 241 } catch (IOException exception) { 242 LOG.error("PackageNormalize::getCrcFromFile io exception: " + exception.getMessage()); 243 throw new BundleException("Get Crc from file failed, io exception."); 244 } 245 return crc; 246 } 247 } 248