1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.cts.releaseparser;
18 
19 import com.android.cts.releaseparser.ReleaseProto.*;
20 
21 import java.io.File;
22 import java.io.FileOutputStream;
23 import java.io.IOException;
24 import java.io.InputStream;
25 import java.util.ArrayList;
26 import java.util.Enumeration;
27 import java.util.HashMap;
28 import java.util.List;
29 import java.util.logging.Logger;
30 import java.util.zip.ZipEntry;
31 import java.util.zip.ZipFile;
32 
33 public class ZipParser extends FileParser {
34     private PackageFileContent.Builder mPackageFileContentBuilder;
35     private HashMap<String, Entry> mFileMap;
36     private List<String> mDependencies;
37     private List<String> mDynamicLoadingDependencies;
38     private boolean mParseSo;
39     private boolean mParseInternalApi;
40     private StringBuilder mCodeIdStringBuilder;
41 
42     // Todo: provide utilities to parse files in zip, e.g. SOs in an APK
ZipParser(File file)43     ZipParser(File file) {
44         super(file);
45         mParseSo = true;
46         // default off to avoid a large output
47         mParseInternalApi = false;
48         mCodeIdStringBuilder = null;
49     }
50 
51     @Override
getDependencies()52     public List<String> getDependencies() {
53         if (mDependencies == null) {
54             parseFileContent();
55         }
56         return mDependencies;
57     }
58 
59     @Override
getDynamicLoadingDependencies()60     public List<String> getDynamicLoadingDependencies() {
61         if (mDynamicLoadingDependencies == null) {
62             // This also parses DynamicLoadingDependencies
63             getDependencies();
64         }
65         return mDynamicLoadingDependencies;
66     }
67 
68     @Override
getCodeId()69     public String getCodeId() {
70         if (mCodeIdStringBuilder == null) {
71             parseFileContent();
72         }
73         return mCodeIdStringBuilder.toString();
74     }
75 
76     @Override
getFileEntryBuilder()77     public Entry.Builder getFileEntryBuilder() {
78         if (mFileEntryBuilder == null) {
79             super.getFileEntryBuilder();
80             parseFileContent();
81             mFileEntryBuilder.addAllDependencies(getDependencies());
82             mFileEntryBuilder.addAllDynamicLoadingDependencies(getDynamicLoadingDependencies());
83         }
84         return mFileEntryBuilder;
85     }
86 
setParseSo(boolean parseSo)87     public void setParseSo(boolean parseSo) {
88         mParseSo = parseSo;
89     }
90 
setParseInternalApi(boolean parseInternalApi)91     public void setParseInternalApi(boolean parseInternalApi) {
92         mParseInternalApi = parseInternalApi;
93     }
94 
getFilefromZip(ZipFile zipFile, ZipEntry zipEntry)95     public static File getFilefromZip(ZipFile zipFile, ZipEntry zipEntry) throws IOException {
96         String fileName = zipEntry.getName();
97         File tmpFile = File.createTempFile("RPZ", "");
98         tmpFile.deleteOnExit();
99         InputStream iStream = zipFile.getInputStream(zipEntry);
100         FileOutputStream fOutputStream = new FileOutputStream(tmpFile);
101         byte[] buffer = new byte[READ_BLOCK_SIZE];
102         int length;
103         while ((length = iStream.read(buffer)) >= 0) {
104             fOutputStream.write(buffer, 0, length);
105         }
106         iStream.close();
107         fOutputStream.close();
108         return tmpFile;
109     }
110 
getPackageFileContent()111     public PackageFileContent getPackageFileContent() {
112         if (mPackageFileContentBuilder == null) {
113             parseFileContent();
114         }
115         return mPackageFileContentBuilder.build();
116     }
117 
parseFileContent()118     private void parseFileContent() {
119         ZipFile zFile = null;
120         mPackageFileContentBuilder = PackageFileContent.newBuilder();
121         mFileMap = new HashMap<String, Entry>();
122         mDependencies = new ArrayList<String>();
123         mDynamicLoadingDependencies = new ArrayList<String>();
124         mCodeIdStringBuilder = new StringBuilder();
125 
126         try {
127             zFile = new ZipFile(getFile());
128 
129             final Enumeration<? extends ZipEntry> entries = zFile.entries();
130             while (entries.hasMoreElements()) {
131                 final ZipEntry entry = entries.nextElement();
132                 final String name = entry.getName();
133 
134                 Entry.Builder entryBuilder = Entry.newBuilder();
135                 entryBuilder.setName(name);
136                 entryBuilder.setContentId(String.format(CODE_ID_FORMAT, entry.hashCode()));
137                 entryBuilder.setSize(entry.getSize());
138                 if (entry.isDirectory()) {
139                     entryBuilder.setType(Entry.EntryType.FOLDER);
140                 } else {
141                     entryBuilder.setType(Entry.EntryType.FILE);
142                     appendToCodeID(entry);
143                     if (mParseSo) {
144                         // ToDo: to be optimized if taking too long
145                         if (name.endsWith(SO_EXT_TAG)) {
146                             try {
147                                 final File soFile = getFilefromZip(zFile, entry);
148                                 SoParser fParser = new SoParser(soFile);
149                                 fParser.setPackageName(name);
150                                 fParser.setParseInternalApi(mParseInternalApi);
151 
152                                 if (fParser.getDependencies() != null) {
153                                     mDependencies.addAll(fParser.getDependencies());
154                                 }
155                                 if (fParser.getDynamicLoadingDependencies() != null) {
156                                     mDynamicLoadingDependencies.addAll(
157                                             fParser.getDynamicLoadingDependencies());
158                                 }
159                                 entryBuilder.setAppInfo(fParser.getAppInfo());
160                             } catch (IOException ex) {
161                                 System.err.println(
162                                         "Failed to parse: " + name + "\n" + ex.getMessage());
163                             }
164                         }
165                     }
166                 }
167                 mFileMap.put(name, entryBuilder.build());
168             }
169             mPackageFileContentBuilder.putAllEntries(mFileMap);
170         } catch (IOException e) {
171             System.err.println("Failed to parse: " + getFileName() + "\n" + e.getMessage());
172             // error while opening a ZIP file
173         } finally {
174             if (zFile != null) {
175                 try {
176                     zFile.close();
177                 } catch (IOException e) {
178                     System.err.println("Failed to close: " + getFileName() + "\n" + e.getMessage());
179                 }
180             }
181         }
182     }
183 
appendToCodeID(ZipEntry zEntry)184     private void appendToCodeID(ZipEntry zEntry) {
185         String name = zEntry.getName();
186         if (name.endsWith(SO_EXT_TAG)
187                 || name.endsWith(DEX_EXT_TAG)
188                 || name.endsWith(ANDROID_MANIFEST_TAG)) {
189             mCodeIdStringBuilder.append(String.format(CODE_ID_FORMAT, zEntry.hashCode()));
190         }
191     }
192 
193     private static final String USAGE_MESSAGE =
194             "Usage: java -jar releaseparser.jar "
195                     + ZipParser.class.getCanonicalName()
196                     + " [-options <parameter>]...\n"
197                     + "           to prase ZIP file meta data\n"
198                     + "Options:\n"
199                     + "\t-i PATH\t The path of the file to be parsed.\n"
200                     + "\t-of PATH\t The file path of the output file instead of printing to System.out.\n"
201                     + "\t-pi \t Parses internal methods and fields too. Output will be large when parsing multiple files in a release.\n"
202                     + "\t-s \t Skips parsing embedded SO files if it takes too long time.\n";
203 
main(String[] args)204     public static void main(String[] args) {
205         try {
206             ArgumentParser argParser = new ArgumentParser(args);
207             String fileName = argParser.getParameterList("i").get(0);
208             String outputFileName = argParser.getParameterElement("of", 0);
209             boolean parseSo = !argParser.containsOption("s");
210             boolean parseInternalApi = argParser.containsOption("pi");
211 
212             File aFile = new File(fileName);
213             ZipParser aParser = new ZipParser(aFile);
214             aParser.setParseSo(parseSo);
215             aParser.setParseInternalApi(parseInternalApi);
216 
217             Entry.Builder fileEntryBuilder = aParser.getFileEntryBuilder();
218             writeTextFormatMessage(outputFileName, fileEntryBuilder.build());
219         } catch (Exception ex) {
220             System.out.printf(USAGE_MESSAGE);
221             System.err.printf(ex.getMessage());
222         }
223     }
224 
getLogger()225     private static Logger getLogger() {
226         return Logger.getLogger(ZipParser.class.getSimpleName());
227     }
228 }
229