1/*
2 * Copyright 2016, 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
32package org.jf.dexlib2.dexbacked;
33
34import com.google.common.collect.Lists;
35import com.google.common.io.ByteStreams;
36import org.jf.dexlib2.Opcodes;
37import org.jf.dexlib2.dexbacked.DexBackedDexFile.NotADexFile;
38import org.jf.dexlib2.dexbacked.ZipDexContainer.ZipDexFile;
39import org.jf.dexlib2.iface.MultiDexContainer;
40import org.jf.dexlib2.util.DexUtil;
41import org.jf.dexlib2.util.DexUtil.InvalidFile;
42import org.jf.dexlib2.util.DexUtil.UnsupportedFile;
43
44import javax.annotation.Nonnull;
45import javax.annotation.Nullable;
46import java.io.BufferedInputStream;
47import java.io.File;
48import java.io.IOException;
49import java.io.InputStream;
50import java.util.Enumeration;
51import java.util.List;
52import java.util.zip.ZipEntry;
53import java.util.zip.ZipFile;
54
55/**
56 * Represents a zip file that contains dex files (i.e. an apk or jar file)
57 */
58public class ZipDexContainer implements MultiDexContainer<ZipDexFile> {
59
60    private final File zipFilePath;
61    private final Opcodes opcodes;
62
63    /**
64     * Constructs a new ZipDexContainer for the given zip file
65     *
66     * @param zipFilePath The path to the zip file
67     * @param opcodes The Opcodes instance to use when loading dex files from this container
68     */
69    public ZipDexContainer(@Nonnull File zipFilePath, @Nonnull Opcodes opcodes) {
70        this.zipFilePath = zipFilePath;
71        this.opcodes = opcodes;
72    }
73
74    @Nonnull @Override public Opcodes getOpcodes() {
75        return opcodes;
76    }
77
78    /**
79     * Gets a list of the names of dex files in this zip file.
80     *
81     * @return A list of the names of dex files in this zip file
82     */
83    @Nonnull @Override public List<String> getDexEntryNames() throws IOException {
84        List<String> entryNames = Lists.newArrayList();
85        ZipFile zipFile = getZipFile();
86        try {
87            Enumeration<? extends ZipEntry> entriesEnumeration = zipFile.entries();
88
89            while (entriesEnumeration.hasMoreElements()) {
90                ZipEntry entry = entriesEnumeration.nextElement();
91
92                if (!isDex(zipFile, entry)) {
93                    continue;
94                }
95
96                entryNames.add(entry.getName());
97            }
98
99            return entryNames;
100        } finally {
101            zipFile.close();
102        }
103    }
104
105    /**
106     * Loads a dex file from a specific named entry.
107     *
108     * @param entryName The name of the entry
109     * @return A ZipDexFile, or null if there is no entry with the given name
110     * @throws NotADexFile If the entry isn't a dex file
111     */
112    @Nullable @Override public ZipDexFile getEntry(@Nonnull String entryName) throws IOException {
113        ZipFile zipFile = getZipFile();
114        try {
115            ZipEntry entry = zipFile.getEntry(entryName);
116            if (entry == null) {
117                return null;
118            }
119
120            return loadEntry(zipFile, entry);
121        } finally {
122            zipFile.close();
123        }
124    }
125
126    public boolean isZipFile() {
127        ZipFile zipFile = null;
128        try {
129            zipFile = getZipFile();
130            return true;
131        } catch (IOException ex) {
132            return false;
133        } catch (NotAZipFileException ex) {
134            return false;
135        } finally {
136            if(zipFile != null) {
137                try {
138                    zipFile.close();
139                } catch (IOException ex) {
140                    // just eat it
141                }
142            }
143        }
144    }
145
146    public class ZipDexFile extends DexBackedDexFile implements MultiDexContainer.MultiDexFile {
147
148        private final String entryName;
149
150        protected ZipDexFile(@Nonnull Opcodes opcodes, @Nonnull byte[] buf, @Nonnull String entryName) {
151            super(opcodes, buf, 0);
152            this.entryName = entryName;
153        }
154
155        @Nonnull @Override public String getEntryName() {
156            return entryName;
157        }
158
159        @Nonnull @Override public MultiDexContainer getContainer() {
160            return ZipDexContainer.this;
161        }
162    }
163
164    protected boolean isDex(@Nonnull ZipFile zipFile, @Nonnull ZipEntry zipEntry) throws IOException {
165        InputStream inputStream = new BufferedInputStream(zipFile.getInputStream(zipEntry));
166        try {
167            DexUtil.verifyDexHeader(inputStream);
168        } catch (NotADexFile ex) {
169            return false;
170        } catch (InvalidFile ex) {
171            return false;
172        } catch (UnsupportedFile ex) {
173            return false;
174        } finally {
175            inputStream.close();
176        }
177        return true;
178    }
179
180    protected ZipFile getZipFile() throws IOException {
181        try {
182            return new ZipFile(zipFilePath);
183        } catch (IOException ex) {
184            throw new NotAZipFileException();
185        }
186    }
187
188    @Nonnull
189    protected ZipDexFile loadEntry(@Nonnull ZipFile zipFile, @Nonnull ZipEntry zipEntry) throws IOException {
190        InputStream inputStream = zipFile.getInputStream(zipEntry);
191        try {
192            byte[] buf = ByteStreams.toByteArray(inputStream);
193            return new ZipDexFile(opcodes, buf, zipEntry.getName());
194        } finally {
195            inputStream.close();
196        }
197    }
198
199    public static class NotAZipFileException extends RuntimeException {
200    }
201}
202