• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  * Copyright (c) 1996, 2015, 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 
27 package java.util.zip;
28 
29 import java.io.InputStream;
30 import java.io.IOException;
31 import java.io.EOFException;
32 import java.io.PushbackInputStream;
33 import java.nio.charset.Charset;
34 import java.nio.charset.StandardCharsets;
35 import static java.util.zip.ZipConstants64.*;
36 import static java.util.zip.ZipUtils.*;
37 
38 import dalvik.system.ZipPathValidator;
39 
40 /**
41  * This class implements an input stream filter for reading files in the
42  * ZIP file format. Includes support for both compressed and uncompressed
43  * entries.
44  *
45  * @author      David Connelly
46  * @since 1.1
47  */
48 public
49 class ZipInputStream extends InflaterInputStream implements ZipConstants {
50     private ZipEntry entry;
51     private int flag;
52     private CRC32 crc = new CRC32();
53     private long remaining;
54     private byte[] tmpbuf = new byte[512];
55 
56     private static final int STORED = ZipEntry.STORED;
57     private static final int DEFLATED = ZipEntry.DEFLATED;
58 
59     private boolean closed = false;
60     // this flag is set to true after EOF has reached for
61     // one entry
62     private boolean entryEOF = false;
63 
64     private ZipCoder zc;
65 
66     /**
67      * Check to make sure that this stream has not been closed
68      */
ensureOpen()69     private void ensureOpen() throws IOException {
70         if (closed) {
71             throw new IOException("Stream closed");
72         }
73     }
74 
75     /**
76      * Creates a new ZIP input stream.
77      *
78      * <p>The UTF-8 {@link java.nio.charset.Charset charset} is used to
79      * decode the entry names.
80      *
81      * @param in the actual input stream
82      */
ZipInputStream(InputStream in)83     public ZipInputStream(InputStream in) {
84         this(in, StandardCharsets.UTF_8);
85     }
86 
87     /**
88      * Creates a new ZIP input stream.
89      *
90      * @param in the actual input stream
91      *
92      * @param charset
93      *        The {@linkplain java.nio.charset.Charset charset} to be
94      *        used to decode the ZIP entry name (ignored if the
95      *        <a href="package-summary.html#lang_encoding"> language
96      *        encoding bit</a> of the ZIP entry's general purpose bit
97      *        flag is set).
98      *
99      * @since 1.7
100      */
ZipInputStream(InputStream in, Charset charset)101     public ZipInputStream(InputStream in, Charset charset) {
102         super(new PushbackInputStream(in, 512), new Inflater(true), 512);
103         // Android-changed: Unconditionally close external inflaters (b/26462400)
104         // usesDefaultInflater = true;
105         if(in == null) {
106             throw new NullPointerException("in is null");
107         }
108         if (charset == null)
109             throw new NullPointerException("charset is null");
110         this.zc = ZipCoder.get(charset);
111     }
112 
113     // Android-changed: Additional ZipException throw scenario with ZipPathValidator.
114     /**
115      * Reads the next ZIP file entry and positions the stream at the
116      * beginning of the entry data.
117      *
118      * <p>If the app targets Android U or above, zip file entry names containing
119      * ".." or starting with "/" passed here will throw a {@link ZipException}.
120      * For more details, see {@link dalvik.system.ZipPathValidator}.
121      *
122      * @return the next ZIP file entry, or null if there are no more entries
123      * @exception ZipException if (1) a ZIP file error has occurred or
124      *            (2) <code>targetSdkVersion >= BUILD.VERSION_CODES.UPSIDE_DOWN_CAKE</code>
125      *            and (the <code>name</code> argument contains ".." or starts with "/").
126      * @exception IOException if an I/O error has occurred
127      */
getNextEntry()128     public ZipEntry getNextEntry() throws IOException {
129         ensureOpen();
130         if (entry != null) {
131             closeEntry();
132         }
133         crc.reset();
134         inf.reset();
135         if ((entry = readLOC()) == null) {
136             return null;
137         }
138         // Android-changed: Return more accurate value from available().
139         // Initialize the remaining field with the number of bytes that can be read from the entry
140         // for both uncompressed and compressed entries so that it can be used to provide a more
141         // accurate return value for available().
142         // if (entry.method == STORED) {
143         if (entry.method == STORED || entry.method == DEFLATED) {
144             remaining = entry.size;
145         }
146         entryEOF = false;
147         return entry;
148     }
149 
150     /**
151      * Closes the current ZIP entry and positions the stream for reading the
152      * next entry.
153      * @exception ZipException if a ZIP file error has occurred
154      * @exception IOException if an I/O error has occurred
155      */
closeEntry()156     public void closeEntry() throws IOException {
157         ensureOpen();
158         while (read(tmpbuf, 0, tmpbuf.length) != -1) ;
159         entryEOF = true;
160     }
161 
162     /**
163      * Returns 0 after EOF has reached for the current entry data,
164      * otherwise always return 1.
165      * <p>
166      * Programs should not count on this method to return the actual number
167      * of bytes that could be read without blocking.
168      *
169      * @return     1 before EOF and 0 after EOF has reached for current entry.
170      * @exception  IOException  if an I/O error occurs.
171      *
172      */
available()173     public int available() throws IOException {
174         ensureOpen();
175         // Android-changed: Return more accurate value from available().
176         // Tracks the remaining bytes in order to return a more accurate value for the available
177         // bytes. Given an entry of size N both Android and upstream will return 1 until N bytes
178         // have been read at which point Android will return 0 and upstream will return 1.
179         // Upstream will only return 0 after an attempt to read a byte fails because the EOF has
180         // been reached. See http://b/111439440 for more details.
181         // if (entryEOF) {
182         if (entryEOF || (entry != null && remaining == 0)) {
183             return 0;
184         } else {
185             return 1;
186         }
187     }
188 
189     /**
190      * Reads from the current ZIP entry into an array of bytes.
191      * If <code>len</code> is not zero, the method
192      * blocks until some input is available; otherwise, no
193      * bytes are read and <code>0</code> is returned.
194      * @param b the buffer into which the data is read
195      * @param off the start offset in the destination array <code>b</code>
196      * @param len the maximum number of bytes read
197      * @return the actual number of bytes read, or -1 if the end of the
198      *         entry is reached
199      * @exception  NullPointerException if <code>b</code> is <code>null</code>.
200      * @exception  IndexOutOfBoundsException if <code>off</code> is negative,
201      * <code>len</code> is negative, or <code>len</code> is greater than
202      * <code>b.length - off</code>
203      * @exception ZipException if a ZIP file error has occurred
204      * @exception IOException if an I/O error has occurred
205      */
read(byte[] b, int off, int len)206     public int read(byte[] b, int off, int len) throws IOException {
207         ensureOpen();
208         if (off < 0 || len < 0 || off > b.length - len) {
209             throw new IndexOutOfBoundsException();
210         } else if (len == 0) {
211             return 0;
212         }
213 
214         if (entry == null) {
215             return -1;
216         }
217         switch (entry.method) {
218         case DEFLATED:
219             len = super.read(b, off, len);
220             if (len == -1) {
221                 readEnd(entry);
222                 entryEOF = true;
223                 entry = null;
224             } else {
225                 crc.update(b, off, len);
226                 // Android-added: Return more accurate value from available().
227                 // Update the remaining field so it is an accurate count of the number of bytes
228                 // remaining in this stream, after deflation.
229                 remaining -= len;
230             }
231             return len;
232         case STORED:
233             if (remaining <= 0) {
234                 entryEOF = true;
235                 entry = null;
236                 return -1;
237             }
238             if (len > remaining) {
239                 len = (int)remaining;
240             }
241             len = in.read(b, off, len);
242             if (len == -1) {
243                 throw new ZipException("unexpected EOF");
244             }
245             crc.update(b, off, len);
246             remaining -= len;
247             if (remaining == 0 && entry.crc != crc.getValue()) {
248                 throw new ZipException(
249                     "invalid entry CRC (expected 0x" + Long.toHexString(entry.crc) +
250                     " but got 0x" + Long.toHexString(crc.getValue()) + ")");
251             }
252             return len;
253         default:
254             throw new ZipException("invalid compression method");
255         }
256     }
257 
258     /**
259      * Skips specified number of bytes in the current ZIP entry.
260      * @param n the number of bytes to skip
261      * @return the actual number of bytes skipped
262      * @exception ZipException if a ZIP file error has occurred
263      * @exception IOException if an I/O error has occurred
264      * @exception IllegalArgumentException if {@code n < 0}
265      */
skip(long n)266     public long skip(long n) throws IOException {
267         if (n < 0) {
268             throw new IllegalArgumentException("negative skip length");
269         }
270         ensureOpen();
271         int max = (int)Math.min(n, Integer.MAX_VALUE);
272         int total = 0;
273         while (total < max) {
274             int len = max - total;
275             if (len > tmpbuf.length) {
276                 len = tmpbuf.length;
277             }
278             len = read(tmpbuf, 0, len);
279             if (len == -1) {
280                 entryEOF = true;
281                 break;
282             }
283             total += len;
284         }
285         return total;
286     }
287 
288     /**
289      * Closes this input stream and releases any system resources associated
290      * with the stream.
291      * @exception IOException if an I/O error has occurred
292      */
close()293     public void close() throws IOException {
294         if (!closed) {
295             super.close();
296             closed = true;
297         }
298     }
299 
300     private byte[] b = new byte[256];
301 
302     /*
303      * Reads local file (LOC) header for next entry.
304      */
readLOC()305     private ZipEntry readLOC() throws IOException {
306         try {
307             readFully(tmpbuf, 0, LOCHDR);
308         } catch (EOFException e) {
309             return null;
310         }
311         if (get32(tmpbuf, 0) != LOCSIG) {
312             return null;
313         }
314         // get flag first, we need check USE_UTF8.
315         flag = get16(tmpbuf, LOCFLG);
316         // get the entry name and create the ZipEntry first
317         int len = get16(tmpbuf, LOCNAM);
318         int blen = b.length;
319         if (len > blen) {
320             do {
321                 blen = blen * 2;
322             } while (len > blen);
323             b = new byte[blen];
324         }
325         readFully(b, 0, len);
326         // Force to use UTF-8 if the USE_UTF8 bit is ON
327         ZipEntry e = createZipEntry(((flag & USE_UTF8) != 0)
328                                     ? zc.toStringUTF8(b, len)
329                                     : zc.toString(b, len));
330         // now get the remaining fields for the entry
331         if ((flag & 1) == 1) {
332             throw new ZipException("encrypted ZIP entry not supported");
333         }
334         // BEGIN Android-added: Use ZipPathValidator to validate zip entry name.
335         ZipPathValidator.getInstance().onZipEntryAccess(e.name);
336         // END Android-added: Use ZipPathValidator to validate zip entry name.
337         e.method = get16(tmpbuf, LOCHOW);
338         e.xdostime = get32(tmpbuf, LOCTIM);
339         if ((flag & 8) == 8) {
340             // Android-changed: Remove the requirement that only DEFLATED entries
341             // can have data descriptors. This is not required by the ZIP spec and
342             // is inconsistent with the behaviour of ZipFile and versions of Android
343             // prior to Android N.
344             //
345             // /* "Data Descriptor" present */
346             // if (e.method != DEFLATED) {
347             //     throw new ZipException(
348             //             "only DEFLATED entries can have EXT descriptor");
349             // }
350         } else {
351             e.crc = get32(tmpbuf, LOCCRC);
352             e.csize = get32(tmpbuf, LOCSIZ);
353             e.size = get32(tmpbuf, LOCLEN);
354         }
355         len = get16(tmpbuf, LOCEXT);
356         if (len > 0) {
357             byte[] extra = new byte[len];
358             readFully(extra, 0, len);
359             e.setExtra0(extra,
360                         e.csize == ZIP64_MAGICVAL || e.size == ZIP64_MAGICVAL);
361         }
362         return e;
363     }
364 
365     /**
366      * Creates a new <code>ZipEntry</code> object for the specified
367      * entry name.
368      *
369      * @param name the ZIP file entry name
370      * @return the ZipEntry just created
371      */
createZipEntry(String name)372     protected ZipEntry createZipEntry(String name) {
373         return new ZipEntry(name);
374     }
375 
376     /**
377      * Reads end of deflated entry as well as EXT descriptor if present.
378      *
379      * Local headers for DEFLATED entries may optionally be followed by a
380      * data descriptor, and that data descriptor may optionally contain a
381      * leading signature (EXTSIG).
382      *
383      * From the zip spec http://www.pkware.com/documents/casestudies/APPNOTE.TXT
384      *
385      * """Although not originally assigned a signature, the value 0x08074b50
386      * has commonly been adopted as a signature value for the data descriptor
387      * record.  Implementers should be aware that ZIP files may be
388      * encountered with or without this signature marking data descriptors
389      * and should account for either case when reading ZIP files to ensure
390      * compatibility."""
391      */
readEnd(ZipEntry e)392     private void readEnd(ZipEntry e) throws IOException {
393         int n = inf.getRemaining();
394         if (n > 0) {
395             ((PushbackInputStream)in).unread(buf, len - n, n);
396         }
397         if ((flag & 8) == 8) {
398             /* "Data Descriptor" present */
399             if (inf.getBytesWritten() > ZIP64_MAGICVAL ||
400                 inf.getBytesRead() > ZIP64_MAGICVAL) {
401                 // ZIP64 format
402                 readFully(tmpbuf, 0, ZIP64_EXTHDR);
403                 long sig = get32(tmpbuf, 0);
404                 if (sig != EXTSIG) { // no EXTSIG present
405                     e.crc = sig;
406                     e.csize = get64(tmpbuf, ZIP64_EXTSIZ - ZIP64_EXTCRC);
407                     e.size = get64(tmpbuf, ZIP64_EXTLEN - ZIP64_EXTCRC);
408                     ((PushbackInputStream)in).unread(
409                         tmpbuf, ZIP64_EXTHDR - ZIP64_EXTCRC, ZIP64_EXTCRC);
410                 } else {
411                     e.crc = get32(tmpbuf, ZIP64_EXTCRC);
412                     e.csize = get64(tmpbuf, ZIP64_EXTSIZ);
413                     e.size = get64(tmpbuf, ZIP64_EXTLEN);
414                 }
415             } else {
416                 readFully(tmpbuf, 0, EXTHDR);
417                 long sig = get32(tmpbuf, 0);
418                 if (sig != EXTSIG) { // no EXTSIG present
419                     e.crc = sig;
420                     e.csize = get32(tmpbuf, EXTSIZ - EXTCRC);
421                     e.size = get32(tmpbuf, EXTLEN - EXTCRC);
422                     ((PushbackInputStream)in).unread(
423                                                tmpbuf, EXTHDR - EXTCRC, EXTCRC);
424                 } else {
425                     e.crc = get32(tmpbuf, EXTCRC);
426                     e.csize = get32(tmpbuf, EXTSIZ);
427                     e.size = get32(tmpbuf, EXTLEN);
428                 }
429             }
430         }
431         if (e.size != inf.getBytesWritten()) {
432             throw new ZipException(
433                 "invalid entry size (expected " + e.size +
434                 " but got " + inf.getBytesWritten() + " bytes)");
435         }
436         if (e.csize != inf.getBytesRead()) {
437             throw new ZipException(
438                 "invalid entry compressed size (expected " + e.csize +
439                 " but got " + inf.getBytesRead() + " bytes)");
440         }
441         if (e.crc != crc.getValue()) {
442             throw new ZipException(
443                 "invalid entry CRC (expected 0x" + Long.toHexString(e.crc) +
444                 " but got 0x" + Long.toHexString(crc.getValue()) + ")");
445         }
446     }
447 
448     /*
449      * Reads bytes, blocking until all bytes are read.
450      */
readFully(byte[] b, int off, int len)451     private void readFully(byte[] b, int off, int len) throws IOException {
452         while (len > 0) {
453             int n = in.read(b, off, len);
454             if (n == -1) {
455                 throw new EOFException();
456             }
457             off += n;
458             len -= n;
459         }
460     }
461 
462 }
463