Home | History | Annotate | Download | only in dexlib2
      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