ZipInputStream.java revision 49965c1dc9da104344f4893a05e45795a5740d20
1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 * Copyright (c) 1996, 2009, Oracle and/or its affiliates. All rights reserved.
4 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
5 *
6 * This code is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License version 2 only, as
8 * published by the Free Software Foundation.  Oracle designates this
9 * particular file as subject to the "Classpath" exception as provided
10 * by Oracle in the LICENSE file that accompanied this code.
11 *
12 * This code is distributed in the hope that it will be useful, but WITHOUT
13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
15 * version 2 for more details (a copy is included in the LICENSE file that
16 * accompanied this code).
17 *
18 * You should have received a copy of the GNU General Public License version
19 * 2 along with this work; if not, write to the Free Software Foundation,
20 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
21 *
22 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
23 * or visit www.oracle.com if you need additional information or have any
24 * questions.
25 */
26
27package java.util.zip;
28
29import java.io.InputStream;
30import java.io.IOException;
31import java.io.EOFException;
32import java.io.PushbackInputStream;
33import java.nio.charset.Charset;
34import java.nio.charset.StandardCharsets;
35import static java.util.zip.ZipConstants64.*;
36
37/**
38 * This class implements an input stream filter for reading files in the
39 * ZIP file format. Includes support for both compressed and uncompressed
40 * entries.
41 *
42 * @author      David Connelly
43 */
44public
45class ZipInputStream extends InflaterInputStream implements ZipConstants {
46    private ZipEntry entry;
47    private int flag;
48    private CRC32 crc = new CRC32();
49    private long remaining;
50    private byte[] tmpbuf = new byte[512];
51
52    private static final int STORED = ZipEntry.STORED;
53    private static final int DEFLATED = ZipEntry.DEFLATED;
54
55    private boolean closed = false;
56    // this flag is set to true after EOF has reached for
57    // one entry
58    private boolean entryEOF = false;
59
60    private ZipCoder zc;
61
62    /**
63     * Check to make sure that this stream has not been closed
64     */
65    private void ensureOpen() throws IOException {
66        if (closed) {
67            throw new IOException("Stream closed");
68        }
69    }
70
71    /**
72     * Creates a new ZIP input stream.
73     *
74     * <p>The UTF-8 {@link java.nio.charset.Charset charset} is used to
75     * decode the entry names.
76     *
77     * @param in the actual input stream
78     */
79    public ZipInputStream(InputStream in) {
80        this(in, StandardCharsets.UTF_8);
81    }
82
83    /**
84     * Creates a new ZIP input stream.
85     *
86     * @param in the actual input stream
87     *
88     * @param charset
89     *        The {@linkplain java.nio.charset.Charset charset} to be
90     *        used to decode the ZIP entry name (ignored if the
91     *        <a href="package-summary.html#lang_encoding"> language
92     *        encoding bit</a> of the ZIP entry's general purpose bit
93     *        flag is set).
94     *
95     * @since 1.7
96     */
97    public ZipInputStream(InputStream in, Charset charset) {
98        super(new PushbackInputStream(in, 512), new Inflater(true), 512);
99        if(in == null) {
100            throw new NullPointerException("in is null");
101        }
102        if (charset == null)
103            throw new NullPointerException("charset is null");
104        this.zc = ZipCoder.get(charset);
105    }
106
107    /**
108     * Reads the next ZIP file entry and positions the stream at the
109     * beginning of the entry data.
110     * @return the next ZIP file entry, or null if there are no more entries
111     * @exception ZipException if a ZIP file error has occurred
112     * @exception IOException if an I/O error has occurred
113     */
114    public ZipEntry getNextEntry() throws IOException {
115        ensureOpen();
116        if (entry != null) {
117            closeEntry();
118        }
119        crc.reset();
120        inf.reset();
121        if ((entry = readLOC()) == null) {
122            return null;
123        }
124        // ----- BEGIN android -----
125        // if (entry.method == STORED) {
126        if (entry.method == STORED || entry.method == DEFLATED) {
127        // ----- END android -----
128            remaining = entry.size;
129        }
130        entryEOF = false;
131        return entry;
132    }
133
134    /**
135     * Closes the current ZIP entry and positions the stream for reading the
136     * next entry.
137     * @exception ZipException if a ZIP file error has occurred
138     * @exception IOException if an I/O error has occurred
139     */
140    public void closeEntry() throws IOException {
141        ensureOpen();
142        while (read(tmpbuf, 0, tmpbuf.length) != -1) ;
143        entryEOF = true;
144    }
145
146    /**
147     * Returns 0 after EOF has reached for the current entry data,
148     * otherwise always return 1.
149     * <p>
150     * Programs should not count on this method to return the actual number
151     * of bytes that could be read without blocking.
152     *
153     * @return     1 before EOF and 0 after EOF has reached for current entry.
154     * @exception  IOException  if an I/O error occurs.
155     *
156     */
157    public int available() throws IOException {
158        ensureOpen();
159        // ----- BEGIN android -----
160        // if (entryEOF) {
161        if (entryEOF || (entry != null && remaining == 0)) {
162        // ----- END android -----
163            return 0;
164        } else {
165            return 1;
166        }
167    }
168
169    /**
170     * Reads from the current ZIP entry into an array of bytes.
171     * If <code>len</code> is not zero, the method
172     * blocks until some input is available; otherwise, no
173     * bytes are read and <code>0</code> is returned.
174     * @param b the buffer into which the data is read
175     * @param off the start offset in the destination array <code>b</code>
176     * @param len the maximum number of bytes read
177     * @return the actual number of bytes read, or -1 if the end of the
178     *         entry is reached
179     * @exception  NullPointerException if <code>b</code> is <code>null</code>.
180     * @exception  IndexOutOfBoundsException if <code>off</code> is negative,
181     * <code>len</code> is negative, or <code>len</code> is greater than
182     * <code>b.length - off</code>
183     * @exception ZipException if a ZIP file error has occurred
184     * @exception IOException if an I/O error has occurred
185     */
186    public int read(byte[] b, int off, int len) throws IOException {
187        ensureOpen();
188        if (off < 0 || len < 0 || off > b.length - len) {
189            throw new IndexOutOfBoundsException();
190        } else if (len == 0) {
191            return 0;
192        }
193
194        if (entry == null) {
195            return -1;
196        }
197        switch (entry.method) {
198        case DEFLATED:
199            len = super.read(b, off, len);
200            if (len == -1) {
201                readEnd(entry);
202                entryEOF = true;
203                entry = null;
204            } else {
205                crc.update(b, off, len);
206                // ----- BEGIN android -----
207                remaining -= len;
208                // ----- END android -----
209            }
210            return len;
211        case STORED:
212            if (remaining <= 0) {
213                entryEOF = true;
214                entry = null;
215                return -1;
216            }
217            if (len > remaining) {
218                len = (int)remaining;
219            }
220            len = in.read(b, off, len);
221            if (len == -1) {
222                throw new ZipException("unexpected EOF");
223            }
224            crc.update(b, off, len);
225            remaining -= len;
226            if (remaining == 0 && entry.crc != crc.getValue()) {
227                throw new ZipException(
228                    "invalid entry CRC (expected 0x" + Long.toHexString(entry.crc) +
229                    " but got 0x" + Long.toHexString(crc.getValue()) + ")");
230            }
231            return len;
232        default:
233            throw new ZipException("invalid compression method");
234        }
235    }
236
237    /**
238     * Skips specified number of bytes in the current ZIP entry.
239     * @param n the number of bytes to skip
240     * @return the actual number of bytes skipped
241     * @exception ZipException if a ZIP file error has occurred
242     * @exception IOException if an I/O error has occurred
243     * @exception IllegalArgumentException if n < 0
244     */
245    public long skip(long n) throws IOException {
246        if (n < 0) {
247            throw new IllegalArgumentException("negative skip length");
248        }
249        ensureOpen();
250        int max = (int)Math.min(n, Integer.MAX_VALUE);
251        int total = 0;
252        while (total < max) {
253            int len = max - total;
254            if (len > tmpbuf.length) {
255                len = tmpbuf.length;
256            }
257            len = read(tmpbuf, 0, len);
258            if (len == -1) {
259                entryEOF = true;
260                break;
261            }
262            total += len;
263        }
264        return total;
265    }
266
267    /**
268     * Closes this input stream and releases any system resources associated
269     * with the stream.
270     * @exception IOException if an I/O error has occurred
271     */
272    public void close() throws IOException {
273        if (!closed) {
274            super.close();
275            closed = true;
276        }
277    }
278
279    private byte[] b = new byte[256];
280
281    /*
282     * Reads local file (LOC) header for next entry.
283     */
284    private ZipEntry readLOC() throws IOException {
285        try {
286            readFully(tmpbuf, 0, LOCHDR);
287        } catch (EOFException e) {
288            return null;
289        }
290        if (get32(tmpbuf, 0) != LOCSIG) {
291            return null;
292        }
293        // get flag first, we need check EFS.
294        flag = get16(tmpbuf, LOCFLG);
295        // get the entry name and create the ZipEntry first
296        int len = get16(tmpbuf, LOCNAM);
297        int blen = b.length;
298        if (len > blen) {
299            do
300                blen = blen * 2;
301            while (len > blen);
302            b = new byte[blen];
303        }
304        readFully(b, 0, len);
305        // Force to use UTF-8 if the EFS bit is ON, even the cs is NOT UTF-8
306        ZipEntry e = createZipEntry(((flag & EFS) != 0)
307                                    ? zc.toStringUTF8(b, len)
308                                    : zc.toString(b, len));
309        // now get the remaining fields for the entry
310        if ((flag & 1) == 1) {
311            throw new ZipException("encrypted ZIP entry not supported");
312        }
313        e.method = get16(tmpbuf, LOCHOW);
314        e.time = get32(tmpbuf, LOCTIM);
315        if ((flag & 8) == 8) {
316            /* "Data Descriptor" present */
317            if (e.method != DEFLATED) {
318                throw new ZipException(
319                        "only DEFLATED entries can have EXT descriptor");
320            }
321        } else {
322            e.crc = get32(tmpbuf, LOCCRC);
323            e.csize = get32(tmpbuf, LOCSIZ);
324            e.size = get32(tmpbuf, LOCLEN);
325        }
326        len = get16(tmpbuf, LOCEXT);
327        if (len > 0) {
328            byte[] bb = new byte[len];
329            readFully(bb, 0, len);
330            e.setExtra(bb);
331            // extra fields are in "HeaderID(2)DataSize(2)Data... format
332            if (e.csize == ZIP64_MAGICVAL || e.size == ZIP64_MAGICVAL) {
333                int off = 0;
334                while (off + 4 < len) {
335                    int sz = get16(bb, off + 2);
336                    if (get16(bb, off) == ZIP64_EXTID) {
337                        off += 4;
338                        // LOC extra zip64 entry MUST include BOTH original and
339                        // compressed file size fields
340                        if (sz < 16 || (off + sz) > len ) {
341                            // Invalid zip64 extra fields, simply skip. Even it's
342                            // rare, it's possible the entry size happens to be
343                            // the magic value and it "accidnetly" has some bytes
344                            // in extra match the id.
345                            return e;
346                        }
347                        e.size = get64(bb, off);
348                        e.csize = get64(bb, off + 8);
349                        break;
350                    }
351                    off += (sz + 4);
352                }
353            }
354        }
355        return e;
356    }
357
358    /**
359     * Creates a new <code>ZipEntry</code> object for the specified
360     * entry name.
361     *
362     * @param name the ZIP file entry name
363     * @return the ZipEntry just created
364     */
365    protected ZipEntry createZipEntry(String name) {
366        return new ZipEntry(name);
367    }
368
369    /*
370     * Reads end of deflated entry as well as EXT descriptor if present.
371     */
372    private void readEnd(ZipEntry e) throws IOException {
373        int n = inf.getRemaining();
374        if (n > 0) {
375            ((PushbackInputStream)in).unread(buf, len - n, n);
376        }
377        if ((flag & 8) == 8) {
378            /* "Data Descriptor" present */
379            if (inf.getBytesWritten() > ZIP64_MAGICVAL ||
380                inf.getBytesRead() > ZIP64_MAGICVAL) {
381                // ZIP64 format
382                readFully(tmpbuf, 0, ZIP64_EXTHDR);
383                long sig = get32(tmpbuf, 0);
384                if (sig != EXTSIG) { // no EXTSIG present
385                    e.crc = sig;
386                    e.csize = get64(tmpbuf, ZIP64_EXTSIZ - ZIP64_EXTCRC);
387                    e.size = get64(tmpbuf, ZIP64_EXTLEN - ZIP64_EXTCRC);
388                    ((PushbackInputStream)in).unread(
389                        tmpbuf, ZIP64_EXTHDR - ZIP64_EXTCRC - 1, ZIP64_EXTCRC);
390                } else {
391                    e.crc = get32(tmpbuf, ZIP64_EXTCRC);
392                    e.csize = get64(tmpbuf, ZIP64_EXTSIZ);
393                    e.size = get64(tmpbuf, ZIP64_EXTLEN);
394                }
395            } else {
396                readFully(tmpbuf, 0, EXTHDR);
397                long sig = get32(tmpbuf, 0);
398                if (sig != EXTSIG) { // no EXTSIG present
399                    e.crc = sig;
400                    e.csize = get32(tmpbuf, EXTSIZ - EXTCRC);
401                    e.size = get32(tmpbuf, EXTLEN - EXTCRC);
402                    ((PushbackInputStream)in).unread(
403                                               tmpbuf, EXTHDR - EXTCRC - 1, EXTCRC);
404                } else {
405                    e.crc = get32(tmpbuf, EXTCRC);
406                    e.csize = get32(tmpbuf, EXTSIZ);
407                    e.size = get32(tmpbuf, EXTLEN);
408                }
409            }
410        }
411        if (e.size != inf.getBytesWritten()) {
412            throw new ZipException(
413                "invalid entry size (expected " + e.size +
414                " but got " + inf.getBytesWritten() + " bytes)");
415        }
416        if (e.csize != inf.getBytesRead()) {
417            throw new ZipException(
418                "invalid entry compressed size (expected " + e.csize +
419                " but got " + inf.getBytesRead() + " bytes)");
420        }
421        if (e.crc != crc.getValue()) {
422            throw new ZipException(
423                "invalid entry CRC (expected 0x" + Long.toHexString(e.crc) +
424                " but got 0x" + Long.toHexString(crc.getValue()) + ")");
425        }
426    }
427
428    /*
429     * Reads bytes, blocking until all bytes are read.
430     */
431    private void readFully(byte[] b, int off, int len) throws IOException {
432        while (len > 0) {
433            int n = in.read(b, off, len);
434            if (n == -1) {
435                throw new EOFException();
436            }
437            off += n;
438            len -= n;
439        }
440    }
441
442    /*
443     * Fetches unsigned 16-bit value from byte array at specified offset.
444     * The bytes are assumed to be in Intel (little-endian) byte order.
445     */
446    private static final int get16(byte b[], int off) {
447        return (b[off] & 0xff) | ((b[off+1] & 0xff) << 8);
448    }
449
450    /*
451     * Fetches unsigned 32-bit value from byte array at specified offset.
452     * The bytes are assumed to be in Intel (little-endian) byte order.
453     */
454    private static final long get32(byte b[], int off) {
455        return (get16(b, off) | ((long)get16(b, off+2) << 16)) & 0xffffffffL;
456    }
457
458    /*
459     * Fetches signed 64-bit value from byte array at specified offset.
460     * The bytes are assumed to be in Intel (little-endian) byte order.
461     */
462    private static final long get64(byte b[], int off) {
463        return get32(b, off) | (get32(b, off+4) << 32);
464    }
465}
466