1adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project/*
2adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project * Licensed to the Apache Software Foundation (ASF) under one or more
3adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project * contributor license agreements.  See the NOTICE file distributed with
4adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project * this work for additional information regarding copyright ownership.
5adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project * The ASF licenses this file to You under the Apache License, Version 2.0
6adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project * (the "License"); you may not use this file except in compliance with
7adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project * the License.  You may obtain a copy of the License at
8adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project *
9f7c6911047d63bc76292f55ce538da32818dd931Jesse Wilson *     http://www.apache.org/licenses/LICENSE-2.0
10adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project *
11adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project * Unless required by applicable law or agreed to in writing, software
12adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project * distributed under the License is distributed on an "AS IS" BASIS,
13adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project * See the License for the specific language governing permissions and
15adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project * limitations under the License.
16adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project */
17adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
18adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Projectpackage java.util.zip;
19adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
20f7aab022dcbfcd8f27b409ab92b4bca4a84d0b8aBrian Carlstromimport dalvik.system.CloseGuard;
21adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Projectimport java.io.BufferedInputStream;
229902f3494c6d983879d8b9cfe6b1f771cfefe703Elliott Hughesimport java.io.Closeable;
23739493443ce2ea5b0a92dd1725a4ed630db7b27bElliott Hughesimport java.io.DataInputStream;
24adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Projectimport java.io.File;
25adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Projectimport java.io.IOException;
26adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Projectimport java.io.InputStream;
27adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Projectimport java.io.RandomAccessFile;
2843a9f774d075e0e441d8b996e3f6c81ea483ec89Elliott Hughesimport java.nio.ByteOrder;
29820c09bae53d20c5954d544ebc8a6ee8e54abbaeElliott Hughesimport java.nio.charset.StandardCharsets;
30adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Projectimport java.util.Enumeration;
31f7c6911047d63bc76292f55ce538da32818dd931Jesse Wilsonimport java.util.Iterator;
327365de1056414750d0a7d1fdd26025fd247f0d04Jesse Wilsonimport java.util.LinkedHashMap;
3343a9f774d075e0e441d8b996e3f6c81ea483ec89Elliott Hughesimport libcore.io.BufferIterator;
3443a9f774d075e0e441d8b996e3f6c81ea483ec89Elliott Hughesimport libcore.io.HeapBufferIterator;
35a6f350c645dbb66d68cc2b03afb8f2eeaa88fbbaPaul Duffinimport libcore.io.IoUtils;
36ff8234c90ecab9f1db368924bf92a5b16460f9b5Elliott Hughesimport libcore.io.Streams;
37adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
38adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project/**
39f05aeedc00c8e7ab7650067ce1dc301547a3914bElliott Hughes * This class provides random read access to a zip file. You pay more to read
4013f30b9167639ad63da1707102db6320e8f76474Elliott Hughes * the zip file's central directory up front (from the constructor), but if you're using
4113f30b9167639ad63da1707102db6320e8f76474Elliott Hughes * {@link #getEntry} to look up multiple files by name, you get the benefit of this index.
4257995e8186b54515d5a03bf2ab104c3dc247f1b6Jesse Wilson *
433d2b2ad2cd1f05ba72a550082083da4b5898f30bKenny Root * <p>If you only want to iterate through all the files (using {@link #entries()}, you should
4413f30b9167639ad63da1707102db6320e8f76474Elliott Hughes * consider {@link ZipInputStream}, which provides stream-like read access to a zip file and
45f05aeedc00c8e7ab7650067ce1dc301547a3914bElliott Hughes * has a lower up-front cost because you don't pay to build an in-memory index.
4613f30b9167639ad63da1707102db6320e8f76474Elliott Hughes *
4713f30b9167639ad63da1707102db6320e8f76474Elliott Hughes * <p>If you want to create a zip file, use {@link ZipOutputStream}. There is no API for updating
4813f30b9167639ad63da1707102db6320e8f76474Elliott Hughes * an existing zip file.
49adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project */
509902f3494c6d983879d8b9cfe6b1f771cfefe703Elliott Hughespublic class ZipFile implements Closeable, ZipConstants {
511d8bb71cc4f69089311416a8402f492b7af4784dElliott Hughes    /**
527a302a49a7c8b99e2f34fff660e199fb7c776bc1William Luh     * General Purpose Bit Flags, Bit 0.
537a302a49a7c8b99e2f34fff660e199fb7c776bc1William Luh     * If set, indicates that the file is encrypted.
547a302a49a7c8b99e2f34fff660e199fb7c776bc1William Luh     */
557a302a49a7c8b99e2f34fff660e199fb7c776bc1William Luh    static final int GPBF_ENCRYPTED_FLAG = 1 << 0;
567a302a49a7c8b99e2f34fff660e199fb7c776bc1William Luh
577a302a49a7c8b99e2f34fff660e199fb7c776bc1William Luh    /**
581d8bb71cc4f69089311416a8402f492b7af4784dElliott Hughes     * General Purpose Bit Flags, Bit 3.
591d8bb71cc4f69089311416a8402f492b7af4784dElliott Hughes     * If this bit is set, the fields crc-32, compressed
601d8bb71cc4f69089311416a8402f492b7af4784dElliott Hughes     * size and uncompressed size are set to zero in the
611d8bb71cc4f69089311416a8402f492b7af4784dElliott Hughes     * local header.  The correct values are put in the
621d8bb71cc4f69089311416a8402f492b7af4784dElliott Hughes     * data descriptor immediately following the compressed
631d8bb71cc4f69089311416a8402f492b7af4784dElliott Hughes     * data.  (Note: PKZIP version 2.04g for DOS only
641d8bb71cc4f69089311416a8402f492b7af4784dElliott Hughes     * recognizes this bit for method 8 compression, newer
651d8bb71cc4f69089311416a8402f492b7af4784dElliott Hughes     * versions of PKZIP recognize this bit for any
661d8bb71cc4f69089311416a8402f492b7af4784dElliott Hughes     * compression method.)
671d8bb71cc4f69089311416a8402f492b7af4784dElliott Hughes     */
68fb0ec0e650bf8be35acb0d47da0311a7c446aa33Elliott Hughes    static final int GPBF_DATA_DESCRIPTOR_FLAG = 1 << 3;
691d8bb71cc4f69089311416a8402f492b7af4784dElliott Hughes
701d8bb71cc4f69089311416a8402f492b7af4784dElliott Hughes    /**
711d8bb71cc4f69089311416a8402f492b7af4784dElliott Hughes     * General Purpose Bit Flags, Bit 11.
721d8bb71cc4f69089311416a8402f492b7af4784dElliott Hughes     * Language encoding flag (EFS).  If this bit is set,
731d8bb71cc4f69089311416a8402f492b7af4784dElliott Hughes     * the filename and comment fields for this file
741d8bb71cc4f69089311416a8402f492b7af4784dElliott Hughes     * must be encoded using UTF-8.
751d8bb71cc4f69089311416a8402f492b7af4784dElliott Hughes     */
76fb0ec0e650bf8be35acb0d47da0311a7c446aa33Elliott Hughes    static final int GPBF_UTF8_FLAG = 1 << 11;
77adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
78adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    /**
796aed748da1deaa820d574446037401893d73be9bWilliam Luh     * Supported General Purpose Bit Flags Mask.
807a302a49a7c8b99e2f34fff660e199fb7c776bc1William Luh     * Bit mask of bits not supported.
817a302a49a7c8b99e2f34fff660e199fb7c776bc1William Luh     * Note: The only bit that we will enforce at this time
827a302a49a7c8b99e2f34fff660e199fb7c776bc1William Luh     * is the encrypted bit. Although other bits are not supported,
837a302a49a7c8b99e2f34fff660e199fb7c776bc1William Luh     * we must not enforce them as this could break some legitimate
847a302a49a7c8b99e2f34fff660e199fb7c776bc1William Luh     * use cases (See http://b/8617715).
856aed748da1deaa820d574446037401893d73be9bWilliam Luh     */
867a302a49a7c8b99e2f34fff660e199fb7c776bc1William Luh    static final int GPBF_UNSUPPORTED_MASK = GPBF_ENCRYPTED_FLAG;
876aed748da1deaa820d574446037401893d73be9bWilliam Luh
886aed748da1deaa820d574446037401893d73be9bWilliam Luh    /**
89f05aeedc00c8e7ab7650067ce1dc301547a3914bElliott Hughes     * Open zip file for reading.
90adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     */
91adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    public static final int OPEN_READ = 1;
92adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
93adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    /**
94f05aeedc00c8e7ab7650067ce1dc301547a3914bElliott Hughes     * Delete zip file when closed.
95adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     */
96adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    public static final int OPEN_DELETE = 4;
97adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
98f05aeedc00c8e7ab7650067ce1dc301547a3914bElliott Hughes    private final String filename;
99f7c6911047d63bc76292f55ce538da32818dd931Jesse Wilson
100f05aeedc00c8e7ab7650067ce1dc301547a3914bElliott Hughes    private File fileToDeleteOnClose;
101f7c6911047d63bc76292f55ce538da32818dd931Jesse Wilson
102f05aeedc00c8e7ab7650067ce1dc301547a3914bElliott Hughes    private RandomAccessFile raf;
103f7c6911047d63bc76292f55ce538da32818dd931Jesse Wilson
104f05aeedc00c8e7ab7650067ce1dc301547a3914bElliott Hughes    private final LinkedHashMap<String, ZipEntry> entries = new LinkedHashMap<String, ZipEntry>();
105f7c6911047d63bc76292f55ce538da32818dd931Jesse Wilson
106820c09bae53d20c5954d544ebc8a6ee8e54abbaeElliott Hughes    private String comment;
107820c09bae53d20c5954d544ebc8a6ee8e54abbaeElliott Hughes
10812f2d8e2760b78c673b7a187b9062b3938a03147Brian Carlstrom    private final CloseGuard guard = CloseGuard.get();
109f7aab022dcbfcd8f27b409ab92b4bca4a84d0b8aBrian Carlstrom
110adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    /**
11113f30b9167639ad63da1707102db6320e8f76474Elliott Hughes     * Constructs a new {@code ZipFile} allowing read access to the contents of the given file.
112e3d756c5dae1af2aa5f0ad8bc7f133df3e7401ebNeil Fuller     *
113e3d756c5dae1af2aa5f0ad8bc7f133df3e7401ebNeil Fuller     * <p>UTF-8 is used to decode all comments and entry names in the file.
114e3d756c5dae1af2aa5f0ad8bc7f133df3e7401ebNeil Fuller     *
115f05aeedc00c8e7ab7650067ce1dc301547a3914bElliott Hughes     * @throws ZipException if a zip error occurs.
11613f30b9167639ad63da1707102db6320e8f76474Elliott Hughes     * @throws IOException if an {@code IOException} occurs.
117adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     */
118adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    public ZipFile(File file) throws ZipException, IOException {
11963744c884dd4b4f4307f2b021fb894af164972afElliott Hughes        this(file, OPEN_READ);
120e3d756c5dae1af2aa5f0ad8bc7f133df3e7401ebNeil Fuller    }
121e3d756c5dae1af2aa5f0ad8bc7f133df3e7401ebNeil Fuller
122e3d756c5dae1af2aa5f0ad8bc7f133df3e7401ebNeil Fuller    /**
123e3d756c5dae1af2aa5f0ad8bc7f133df3e7401ebNeil Fuller     * Constructs a new {@code ZipFile} allowing read access to the contents of the given file.
124e3d756c5dae1af2aa5f0ad8bc7f133df3e7401ebNeil Fuller     *
125e3d756c5dae1af2aa5f0ad8bc7f133df3e7401ebNeil Fuller     * <p>UTF-8 is used to decode all comments and entry names in the file.
126e3d756c5dae1af2aa5f0ad8bc7f133df3e7401ebNeil Fuller     *
12713f30b9167639ad63da1707102db6320e8f76474Elliott Hughes     * @throws IOException if an IOException occurs.
12813f30b9167639ad63da1707102db6320e8f76474Elliott Hughes     */
12913f30b9167639ad63da1707102db6320e8f76474Elliott Hughes    public ZipFile(String name) throws IOException {
13063744c884dd4b4f4307f2b021fb894af164972afElliott Hughes        this(new File(name), OPEN_READ);
13113f30b9167639ad63da1707102db6320e8f76474Elliott Hughes    }
13213f30b9167639ad63da1707102db6320e8f76474Elliott Hughes
13313f30b9167639ad63da1707102db6320e8f76474Elliott Hughes    /**
13413f30b9167639ad63da1707102db6320e8f76474Elliott Hughes     * Constructs a new {@code ZipFile} allowing access to the given file.
13557995e8186b54515d5a03bf2ab104c3dc247f1b6Jesse Wilson     *
136e3d756c5dae1af2aa5f0ad8bc7f133df3e7401ebNeil Fuller     * <p>UTF-8 is used to decode all comments and entry names in the file.
137e3d756c5dae1af2aa5f0ad8bc7f133df3e7401ebNeil Fuller     *
138e3d756c5dae1af2aa5f0ad8bc7f133df3e7401ebNeil Fuller     * <p>The {@code mode} must be either {@code OPEN_READ} or {@code OPEN_READ|OPEN_DELETE}.
139e3d756c5dae1af2aa5f0ad8bc7f133df3e7401ebNeil Fuller     * If the {@code OPEN_DELETE} flag is supplied, the file will be deleted at or before the
14013f30b9167639ad63da1707102db6320e8f76474Elliott Hughes     * time that the {@code ZipFile} is closed (the contents will remain accessible until
14113f30b9167639ad63da1707102db6320e8f76474Elliott Hughes     * this {@code ZipFile} is closed); it also calls {@code File.deleteOnExit}.
14213f30b9167639ad63da1707102db6320e8f76474Elliott Hughes     *
14313f30b9167639ad63da1707102db6320e8f76474Elliott Hughes     * @throws IOException if an {@code IOException} occurs.
144adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     */
145adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    public ZipFile(File file, int mode) throws IOException {
146f05aeedc00c8e7ab7650067ce1dc301547a3914bElliott Hughes        filename = file.getPath();
147f7c6911047d63bc76292f55ce538da32818dd931Jesse Wilson        if (mode != OPEN_READ && mode != (OPEN_READ | OPEN_DELETE)) {
148cff1616012dc0d56c2da9af2b9b1183e76c7e044Elliott Hughes            throw new IllegalArgumentException("Bad mode: " + mode);
149f7c6911047d63bc76292f55ce538da32818dd931Jesse Wilson        }
150f7c6911047d63bc76292f55ce538da32818dd931Jesse Wilson
151f7c6911047d63bc76292f55ce538da32818dd931Jesse Wilson        if ((mode & OPEN_DELETE) != 0) {
152f05aeedc00c8e7ab7650067ce1dc301547a3914bElliott Hughes            fileToDeleteOnClose = file;
153f05aeedc00c8e7ab7650067ce1dc301547a3914bElliott Hughes            fileToDeleteOnClose.deleteOnExit();
15457995e8186b54515d5a03bf2ab104c3dc247f1b6Jesse Wilson        } else {
155f05aeedc00c8e7ab7650067ce1dc301547a3914bElliott Hughes            fileToDeleteOnClose = null;
15657995e8186b54515d5a03bf2ab104c3dc247f1b6Jesse Wilson        }
15757995e8186b54515d5a03bf2ab104c3dc247f1b6Jesse Wilson
158f05aeedc00c8e7ab7650067ce1dc301547a3914bElliott Hughes        raf = new RandomAccessFile(filename, "r");
159adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
160a6f350c645dbb66d68cc2b03afb8f2eeaa88fbbaPaul Duffin        // Make sure to close the RandomAccessFile if reading the central directory fails.
161a6f350c645dbb66d68cc2b03afb8f2eeaa88fbbaPaul Duffin        boolean mustCloseFile = true;
162a6f350c645dbb66d68cc2b03afb8f2eeaa88fbbaPaul Duffin        try {
163a6f350c645dbb66d68cc2b03afb8f2eeaa88fbbaPaul Duffin            readCentralDir();
164a6f350c645dbb66d68cc2b03afb8f2eeaa88fbbaPaul Duffin
165a6f350c645dbb66d68cc2b03afb8f2eeaa88fbbaPaul Duffin            // Read succeeded so do not close the underlying RandomAccessFile.
166a6f350c645dbb66d68cc2b03afb8f2eeaa88fbbaPaul Duffin            mustCloseFile = false;
167a6f350c645dbb66d68cc2b03afb8f2eeaa88fbbaPaul Duffin        } finally {
168a6f350c645dbb66d68cc2b03afb8f2eeaa88fbbaPaul Duffin            if (mustCloseFile) {
169a6f350c645dbb66d68cc2b03afb8f2eeaa88fbbaPaul Duffin                IoUtils.closeQuietly(raf);
170a6f350c645dbb66d68cc2b03afb8f2eeaa88fbbaPaul Duffin            }
171a6f350c645dbb66d68cc2b03afb8f2eeaa88fbbaPaul Duffin        }
172a6f350c645dbb66d68cc2b03afb8f2eeaa88fbbaPaul Duffin
17312f2d8e2760b78c673b7a187b9062b3938a03147Brian Carlstrom        guard.open("close");
174adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    }
175adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
176e2f58c9501eac730d048199906dc41fe8e4cd6e9Brian Carlstrom    @Override protected void finalize() throws IOException {
177e2f58c9501eac730d048199906dc41fe8e4cd6e9Brian Carlstrom        try {
17812f2d8e2760b78c673b7a187b9062b3938a03147Brian Carlstrom            if (guard != null) {
17912f2d8e2760b78c673b7a187b9062b3938a03147Brian Carlstrom                guard.warnIfOpen();
18012f2d8e2760b78c673b7a187b9062b3938a03147Brian Carlstrom            }
181e2f58c9501eac730d048199906dc41fe8e4cd6e9Brian Carlstrom        } finally {
182e2f58c9501eac730d048199906dc41fe8e4cd6e9Brian Carlstrom            try {
183e2f58c9501eac730d048199906dc41fe8e4cd6e9Brian Carlstrom                super.finalize();
184e2f58c9501eac730d048199906dc41fe8e4cd6e9Brian Carlstrom            } catch (Throwable t) {
185e2f58c9501eac730d048199906dc41fe8e4cd6e9Brian Carlstrom                throw new AssertionError(t);
186e2f58c9501eac730d048199906dc41fe8e4cd6e9Brian Carlstrom            }
187e2f58c9501eac730d048199906dc41fe8e4cd6e9Brian Carlstrom        }
188adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    }
189adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
190adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    /**
191f05aeedc00c8e7ab7650067ce1dc301547a3914bElliott Hughes     * Closes this zip file. This method is idempotent. This method may cause I/O if the
192f05aeedc00c8e7ab7650067ce1dc301547a3914bElliott Hughes     * zip file needs to be deleted.
19357995e8186b54515d5a03bf2ab104c3dc247f1b6Jesse Wilson     *
194adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * @throws IOException
195adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     *             if an IOException occurs.
196adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     */
197adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    public void close() throws IOException {
198f7aab022dcbfcd8f27b409ab92b4bca4a84d0b8aBrian Carlstrom        guard.close();
199adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
200f05aeedc00c8e7ab7650067ce1dc301547a3914bElliott Hughes        RandomAccessFile localRaf = raf;
201f05aeedc00c8e7ab7650067ce1dc301547a3914bElliott Hughes        if (localRaf != null) { // Only close initialized instances
202f05aeedc00c8e7ab7650067ce1dc301547a3914bElliott Hughes            synchronized (localRaf) {
203f05aeedc00c8e7ab7650067ce1dc301547a3914bElliott Hughes                raf = null;
204f05aeedc00c8e7ab7650067ce1dc301547a3914bElliott Hughes                localRaf.close();
205adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project            }
206f05aeedc00c8e7ab7650067ce1dc301547a3914bElliott Hughes            if (fileToDeleteOnClose != null) {
207f05aeedc00c8e7ab7650067ce1dc301547a3914bElliott Hughes                fileToDeleteOnClose.delete();
208f05aeedc00c8e7ab7650067ce1dc301547a3914bElliott Hughes                fileToDeleteOnClose = null;
209adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project            }
210adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        }
211adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    }
212adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
213f7f90ec7f63bd80b7724d64ea664fdb589052fceJesse Wilson    private void checkNotClosed() {
214f05aeedc00c8e7ab7650067ce1dc301547a3914bElliott Hughes        if (raf == null) {
21560eb73fc9f0636c3d0bda5baba641a5f88f8476fElliott Hughes            throw new IllegalStateException("Zip file closed");
216f7f90ec7f63bd80b7724d64ea664fdb589052fceJesse Wilson        }
217f7f90ec7f63bd80b7724d64ea664fdb589052fceJesse Wilson    }
218f7f90ec7f63bd80b7724d64ea664fdb589052fceJesse Wilson
219adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    /**
220adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * Returns an enumeration of the entries. The entries are listed in the
221f05aeedc00c8e7ab7650067ce1dc301547a3914bElliott Hughes     * order in which they appear in the zip file.
22257995e8186b54515d5a03bf2ab104c3dc247f1b6Jesse Wilson     *
223f05aeedc00c8e7ab7650067ce1dc301547a3914bElliott Hughes     * <p>If you only need to iterate over the entries in a zip file, and don't
224f05aeedc00c8e7ab7650067ce1dc301547a3914bElliott Hughes     * need random-access entry lookup by name, you should probably use {@link ZipInputStream}
225f05aeedc00c8e7ab7650067ce1dc301547a3914bElliott Hughes     * instead, to avoid paying to construct the in-memory index.
226f05aeedc00c8e7ab7650067ce1dc301547a3914bElliott Hughes     *
227f05aeedc00c8e7ab7650067ce1dc301547a3914bElliott Hughes     * @throws IllegalStateException if this zip file has been closed.
228adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     */
229adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    public Enumeration<? extends ZipEntry> entries() {
230f7f90ec7f63bd80b7724d64ea664fdb589052fceJesse Wilson        checkNotClosed();
231f05aeedc00c8e7ab7650067ce1dc301547a3914bElliott Hughes        final Iterator<ZipEntry> iterator = entries.values().iterator();
232f7f90ec7f63bd80b7724d64ea664fdb589052fceJesse Wilson
233adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        return new Enumeration<ZipEntry>() {
234adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project            public boolean hasMoreElements() {
235f7f90ec7f63bd80b7724d64ea664fdb589052fceJesse Wilson                checkNotClosed();
236f7c6911047d63bc76292f55ce538da32818dd931Jesse Wilson                return iterator.hasNext();
237adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project            }
238adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
239adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project            public ZipEntry nextElement() {
240f7f90ec7f63bd80b7724d64ea664fdb589052fceJesse Wilson                checkNotClosed();
241f7c6911047d63bc76292f55ce538da32818dd931Jesse Wilson                return iterator.next();
242adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project            }
243adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        };
244adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    }
245adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
246adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    /**
247820c09bae53d20c5954d544ebc8a6ee8e54abbaeElliott Hughes     * Returns this file's comment, or null if it doesn't have one.
248820c09bae53d20c5954d544ebc8a6ee8e54abbaeElliott Hughes     * See {@link ZipOutputStream#setComment}.
249820c09bae53d20c5954d544ebc8a6ee8e54abbaeElliott Hughes     *
250820c09bae53d20c5954d544ebc8a6ee8e54abbaeElliott Hughes     * @throws IllegalStateException if this zip file has been closed.
251820c09bae53d20c5954d544ebc8a6ee8e54abbaeElliott Hughes     * @since 1.7
252820c09bae53d20c5954d544ebc8a6ee8e54abbaeElliott Hughes     */
253820c09bae53d20c5954d544ebc8a6ee8e54abbaeElliott Hughes    public String getComment() {
254820c09bae53d20c5954d544ebc8a6ee8e54abbaeElliott Hughes        checkNotClosed();
255820c09bae53d20c5954d544ebc8a6ee8e54abbaeElliott Hughes        return comment;
256820c09bae53d20c5954d544ebc8a6ee8e54abbaeElliott Hughes    }
257820c09bae53d20c5954d544ebc8a6ee8e54abbaeElliott Hughes
258820c09bae53d20c5954d544ebc8a6ee8e54abbaeElliott Hughes    /**
259f05aeedc00c8e7ab7650067ce1dc301547a3914bElliott Hughes     * Returns the zip entry with the given name, or null if there is no such entry.
26057995e8186b54515d5a03bf2ab104c3dc247f1b6Jesse Wilson     *
261f05aeedc00c8e7ab7650067ce1dc301547a3914bElliott Hughes     * @throws IllegalStateException if this zip file has been closed.
262adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     */
263adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    public ZipEntry getEntry(String entryName) {
264f7f90ec7f63bd80b7724d64ea664fdb589052fceJesse Wilson        checkNotClosed();
265f7c6911047d63bc76292f55ce538da32818dd931Jesse Wilson        if (entryName == null) {
26686acc043d3334651ee26c65467d78d6cefedd397Kenny Root            throw new NullPointerException("entryName == null");
267f7c6911047d63bc76292f55ce538da32818dd931Jesse Wilson        }
268f7c6911047d63bc76292f55ce538da32818dd931Jesse Wilson
269f05aeedc00c8e7ab7650067ce1dc301547a3914bElliott Hughes        ZipEntry ze = entries.get(entryName);
270f7c6911047d63bc76292f55ce538da32818dd931Jesse Wilson        if (ze == null) {
271f05aeedc00c8e7ab7650067ce1dc301547a3914bElliott Hughes            ze = entries.get(entryName + "/");
272adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        }
273f7c6911047d63bc76292f55ce538da32818dd931Jesse Wilson        return ze;
274adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    }
275adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
276adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    /**
277adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * Returns an input stream on the data of the specified {@code ZipEntry}.
27857995e8186b54515d5a03bf2ab104c3dc247f1b6Jesse Wilson     *
279adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * @param entry
280adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     *            the ZipEntry.
281adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * @return an input stream of the data contained in the {@code ZipEntry}.
282adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * @throws IOException
283adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     *             if an {@code IOException} occurs.
284f05aeedc00c8e7ab7650067ce1dc301547a3914bElliott Hughes     * @throws IllegalStateException if this zip file has been closed.
285adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     */
286adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    public InputStream getInputStream(ZipEntry entry) throws IOException {
287739493443ce2ea5b0a92dd1725a4ed630db7b27bElliott Hughes        // Make sure this ZipEntry is in this Zip file.  We run it through the name lookup.
288adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        entry = getEntry(entry.getName());
289f7c6911047d63bc76292f55ce538da32818dd931Jesse Wilson        if (entry == null) {
290adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project            return null;
291f7c6911047d63bc76292f55ce538da32818dd931Jesse Wilson        }
292adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
293739493443ce2ea5b0a92dd1725a4ed630db7b27bElliott Hughes        // Create an InputStream at the right part of the file.
294f05aeedc00c8e7ab7650067ce1dc301547a3914bElliott Hughes        RandomAccessFile localRaf = raf;
295f05aeedc00c8e7ab7650067ce1dc301547a3914bElliott Hughes        synchronized (localRaf) {
296f7c6911047d63bc76292f55ce538da32818dd931Jesse Wilson            // We don't know the entry data's start position. All we have is the
2973d2b2ad2cd1f05ba72a550082083da4b5898f30bKenny Root            // position of the entry's local header.
2986aed748da1deaa820d574446037401893d73be9bWilliam Luh            // http://www.pkware.com/documents/casestudies/APPNOTE.TXT
2993d2b2ad2cd1f05ba72a550082083da4b5898f30bKenny Root            RAFStream rafStream = new RAFStream(localRaf, entry.localHeaderRelOffset);
30013f30b9167639ad63da1707102db6320e8f76474Elliott Hughes            DataInputStream is = new DataInputStream(rafStream);
3013d2b2ad2cd1f05ba72a550082083da4b5898f30bKenny Root
3023d2b2ad2cd1f05ba72a550082083da4b5898f30bKenny Root            final int localMagic = Integer.reverseBytes(is.readInt());
3033d2b2ad2cd1f05ba72a550082083da4b5898f30bKenny Root            if (localMagic != LOCSIG) {
3043d2b2ad2cd1f05ba72a550082083da4b5898f30bKenny Root                throwZipException("Local File Header", localMagic);
3053d2b2ad2cd1f05ba72a550082083da4b5898f30bKenny Root            }
3063d2b2ad2cd1f05ba72a550082083da4b5898f30bKenny Root
3073d2b2ad2cd1f05ba72a550082083da4b5898f30bKenny Root            is.skipBytes(2);
3083d2b2ad2cd1f05ba72a550082083da4b5898f30bKenny Root
3093d2b2ad2cd1f05ba72a550082083da4b5898f30bKenny Root            // At position 6 we find the General Purpose Bit Flag.
3109edf43dfcc35c761d97eb9156ac4254152ddbc55Elliott Hughes            int gpbf = Short.reverseBytes(is.readShort()) & 0xffff;
3117a302a49a7c8b99e2f34fff660e199fb7c776bc1William Luh            if ((gpbf & ZipFile.GPBF_UNSUPPORTED_MASK) != 0) {
3126aed748da1deaa820d574446037401893d73be9bWilliam Luh                throw new ZipException("Invalid General Purpose Bit Flag: " + gpbf);
3136aed748da1deaa820d574446037401893d73be9bWilliam Luh            }
3146aed748da1deaa820d574446037401893d73be9bWilliam Luh
3152da1bf57a6631f1cbd47cdd7692ba8743c993ad9Elliott Hughes            // Offset 26 has the file name length, and offset 28 has the extra field length.
3162da1bf57a6631f1cbd47cdd7692ba8743c993ad9Elliott Hughes            // These lengths can differ from the ones in the central header.
3172da1bf57a6631f1cbd47cdd7692ba8743c993ad9Elliott Hughes            is.skipBytes(18);
3182da1bf57a6631f1cbd47cdd7692ba8743c993ad9Elliott Hughes            int fileNameLength = Short.reverseBytes(is.readShort()) & 0xffff;
3192da1bf57a6631f1cbd47cdd7692ba8743c993ad9Elliott Hughes            int extraFieldLength = Short.reverseBytes(is.readShort()) & 0xffff;
320739493443ce2ea5b0a92dd1725a4ed630db7b27bElliott Hughes            is.close();
321739493443ce2ea5b0a92dd1725a4ed630db7b27bElliott Hughes
3222da1bf57a6631f1cbd47cdd7692ba8743c993ad9Elliott Hughes            // Skip the variable-size file name and extra field data.
3232da1bf57a6631f1cbd47cdd7692ba8743c993ad9Elliott Hughes            rafStream.skip(fileNameLength + extraFieldLength);
3242da1bf57a6631f1cbd47cdd7692ba8743c993ad9Elliott Hughes
3253d2b2ad2cd1f05ba72a550082083da4b5898f30bKenny Root            if (entry.compressionMethod == ZipEntry.STORED) {
3263d2b2ad2cd1f05ba72a550082083da4b5898f30bKenny Root                rafStream.endOffset = rafStream.offset + entry.size;
3273d2b2ad2cd1f05ba72a550082083da4b5898f30bKenny Root                return rafStream;
3283d2b2ad2cd1f05ba72a550082083da4b5898f30bKenny Root            } else {
3293d2b2ad2cd1f05ba72a550082083da4b5898f30bKenny Root                rafStream.endOffset = rafStream.offset + entry.compressedSize;
3302da1bf57a6631f1cbd47cdd7692ba8743c993ad9Elliott Hughes                int bufSize = Math.max(1024, (int) Math.min(entry.getSize(), 65535L));
33113f30b9167639ad63da1707102db6320e8f76474Elliott Hughes                return new ZipInflaterInputStream(rafStream, new Inflater(true), bufSize, entry);
332adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project            }
333adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        }
334adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    }
335adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
336adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    /**
337adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * Gets the file name of this {@code ZipFile}.
33857995e8186b54515d5a03bf2ab104c3dc247f1b6Jesse Wilson     *
339adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * @return the file name of this {@code ZipFile}.
340adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     */
341adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    public String getName() {
342f05aeedc00c8e7ab7650067ce1dc301547a3914bElliott Hughes        return filename;
343adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    }
344adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
345adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    /**
346adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * Returns the number of {@code ZipEntries} in this {@code ZipFile}.
34757995e8186b54515d5a03bf2ab104c3dc247f1b6Jesse Wilson     *
348adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * @return the number of entries in this file.
349f05aeedc00c8e7ab7650067ce1dc301547a3914bElliott Hughes     * @throws IllegalStateException if this zip file has been closed.
350adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     */
351adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    public int size() {
352f7f90ec7f63bd80b7724d64ea664fdb589052fceJesse Wilson        checkNotClosed();
353f05aeedc00c8e7ab7650067ce1dc301547a3914bElliott Hughes        return entries.size();
354adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    }
355adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
356f7c6911047d63bc76292f55ce538da32818dd931Jesse Wilson    /**
357adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * Find the central directory and read the contents.
358adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     *
359f7c6911047d63bc76292f55ce538da32818dd931Jesse Wilson     * <p>The central directory can be followed by a variable-length comment
360adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * field, so we have to scan through it backwards.  The comment is at
361adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * most 64K, plus we have 18 bytes for the end-of-central-dir stuff
362adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * itself, plus apparently sometimes people throw random junk on the end
363adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * just for the fun of it.
364f7c6911047d63bc76292f55ce538da32818dd931Jesse Wilson     *
365f7c6911047d63bc76292f55ce538da32818dd931Jesse Wilson     * <p>This is all a little wobbly.  If the wrong value ends up in the EOCD
366f7c6911047d63bc76292f55ce538da32818dd931Jesse Wilson     * area, we're hosed. This appears to be the way that everybody handles
367f7c6911047d63bc76292f55ce538da32818dd931Jesse Wilson     * it though, so we're in good company if this fails.
368adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     */
369adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    private void readCentralDir() throws IOException {
370f05aeedc00c8e7ab7650067ce1dc301547a3914bElliott Hughes        // Scan back, looking for the End Of Central Directory field. If the zip file doesn't
371f05aeedc00c8e7ab7650067ce1dc301547a3914bElliott Hughes        // have an overall comment (unrelated to any per-entry comments), we'll hit the EOCD
372f05aeedc00c8e7ab7650067ce1dc301547a3914bElliott Hughes        // on the first try.
373f05aeedc00c8e7ab7650067ce1dc301547a3914bElliott Hughes        // No need to synchronize raf here -- we only do this when we first open the zip file.
374f05aeedc00c8e7ab7650067ce1dc301547a3914bElliott Hughes        long scanOffset = raf.length() - ENDHDR;
375f7c6911047d63bc76292f55ce538da32818dd931Jesse Wilson        if (scanOffset < 0) {
376f05aeedc00c8e7ab7650067ce1dc301547a3914bElliott Hughes            throw new ZipException("File too short to be a zip file: " + raf.length());
377f7c6911047d63bc76292f55ce538da32818dd931Jesse Wilson        }
378adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
3793d2b2ad2cd1f05ba72a550082083da4b5898f30bKenny Root        raf.seek(0);
3803d2b2ad2cd1f05ba72a550082083da4b5898f30bKenny Root        final int headerMagic = Integer.reverseBytes(raf.readInt());
381ef164bf196538c04f499dcbb49a389c70ff5601aPaul Duffin        if (headerMagic == ENDSIG) {
382ef164bf196538c04f499dcbb49a389c70ff5601aPaul Duffin            throw new ZipException("Empty zip archive not supported");
383ef164bf196538c04f499dcbb49a389c70ff5601aPaul Duffin        }
3843d2b2ad2cd1f05ba72a550082083da4b5898f30bKenny Root        if (headerMagic != LOCSIG) {
3853d2b2ad2cd1f05ba72a550082083da4b5898f30bKenny Root            throw new ZipException("Not a zip archive");
3863d2b2ad2cd1f05ba72a550082083da4b5898f30bKenny Root        }
3873d2b2ad2cd1f05ba72a550082083da4b5898f30bKenny Root
388f7c6911047d63bc76292f55ce538da32818dd931Jesse Wilson        long stopOffset = scanOffset - 65536;
389f7c6911047d63bc76292f55ce538da32818dd931Jesse Wilson        if (stopOffset < 0) {
390adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project            stopOffset = 0;
391f7c6911047d63bc76292f55ce538da32818dd931Jesse Wilson        }
392adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
393adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        while (true) {
394f05aeedc00c8e7ab7650067ce1dc301547a3914bElliott Hughes            raf.seek(scanOffset);
3953d2b2ad2cd1f05ba72a550082083da4b5898f30bKenny Root            if (Integer.reverseBytes(raf.readInt()) == ENDSIG) {
396adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project                break;
397f7c6911047d63bc76292f55ce538da32818dd931Jesse Wilson            }
398adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
399adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project            scanOffset--;
400f7c6911047d63bc76292f55ce538da32818dd931Jesse Wilson            if (scanOffset < stopOffset) {
4013d2b2ad2cd1f05ba72a550082083da4b5898f30bKenny Root                throw new ZipException("End Of Central Directory signature not found");
402f7c6911047d63bc76292f55ce538da32818dd931Jesse Wilson            }
403adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        }
404adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
405820c09bae53d20c5954d544ebc8a6ee8e54abbaeElliott Hughes        // Read the End Of Central Directory. ENDHDR includes the signature bytes,
406820c09bae53d20c5954d544ebc8a6ee8e54abbaeElliott Hughes        // which we've already read.
407820c09bae53d20c5954d544ebc8a6ee8e54abbaeElliott Hughes        byte[] eocd = new byte[ENDHDR - 4];
408f05aeedc00c8e7ab7650067ce1dc301547a3914bElliott Hughes        raf.readFully(eocd);
409739493443ce2ea5b0a92dd1725a4ed630db7b27bElliott Hughes
41043a9f774d075e0e441d8b996e3f6c81ea483ec89Elliott Hughes        // Pull out the information we need.
41143a9f774d075e0e441d8b996e3f6c81ea483ec89Elliott Hughes        BufferIterator it = HeapBufferIterator.iterator(eocd, 0, eocd.length, ByteOrder.LITTLE_ENDIAN);
4129f050bd1d16b822532430c897991e27a58605ff5Elliott Hughes        int diskNumber = it.readShort() & 0xffff;
4139f050bd1d16b822532430c897991e27a58605ff5Elliott Hughes        int diskWithCentralDir = it.readShort() & 0xffff;
4149f050bd1d16b822532430c897991e27a58605ff5Elliott Hughes        int numEntries = it.readShort() & 0xffff;
4159f050bd1d16b822532430c897991e27a58605ff5Elliott Hughes        int totalNumEntries = it.readShort() & 0xffff;
41643a9f774d075e0e441d8b996e3f6c81ea483ec89Elliott Hughes        it.skip(4); // Ignore centralDirSize.
41713f30b9167639ad63da1707102db6320e8f76474Elliott Hughes        long centralDirOffset = ((long) it.readInt()) & 0xffffffffL;
418820c09bae53d20c5954d544ebc8a6ee8e54abbaeElliott Hughes        int commentLength = it.readShort() & 0xffff;
419739493443ce2ea5b0a92dd1725a4ed630db7b27bElliott Hughes
420739493443ce2ea5b0a92dd1725a4ed630db7b27bElliott Hughes        if (numEntries != totalNumEntries || diskNumber != 0 || diskWithCentralDir != 0) {
4213d2b2ad2cd1f05ba72a550082083da4b5898f30bKenny Root            throw new ZipException("Spanned archives not supported");
422f7c6911047d63bc76292f55ce538da32818dd931Jesse Wilson        }
423adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
424820c09bae53d20c5954d544ebc8a6ee8e54abbaeElliott Hughes        if (commentLength > 0) {
425820c09bae53d20c5954d544ebc8a6ee8e54abbaeElliott Hughes            byte[] commentBytes = new byte[commentLength];
426820c09bae53d20c5954d544ebc8a6ee8e54abbaeElliott Hughes            raf.readFully(commentBytes);
42763744c884dd4b4f4307f2b021fb894af164972afElliott Hughes            comment = new String(commentBytes, 0, commentBytes.length, StandardCharsets.UTF_8);
428820c09bae53d20c5954d544ebc8a6ee8e54abbaeElliott Hughes        }
429820c09bae53d20c5954d544ebc8a6ee8e54abbaeElliott Hughes
43043a9f774d075e0e441d8b996e3f6c81ea483ec89Elliott Hughes        // Seek to the first CDE and read all entries.
43113f30b9167639ad63da1707102db6320e8f76474Elliott Hughes        // We have to do this now (from the constructor) rather than lazily because the
43213f30b9167639ad63da1707102db6320e8f76474Elliott Hughes        // public API doesn't allow us to throw IOException except from the constructor
43313f30b9167639ad63da1707102db6320e8f76474Elliott Hughes        // or from getInputStream.
434f05aeedc00c8e7ab7650067ce1dc301547a3914bElliott Hughes        RAFStream rafStream = new RAFStream(raf, centralDirOffset);
43513f30b9167639ad63da1707102db6320e8f76474Elliott Hughes        BufferedInputStream bufferedStream = new BufferedInputStream(rafStream, 4096);
436739493443ce2ea5b0a92dd1725a4ed630db7b27bElliott Hughes        byte[] hdrBuf = new byte[CENHDR]; // Reuse the same buffer for each entry.
437739493443ce2ea5b0a92dd1725a4ed630db7b27bElliott Hughes        for (int i = 0; i < numEntries; ++i) {
43863744c884dd4b4f4307f2b021fb894af164972afElliott Hughes            ZipEntry newEntry = new ZipEntry(hdrBuf, bufferedStream, StandardCharsets.UTF_8);
4393d2b2ad2cd1f05ba72a550082083da4b5898f30bKenny Root            if (newEntry.localHeaderRelOffset >= centralDirOffset) {
4403d2b2ad2cd1f05ba72a550082083da4b5898f30bKenny Root                throw new ZipException("Local file header offset is after central directory");
4413d2b2ad2cd1f05ba72a550082083da4b5898f30bKenny Root            }
44238cad1eb5cc0c30e034063c14c210912d97acb92Geremy Condra            String entryName = newEntry.getName();
44338cad1eb5cc0c30e034063c14c210912d97acb92Geremy Condra            if (entries.put(entryName, newEntry) != null) {
44438cad1eb5cc0c30e034063c14c210912d97acb92Geremy Condra                throw new ZipException("Duplicate entry name: " + entryName);
44538cad1eb5cc0c30e034063c14c210912d97acb92Geremy Condra            }
446adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        }
447adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    }
448adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
4493d2b2ad2cd1f05ba72a550082083da4b5898f30bKenny Root    static void throwZipException(String msg, int magic) throws ZipException {
4503d2b2ad2cd1f05ba72a550082083da4b5898f30bKenny Root        final String hexString = IntegralToString.intToHexString(magic, true, 8);
4513d2b2ad2cd1f05ba72a550082083da4b5898f30bKenny Root        throw new ZipException(msg + " signature not found; was " + hexString);
4523d2b2ad2cd1f05ba72a550082083da4b5898f30bKenny Root    }
4533d2b2ad2cd1f05ba72a550082083da4b5898f30bKenny Root
454f7c6911047d63bc76292f55ce538da32818dd931Jesse Wilson    /**
455f7c6911047d63bc76292f55ce538da32818dd931Jesse Wilson     * Wrap a stream around a RandomAccessFile.  The RandomAccessFile is shared
456f7c6911047d63bc76292f55ce538da32818dd931Jesse Wilson     * among all streams returned by getInputStream(), so we have to synchronize
457f7c6911047d63bc76292f55ce538da32818dd931Jesse Wilson     * access to it.  (We can optimize this by adding buffering here to reduce
458f7c6911047d63bc76292f55ce538da32818dd931Jesse Wilson     * collisions.)
459adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     *
460f7c6911047d63bc76292f55ce538da32818dd931Jesse Wilson     * <p>We could support mark/reset, but we don't currently need them.
4610c1869ed7f46baf764f9daf4e64c77cd7fbd3516Narayan Kamath     *
4620c1869ed7f46baf764f9daf4e64c77cd7fbd3516Narayan Kamath     * @hide
463adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     */
4640c1869ed7f46baf764f9daf4e64c77cd7fbd3516Narayan Kamath    public static class RAFStream extends InputStream {
465f05aeedc00c8e7ab7650067ce1dc301547a3914bElliott Hughes        private final RandomAccessFile sharedRaf;
4663d2b2ad2cd1f05ba72a550082083da4b5898f30bKenny Root        private long endOffset;
467f05aeedc00c8e7ab7650067ce1dc301547a3914bElliott Hughes        private long offset;
468f05aeedc00c8e7ab7650067ce1dc301547a3914bElliott Hughes
4690c1869ed7f46baf764f9daf4e64c77cd7fbd3516Narayan Kamath
4700c1869ed7f46baf764f9daf4e64c77cd7fbd3516Narayan Kamath        public RAFStream(RandomAccessFile raf, long initialOffset, long endOffset) {
471f05aeedc00c8e7ab7650067ce1dc301547a3914bElliott Hughes            sharedRaf = raf;
472f05aeedc00c8e7ab7650067ce1dc301547a3914bElliott Hughes            offset = initialOffset;
4730c1869ed7f46baf764f9daf4e64c77cd7fbd3516Narayan Kamath            this.endOffset = endOffset;
4740c1869ed7f46baf764f9daf4e64c77cd7fbd3516Narayan Kamath        }
4750c1869ed7f46baf764f9daf4e64c77cd7fbd3516Narayan Kamath
4760c1869ed7f46baf764f9daf4e64c77cd7fbd3516Narayan Kamath        public RAFStream(RandomAccessFile raf, long initialOffset) throws IOException {
4770c1869ed7f46baf764f9daf4e64c77cd7fbd3516Narayan Kamath            this(raf, initialOffset, raf.length());
478adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        }
479adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
480ff8234c90ecab9f1db368924bf92a5b16460f9b5Elliott Hughes        @Override public int available() throws IOException {
4813d2b2ad2cd1f05ba72a550082083da4b5898f30bKenny Root            return (offset < endOffset ? 1 : 0);
482adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        }
483adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
484ff8234c90ecab9f1db368924bf92a5b16460f9b5Elliott Hughes        @Override public int read() throws IOException {
485ff8234c90ecab9f1db368924bf92a5b16460f9b5Elliott Hughes            return Streams.readSingleByte(this);
486adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        }
487adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
488325ff8c68ed5e530e9e1d487b9e2e6d8f8e2bd37Elliott Hughes        @Override public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException {
489f05aeedc00c8e7ab7650067ce1dc301547a3914bElliott Hughes            synchronized (sharedRaf) {
4903d2b2ad2cd1f05ba72a550082083da4b5898f30bKenny Root                final long length = endOffset - offset;
49134f81a14c7c4002ab141ba90b2498974211b7df2Kenny Root                if (byteCount > length) {
49234f81a14c7c4002ab141ba90b2498974211b7df2Kenny Root                    byteCount = (int) length;
493c2bcd6f029fa02657889af1120f2eaaf73da968eKenny Root                }
4943d2b2ad2cd1f05ba72a550082083da4b5898f30bKenny Root                sharedRaf.seek(offset);
495325ff8c68ed5e530e9e1d487b9e2e6d8f8e2bd37Elliott Hughes                int count = sharedRaf.read(buffer, byteOffset, byteCount);
496adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project                if (count > 0) {
497f05aeedc00c8e7ab7650067ce1dc301547a3914bElliott Hughes                    offset += count;
498f7c6911047d63bc76292f55ce538da32818dd931Jesse Wilson                    return count;
499f7c6911047d63bc76292f55ce538da32818dd931Jesse Wilson                } else {
500f7c6911047d63bc76292f55ce538da32818dd931Jesse Wilson                    return -1;
501adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project                }
502adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project            }
503adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        }
504adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
50513f30b9167639ad63da1707102db6320e8f76474Elliott Hughes        @Override public long skip(long byteCount) throws IOException {
5063d2b2ad2cd1f05ba72a550082083da4b5898f30bKenny Root            if (byteCount > endOffset - offset) {
5073d2b2ad2cd1f05ba72a550082083da4b5898f30bKenny Root                byteCount = endOffset - offset;
508f7c6911047d63bc76292f55ce538da32818dd931Jesse Wilson            }
509f05aeedc00c8e7ab7650067ce1dc301547a3914bElliott Hughes            offset += byteCount;
510f9480f317cddcec859025833b748f096247a40aaElliott Hughes            return byteCount;
511adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        }
51213f30b9167639ad63da1707102db6320e8f76474Elliott Hughes
51313f30b9167639ad63da1707102db6320e8f76474Elliott Hughes        public int fill(Inflater inflater, int nativeEndBufSize) throws IOException {
514f05aeedc00c8e7ab7650067ce1dc301547a3914bElliott Hughes            synchronized (sharedRaf) {
5153d2b2ad2cd1f05ba72a550082083da4b5898f30bKenny Root                int len = Math.min((int) (endOffset - offset), nativeEndBufSize);
516f05aeedc00c8e7ab7650067ce1dc301547a3914bElliott Hughes                int cnt = inflater.setFileInput(sharedRaf.getFD(), offset, nativeEndBufSize);
517f05aeedc00c8e7ab7650067ce1dc301547a3914bElliott Hughes                // setFileInput read from the file, so we need to get the OS and RAFStream back
518f05aeedc00c8e7ab7650067ce1dc301547a3914bElliott Hughes                // in sync...
51913f30b9167639ad63da1707102db6320e8f76474Elliott Hughes                skip(cnt);
52013f30b9167639ad63da1707102db6320e8f76474Elliott Hughes                return len;
52113f30b9167639ad63da1707102db6320e8f76474Elliott Hughes            }
52213f30b9167639ad63da1707102db6320e8f76474Elliott Hughes        }
523f7c6911047d63bc76292f55ce538da32818dd931Jesse Wilson    }
524f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes
5250c1869ed7f46baf764f9daf4e64c77cd7fbd3516Narayan Kamath    /** @hide */
5260c1869ed7f46baf764f9daf4e64c77cd7fbd3516Narayan Kamath    public static class ZipInflaterInputStream extends InflaterInputStream {
52713f30b9167639ad63da1707102db6320e8f76474Elliott Hughes        private final ZipEntry entry;
52813f30b9167639ad63da1707102db6320e8f76474Elliott Hughes        private long bytesRead = 0;
529f7c6911047d63bc76292f55ce538da32818dd931Jesse Wilson
530f7c6911047d63bc76292f55ce538da32818dd931Jesse Wilson        public ZipInflaterInputStream(InputStream is, Inflater inf, int bsize, ZipEntry entry) {
531f7c6911047d63bc76292f55ce538da32818dd931Jesse Wilson            super(is, inf, bsize);
532f7c6911047d63bc76292f55ce538da32818dd931Jesse Wilson            this.entry = entry;
533f7c6911047d63bc76292f55ce538da32818dd931Jesse Wilson        }
534f7c6911047d63bc76292f55ce538da32818dd931Jesse Wilson
535325ff8c68ed5e530e9e1d487b9e2e6d8f8e2bd37Elliott Hughes        @Override public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException {
5363d2b2ad2cd1f05ba72a550082083da4b5898f30bKenny Root            final int i;
5373d2b2ad2cd1f05ba72a550082083da4b5898f30bKenny Root            try {
5383d2b2ad2cd1f05ba72a550082083da4b5898f30bKenny Root                i = super.read(buffer, byteOffset, byteCount);
5393d2b2ad2cd1f05ba72a550082083da4b5898f30bKenny Root            } catch (IOException e) {
5403d2b2ad2cd1f05ba72a550082083da4b5898f30bKenny Root                throw new IOException("Error reading data for " + entry.getName() + " near offset "
5413d2b2ad2cd1f05ba72a550082083da4b5898f30bKenny Root                        + bytesRead, e);
5423d2b2ad2cd1f05ba72a550082083da4b5898f30bKenny Root            }
5433d2b2ad2cd1f05ba72a550082083da4b5898f30bKenny Root            if (i == -1) {
5443d2b2ad2cd1f05ba72a550082083da4b5898f30bKenny Root                if (entry.size != bytesRead) {
5453d2b2ad2cd1f05ba72a550082083da4b5898f30bKenny Root                    throw new IOException("Size mismatch on inflated file: " + bytesRead + " vs "
5463d2b2ad2cd1f05ba72a550082083da4b5898f30bKenny Root                            + entry.size);
5473d2b2ad2cd1f05ba72a550082083da4b5898f30bKenny Root                }
5483d2b2ad2cd1f05ba72a550082083da4b5898f30bKenny Root            } else {
549f7c6911047d63bc76292f55ce538da32818dd931Jesse Wilson                bytesRead += i;
550f7c6911047d63bc76292f55ce538da32818dd931Jesse Wilson            }
551f7c6911047d63bc76292f55ce538da32818dd931Jesse Wilson            return i;
552f7c6911047d63bc76292f55ce538da32818dd931Jesse Wilson        }
553f7c6911047d63bc76292f55ce538da32818dd931Jesse Wilson
55413f30b9167639ad63da1707102db6320e8f76474Elliott Hughes        @Override public int available() throws IOException {
55560eb73fc9f0636c3d0bda5baba641a5f88f8476fElliott Hughes            if (closed) {
55660eb73fc9f0636c3d0bda5baba641a5f88f8476fElliott Hughes                // Our superclass will throw an exception, but there's a jtreg test that
55760eb73fc9f0636c3d0bda5baba641a5f88f8476fElliott Hughes                // explicitly checks that the InputStream returned from ZipFile.getInputStream
55860eb73fc9f0636c3d0bda5baba641a5f88f8476fElliott Hughes                // returns 0 even when closed.
55960eb73fc9f0636c3d0bda5baba641a5f88f8476fElliott Hughes                return 0;
56060eb73fc9f0636c3d0bda5baba641a5f88f8476fElliott Hughes            }
561f7c6911047d63bc76292f55ce538da32818dd931Jesse Wilson            return super.available() == 0 ? 0 : (int) (entry.getSize() - bytesRead);
562f7c6911047d63bc76292f55ce538da32818dd931Jesse Wilson        }
563adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    }
564adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project}
565