• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2024 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 package ohos;
17 
18 import com.alibaba.fastjson.JSON;
19 import com.alibaba.fastjson.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