2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
18 package java.util.zip;
20 import java.io.EOFException;
21 import java.io.IOException;
22 import java.io.InputStream;
23 import java.io.PushbackInputStream;
24 import java.util.jar.Attributes;
25 import java.util.jar.JarEntry;
27 import org.apache.harmony.archive.internal.nls.Messages;
28 import org.apache.harmony.luni.util.Util;
31 * This class provides an implementation of {@code FilterInputStream} that
32 * uncompresses data from a <i>ZIP-archive</i> input stream.
34 * A <i>ZIP-archive</i> is a collection of compressed (or uncompressed) files -
35 * the so called ZIP entries. Therefore when reading from a {@code
36 * ZipInputStream} first the entry's attributes will be retrieved with {@code
37 * getNextEntry} before its data is read.
39 * While {@code InflaterInputStream} can read a compressed <i>ZIP-archive</i>
40 * entry, this extension can read uncompressed entries as well.
42 * Use {@code ZipFile} if you can access the archive as a file directly.
47 public class ZipInputStream extends InflaterInputStream implements ZipConstants {
48 static final int DEFLATED = 8;
50 static final int STORED = 0;
52 static final int ZIPDataDescriptorFlag = 8;
54 static final int ZIPLocalHeaderVersionNeeded = 20;
56 // BEGIN android-removed
57 // private boolean zipClosed = false;
58 // END android-removed
60 private boolean entriesEnd = false;
62 private boolean hasDD = false;
64 private int entryIn = 0;
66 private int inRead, lastRead = 0;
68 ZipEntry currentEntry;
70 private final byte[] hdrBuf = new byte[LOCHDR - LOCVER];
72 private final CRC32 crc = new CRC32();
74 private byte[] nameBuf = new byte[256];
76 private char[] charBuf = new char[256];
79 * Constructs a new {@code ZipInputStream} from the specified input stream.
82 * the input stream to representing a ZIP archive.
84 public ZipInputStream(InputStream stream) {
85 super(new PushbackInputStream(stream, BUF_SIZE), new Inflater(true));
87 throw new NullPointerException();
92 * Closes this {@code ZipInputStream}.
95 * if an {@code IOException} occurs.
98 public void close() throws IOException {
99 // BEGIN android-changed
100 if (closed != true) {
101 closeEntry(); // Close the current entry
104 // END android-changed
108 * Closes the current ZIP entry and positions to read the next entry.
110 * @throws IOException
111 * if an {@code IOException} occurs.
113 public void closeEntry() throws IOException {
114 // BEGIN android-changed
116 throw new IOException(Messages.getString("archive.1E")); //$NON-NLS-1$
118 // END android-changed
119 if (currentEntry == null) {
122 if (currentEntry instanceof java.util.jar.JarEntry) {
123 Attributes temp = ((JarEntry) currentEntry).getAttributes();
124 if (temp != null && temp.containsKey("hidden")) { //$NON-NLS-1$
128 // Ensure all entry bytes are read
129 skip(Long.MAX_VALUE);
131 if (currentEntry.compressionMethod == DEFLATED) {
132 inB = inf.getTotalIn();
133 out = inf.getTotalOut();
139 // Pushback any required bytes
140 if ((diff = entryIn - inB) != 0) {
141 ((PushbackInputStream) in).unread(buf, len - diff, diff);
145 in.read(hdrBuf, 0, EXTHDR);
146 if (getLong(hdrBuf, 0) != EXTSIG) {
147 throw new ZipException(Messages.getString("archive.1F")); //$NON-NLS-1$
149 currentEntry.crc = getLong(hdrBuf, EXTCRC);
150 currentEntry.compressedSize = getLong(hdrBuf, EXTSIZ);
151 currentEntry.size = getLong(hdrBuf, EXTLEN);
153 if (currentEntry.crc != crc.getValue()) {
154 throw new ZipException(Messages.getString("archive.20")); //$NON-NLS-1$
156 if (currentEntry.compressedSize != inB || currentEntry.size != out) {
157 throw new ZipException(Messages.getString("archive.21")); //$NON-NLS-1$
161 lastRead = inRead = entryIn = len = 0;
167 * Reads the next entry from this {@code ZipInputStream}.
169 * @return the next {@code ZipEntry} contained in the input stream.
170 * @throws IOException
171 * if the stream is not positioned at the beginning of an entry
172 * or if an other {@code IOException} occurs.
175 public ZipEntry getNextEntry() throws IOException {
176 if (currentEntry != null) {
183 int x = 0, count = 0;
185 count += x = in.read(hdrBuf, count, 4 - count);
190 long hdr = getLong(hdrBuf, 0);
199 // Read the local header
201 while (count != (LOCHDR - LOCVER)) {
202 count += x = in.read(hdrBuf, count, (LOCHDR - LOCVER) - count);
204 throw new EOFException();
207 int version = getShort(hdrBuf, 0) & 0xff;
208 if (version > ZIPLocalHeaderVersionNeeded) {
209 throw new ZipException(Messages.getString("archive.22")); //$NON-NLS-1$
211 int flags = getShort(hdrBuf, LOCFLG - LOCVER);
212 hasDD = ((flags & ZIPDataDescriptorFlag) == ZIPDataDescriptorFlag);
213 int cetime = getShort(hdrBuf, LOCTIM - LOCVER);
214 int cemodDate = getShort(hdrBuf, LOCTIM - LOCVER + 2);
215 int cecompressionMethod = getShort(hdrBuf, LOCHOW - LOCVER);
216 long cecrc = 0, cecompressedSize = 0, cesize = -1;
218 cecrc = getLong(hdrBuf, LOCCRC - LOCVER);
219 cecompressedSize = getLong(hdrBuf, LOCSIZ - LOCVER);
220 cesize = getLong(hdrBuf, LOCLEN - LOCVER);
222 int flen = getShort(hdrBuf, LOCNAM - LOCVER);
224 throw new ZipException(Messages.getString("archive.23")); //$NON-NLS-1$
226 int elen = getShort(hdrBuf, LOCEXT - LOCVER);
229 if (flen > nameBuf.length) {
230 nameBuf = new byte[flen];
231 charBuf = new char[flen];
233 while (count != flen) {
234 count += x = in.read(nameBuf, count, flen - count);
236 throw new EOFException();
239 currentEntry = createZipEntry(Util.convertUTF8WithBuf(nameBuf, charBuf,
241 currentEntry.time = cetime;
242 currentEntry.modDate = cemodDate;
243 currentEntry.setMethod(cecompressionMethod);
245 currentEntry.setCrc(cecrc);
246 currentEntry.setSize(cesize);
247 currentEntry.setCompressedSize(cecompressedSize);
251 byte[] e = new byte[elen];
252 while (count != elen) {
253 count += x = in.read(e, count, elen - count);
255 throw new EOFException();
258 currentEntry.setExtra(e);
260 // BEGIN android-added
266 /* Read 4 bytes from the buffer and store it as an int */
269 * Reads up to the specified number of uncompressed bytes into the buffer
270 * starting at the offset.
275 * the starting offset into the buffer
277 * the number of bytes to read
278 * @return the number of bytes read
281 public int read(byte[] buffer, int start, int length) throws IOException {
282 // BEGIN android-changed
284 throw new IOException(Messages.getString("archive.1E")); //$NON-NLS-1$
286 // END android-changed
287 if (inf.finished() || currentEntry == null) {
290 // avoid int overflow, check null buffer
291 if (start <= buffer.length && length >= 0 && start >= 0
292 && buffer.length - start >= length) {
293 if (currentEntry.compressionMethod == STORED) {
294 int csize = (int) currentEntry.size;
295 if (inRead >= csize) {
296 // BEGIN android-added
301 if (lastRead >= len) {
303 if ((len = in.read(buf)) == -1) {
304 // BEGIN android-added
311 // BEGIN android-changed
312 int toRead = length > (len - lastRead) ? len - lastRead : length;
313 // END android-changed
314 if ((csize - inRead) < toRead) {
315 toRead = csize - inRead;
317 System.arraycopy(buf, lastRead, buffer, start, toRead);
320 crc.update(buffer, start, toRead);
323 if (inf.needsInput()) {
331 read = inf.inflate(buffer, start, length);
332 } catch (DataFormatException e) {
333 throw new ZipException(e.getMessage());
335 if (read == 0 && inf.finished()) {
338 crc.update(buffer, start, read);
341 throw new ArrayIndexOutOfBoundsException();
345 * Skips up to the specified number of bytes in the current ZIP entry.
348 * the number of bytes to skip.
349 * @return the number of bytes skipped.
350 * @throws IOException
351 * if an {@code IOException} occurs.
354 public long skip(long value) throws IOException {
357 byte[] b = new byte[1024];
358 while (skipped != value) {
359 long rem = value - skipped;
360 int x = read(b, 0, (int) (b.length > rem ? rem : b.length));
368 throw new IllegalArgumentException();
372 * Returns 0 if the {@code EOF} has been reached, otherwise returns 1.
374 * @return 0 after {@code EOF} of current entry, 1 otherwise.
375 * @throws IOException
376 * if an IOException occurs.
379 public int available() throws IOException {
380 // BEGIN android-changed
382 throw new IOException(Messages.getString("archive.1E")); //$NON-NLS-1$
384 if (currentEntry == null) {
387 return super.available();
388 // END android-changed
392 * creates a {@link ZipEntry } with the given name.
395 * the name of the entry.
396 * @return the created {@code ZipEntry}.
398 protected ZipEntry createZipEntry(String name) {
399 return new ZipEntry(name);
402 private int getShort(byte[] buffer, int off) {
403 return (buffer[off] & 0xFF) | ((buffer[off + 1] & 0xFF) << 8);
406 private long getLong(byte[] buffer, int off) {
408 l |= (buffer[off] & 0xFF);
409 l |= (buffer[off + 1] & 0xFF) << 8;
410 l |= (buffer[off + 2] & 0xFF) << 16;
411 l |= ((long) (buffer[off + 3] & 0xFF)) << 24;