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