1 /* 2 * Copyright 2012, Google Inc. 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are 7 * met: 8 * 9 * * Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * * Redistributions in binary form must reproduce the above 12 * copyright notice, this list of conditions and the following disclaimer 13 * in the documentation and/or other materials provided with the 14 * distribution. 15 * * Neither the name of Google Inc. nor the names of its 16 * contributors may be used to endorse or promote products derived from 17 * this software without specific prior written permission. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 */ 31 32 package org.jf.dexlib2; 33 34 import com.google.common.base.MoreObjects; 35 import com.google.common.io.ByteStreams; 36 import org.jf.dexlib2.dexbacked.DexBackedDexFile; 37 import org.jf.dexlib2.dexbacked.DexBackedOdexFile; 38 import org.jf.dexlib2.dexbacked.OatFile; 39 import org.jf.dexlib2.dexbacked.OatFile.NotAnOatFileException; 40 import org.jf.dexlib2.dexbacked.OatFile.OatDexFile; 41 import org.jf.dexlib2.iface.DexFile; 42 import org.jf.dexlib2.writer.pool.DexPool; 43 import org.jf.util.ExceptionWithContext; 44 45 import javax.annotation.Nonnull; 46 import javax.annotation.Nullable; 47 import java.io.*; 48 import java.util.List; 49 import java.util.zip.ZipEntry; 50 import java.util.zip.ZipFile; 51 52 public final class DexFileFactory { 53 @Nonnull 54 public static DexBackedDexFile loadDexFile(@Nonnull String path, int api) throws IOException { 55 return loadDexFile(path, api, false); 56 } 57 58 @Nonnull 59 public static DexBackedDexFile loadDexFile(@Nonnull String path, int api, boolean experimental) 60 throws IOException { 61 return loadDexFile(new File(path), "classes.dex", Opcodes.forApi(api, experimental)); 62 } 63 64 @Nonnull 65 public static DexBackedDexFile loadDexFile(@Nonnull File dexFile, int api) throws IOException { 66 return loadDexFile(dexFile, api, false); 67 } 68 69 @Nonnull 70 public static DexBackedDexFile loadDexFile(@Nonnull File dexFile, int api, boolean experimental) 71 throws IOException { 72 return loadDexFile(dexFile, null, Opcodes.forApi(api, experimental)); 73 } 74 75 @Nonnull 76 public static DexBackedDexFile loadDexFile(@Nonnull File dexFile, @Nullable String dexEntry, int api, 77 boolean experimental) throws IOException { 78 return loadDexFile(dexFile, dexEntry, Opcodes.forApi(api, experimental)); 79 } 80 81 @Nonnull 82 public static DexBackedDexFile loadDexFile(@Nonnull File dexFile, @Nullable String dexEntry, 83 @Nonnull Opcodes opcodes) throws IOException { 84 ZipFile zipFile = null; 85 boolean isZipFile = false; 86 try { 87 zipFile = new ZipFile(dexFile); 88 // if we get here, it's safe to assume we have a zip file 89 isZipFile = true; 90 91 String zipEntryName = MoreObjects.firstNonNull(dexEntry, "classes.dex"); 92 ZipEntry zipEntry = zipFile.getEntry(zipEntryName); 93 if (zipEntry == null) { 94 throw new DexFileNotFound("zip file %s does not contain a %s file", dexFile.getName(), zipEntryName); 95 } 96 long fileLength = zipEntry.getSize(); 97 if (fileLength < 40) { 98 throw new ExceptionWithContext("The %s file in %s is too small to be a valid dex file", 99 zipEntryName, dexFile.getName()); 100 } else if (fileLength > Integer.MAX_VALUE) { 101 throw new ExceptionWithContext("The %s file in %s is too large to read in", 102 zipEntryName, dexFile.getName()); 103 } 104 byte[] dexBytes = new byte[(int)fileLength]; 105 ByteStreams.readFully(zipFile.getInputStream(zipEntry), dexBytes); 106 return new DexBackedDexFile(opcodes, dexBytes); 107 } catch (IOException ex) { 108 // don't continue on if we know it's a zip file 109 if (isZipFile) { 110 throw ex; 111 } 112 } finally { 113 if (zipFile != null) { 114 try { 115 zipFile.close(); 116 } catch (IOException ex) { 117 // just eat it 118 } 119 } 120 } 121 122 InputStream inputStream = new BufferedInputStream(new FileInputStream(dexFile)); 123 try { 124 try { 125 return DexBackedDexFile.fromInputStream(opcodes, inputStream); 126 } catch (DexBackedDexFile.NotADexFile ex) { 127 // just eat it 128 } 129 130 // Note: DexBackedDexFile.fromInputStream will reset inputStream back to the same position, if it fails 131 132 try { 133 return DexBackedOdexFile.fromInputStream(opcodes, inputStream); 134 } catch (DexBackedOdexFile.NotAnOdexFile ex) { 135 // just eat it 136 } 137 138 OatFile oatFile = null; 139 try { 140 oatFile = OatFile.fromInputStream(inputStream); 141 } catch (NotAnOatFileException ex) { 142 // just eat it 143 } 144 145 if (oatFile != null) { 146 if (oatFile.isSupportedVersion() == OatFile.UNSUPPORTED) { 147 throw new UnsupportedOatVersionException(oatFile); 148 } 149 150 List<OatDexFile> oatDexFiles = oatFile.getDexFiles(); 151 152 if (oatDexFiles.size() == 0) { 153 throw new DexFileNotFound("Oat file %s contains no dex files", dexFile.getName()); 154 } 155 156 if (dexEntry == null) { 157 if (oatDexFiles.size() > 1) { 158 throw new MultipleDexFilesException(oatFile); 159 } 160 return oatDexFiles.get(0); 161 } else { 162 // first check for an exact match 163 for (OatDexFile oatDexFile : oatFile.getDexFiles()) { 164 if (oatDexFile.filename.equals(dexEntry)) { 165 return oatDexFile; 166 } 167 } 168 169 if (!dexEntry.contains("/")) { 170 for (OatDexFile oatDexFile : oatFile.getDexFiles()) { 171 File oatEntryFile = new File(oatDexFile.filename); 172 if (oatEntryFile.getName().equals(dexEntry)) { 173 return oatDexFile; 174 } 175 } 176 } 177 178 throw new DexFileNotFound("oat file %s does not contain a dex file named %s", 179 dexFile.getName(), dexEntry); 180 } 181 } 182 } finally { 183 inputStream.close(); 184 } 185 186 throw new ExceptionWithContext("%s is not an apk, dex, odex or oat file.", dexFile.getPath()); 187 } 188 189 public static void writeDexFile(@Nonnull String path, @Nonnull DexFile dexFile) throws IOException { 190 DexPool.writeTo(path, dexFile); 191 } 192 193 private DexFileFactory() {} 194 195 public static class DexFileNotFound extends ExceptionWithContext { 196 public DexFileNotFound(@Nullable Throwable cause) { 197 super(cause); 198 } 199 200 public DexFileNotFound(@Nullable Throwable cause, @Nullable String message, Object... formatArgs) { 201 super(cause, message, formatArgs); 202 } 203 204 public DexFileNotFound(@Nullable String message, Object... formatArgs) { 205 super(message, formatArgs); 206 } 207 } 208 209 public static class MultipleDexFilesException extends ExceptionWithContext { 210 @Nonnull public final OatFile oatFile; 211 212 public MultipleDexFilesException(@Nonnull OatFile oatFile) { 213 super("Oat file has multiple dex files."); 214 this.oatFile = oatFile; 215 } 216 } 217 218 public static class UnsupportedOatVersionException extends ExceptionWithContext { 219 @Nonnull public final OatFile oatFile; 220 221 public UnsupportedOatVersionException(@Nonnull OatFile oatFile) { 222 super("Unsupported oat version: %d", oatFile.getOatVersion()); 223 this.oatFile = oatFile; 224 } 225 } 226 } 227