Home | History | Annotate | Download | only in zip
      1 /*
      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
      8  *
      9  *     http://www.apache.org/licenses/LICENSE-2.0
     10  *
     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.
     16  */
     17 
     18 package java.util.zip;
     19 
     20 import java.io.EOFException;
     21 import java.io.IOException;
     22 import java.io.InputStream;
     23 import java.io.PushbackInputStream;
     24 import java.nio.charset.ModifiedUtf8;
     25 import java.util.jar.Attributes;
     26 import java.util.jar.JarEntry;
     27 
     28 /**
     29  * This class provides an implementation of {@code FilterInputStream} that
     30  * uncompresses data from a <i>ZIP-archive</i> input stream.
     31  * <p>
     32  * A <i>ZIP-archive</i> is a collection of compressed (or uncompressed) files -
     33  * the so called ZIP entries. Therefore when reading from a {@code
     34  * ZipInputStream} first the entry's attributes will be retrieved with {@code
     35  * getNextEntry} before its data is read.
     36  * <p>
     37  * While {@code InflaterInputStream} can read a compressed <i>ZIP-archive</i>
     38  * entry, this extension can read uncompressed entries as well.
     39  * <p>
     40  * Use {@code ZipFile} if you can access the archive as a file directly.
     41  *
     42  * @see ZipEntry
     43  * @see ZipFile
     44  */
     45 public class ZipInputStream extends InflaterInputStream implements ZipConstants {
     46     static final int DEFLATED = 8;
     47 
     48     static final int STORED = 0;
     49 
     50     static final int ZIPLocalHeaderVersionNeeded = 20;
     51 
     52     private boolean entriesEnd = false;
     53 
     54     private boolean hasDD = false;
     55 
     56     private int entryIn = 0;
     57 
     58     private int inRead, lastRead = 0;
     59 
     60     ZipEntry currentEntry;
     61 
     62     private final byte[] hdrBuf = new byte[LOCHDR - LOCVER];
     63 
     64     private final CRC32 crc = new CRC32();
     65 
     66     private byte[] nameBuf = new byte[256];
     67 
     68     private char[] charBuf = new char[256];
     69 
     70     /**
     71      * Constructs a new {@code ZipInputStream} from the specified input stream.
     72      *
     73      * @param stream
     74      *            the input stream to representing a ZIP archive.
     75      */
     76     public ZipInputStream(InputStream stream) {
     77         super(new PushbackInputStream(stream, BUF_SIZE), new Inflater(true));
     78         if (stream == null) {
     79             throw new NullPointerException();
     80         }
     81     }
     82 
     83     /**
     84      * Closes this {@code ZipInputStream}.
     85      *
     86      * @throws IOException
     87      *             if an {@code IOException} occurs.
     88      */
     89     @Override
     90     public void close() throws IOException {
     91         if (!closed) {
     92             closeEntry(); // Close the current entry
     93             super.close();
     94         }
     95     }
     96 
     97     /**
     98      * Closes the current ZIP entry and positions to read the next entry.
     99      *
    100      * @throws IOException
    101      *             if an {@code IOException} occurs.
    102      */
    103     public void closeEntry() throws IOException {
    104         checkClosed();
    105         if (currentEntry == null) {
    106             return;
    107         }
    108         if (currentEntry instanceof java.util.jar.JarEntry) {
    109             Attributes temp = ((JarEntry) currentEntry).getAttributes();
    110             if (temp != null && temp.containsKey("hidden")) {
    111                 return;
    112             }
    113         }
    114 
    115         /*
    116          * The following code is careful to leave the ZipInputStream in a
    117          * consistent state, even when close() results in an exception. It does
    118          * so by:
    119          *  - pushing bytes back into the source stream
    120          *  - reading a data descriptor footer from the source stream
    121          *  - resetting fields that manage the entry being closed
    122          */
    123 
    124         // Ensure all entry bytes are read
    125         Exception failure = null;
    126         try {
    127             skip(Long.MAX_VALUE);
    128         } catch (Exception e) {
    129             failure = e;
    130         }
    131 
    132         int inB, out;
    133         if (currentEntry.compressionMethod == DEFLATED) {
    134             inB = inf.getTotalIn();
    135             out = inf.getTotalOut();
    136         } else {
    137             inB = inRead;
    138             out = inRead;
    139         }
    140         int diff = entryIn - inB;
    141         // Pushback any required bytes
    142         if (diff != 0) {
    143             ((PushbackInputStream) in).unread(buf, len - diff, diff);
    144         }
    145 
    146         try {
    147             readAndVerifyDataDescriptor(inB, out);
    148         } catch (Exception e) {
    149             if (failure == null) { // otherwise we're already going to throw
    150                 failure = e;
    151             }
    152         }
    153 
    154         inf.reset();
    155         lastRead = inRead = entryIn = len = 0;
    156         crc.reset();
    157         currentEntry = null;
    158 
    159         if (failure != null) {
    160             if (failure instanceof IOException) {
    161                 throw (IOException) failure;
    162             } else if (failure instanceof RuntimeException) {
    163                 throw (RuntimeException) failure;
    164             }
    165             AssertionError error = new AssertionError();
    166             error.initCause(failure);
    167             throw error;
    168         }
    169     }
    170 
    171     private void readAndVerifyDataDescriptor(int inB, int out) throws IOException {
    172         if (hasDD) {
    173             in.read(hdrBuf, 0, EXTHDR);
    174             long sig = getLong(hdrBuf, 0);
    175             if (sig != EXTSIG) {
    176                 throw new ZipException(String.format("unknown format (EXTSIG=%x)", sig));
    177             }
    178             currentEntry.crc = getLong(hdrBuf, EXTCRC);
    179             currentEntry.compressedSize = getLong(hdrBuf, EXTSIZ);
    180             currentEntry.size = getLong(hdrBuf, EXTLEN);
    181         }
    182         if (currentEntry.crc != crc.getValue()) {
    183             throw new ZipException("CRC mismatch");
    184         }
    185         if (currentEntry.compressedSize != inB || currentEntry.size != out) {
    186             throw new ZipException("Size mismatch");
    187         }
    188     }
    189 
    190     /**
    191      * Reads the next entry from this {@code ZipInputStream} or {@code null} if
    192      * no more entries are present.
    193      *
    194      * @return the next {@code ZipEntry} contained in the input stream.
    195      * @throws IOException
    196      *             if an {@code IOException} occurs.
    197      * @see ZipEntry
    198      */
    199     public ZipEntry getNextEntry() throws IOException {
    200         closeEntry();
    201         if (entriesEnd) {
    202             return null;
    203         }
    204 
    205         int x = 0, count = 0;
    206         while (count != 4) {
    207             count += x = in.read(hdrBuf, count, 4 - count);
    208             if (x == -1) {
    209                 return null;
    210             }
    211         }
    212         long hdr = getLong(hdrBuf, 0);
    213         if (hdr == CENSIG) {
    214             entriesEnd = true;
    215             return null;
    216         }
    217         if (hdr != LOCSIG) {
    218             return null;
    219         }
    220 
    221         // Read the local header
    222         count = 0;
    223         while (count != (LOCHDR - LOCVER)) {
    224             count += x = in.read(hdrBuf, count, (LOCHDR - LOCVER) - count);
    225             if (x == -1) {
    226                 throw new EOFException();
    227             }
    228         }
    229         int version = getShort(hdrBuf, 0) & 0xff;
    230         if (version > ZIPLocalHeaderVersionNeeded) {
    231             throw new ZipException("Cannot read local header version " + version);
    232         }
    233         int flags = getShort(hdrBuf, LOCFLG - LOCVER);
    234         hasDD = ((flags & ZipFile.GPBF_DATA_DESCRIPTOR_FLAG) != 0);
    235         int cetime = getShort(hdrBuf, LOCTIM - LOCVER);
    236         int cemodDate = getShort(hdrBuf, LOCTIM - LOCVER + 2);
    237         int cecompressionMethod = getShort(hdrBuf, LOCHOW - LOCVER);
    238         long cecrc = 0, cecompressedSize = 0, cesize = -1;
    239         if (!hasDD) {
    240             cecrc = getLong(hdrBuf, LOCCRC - LOCVER);
    241             cecompressedSize = getLong(hdrBuf, LOCSIZ - LOCVER);
    242             cesize = getLong(hdrBuf, LOCLEN - LOCVER);
    243         }
    244         int flen = getShort(hdrBuf, LOCNAM - LOCVER);
    245         if (flen == 0) {
    246             throw new ZipException("Entry is not named");
    247         }
    248         int elen = getShort(hdrBuf, LOCEXT - LOCVER);
    249 
    250         count = 0;
    251         if (flen > nameBuf.length) {
    252             nameBuf = new byte[flen];
    253             charBuf = new char[flen];
    254         }
    255         while (count != flen) {
    256             count += x = in.read(nameBuf, count, flen - count);
    257             if (x == -1) {
    258                 throw new EOFException();
    259             }
    260         }
    261         currentEntry = createZipEntry(ModifiedUtf8.decode(nameBuf, charBuf, 0, flen));
    262         currentEntry.time = cetime;
    263         currentEntry.modDate = cemodDate;
    264         currentEntry.setMethod(cecompressionMethod);
    265         if (cesize != -1) {
    266             currentEntry.setCrc(cecrc);
    267             currentEntry.setSize(cesize);
    268             currentEntry.setCompressedSize(cecompressedSize);
    269         }
    270         if (elen > 0) {
    271             count = 0;
    272             byte[] e = new byte[elen];
    273             while (count != elen) {
    274                 count += x = in.read(e, count, elen - count);
    275                 if (x == -1) {
    276                     throw new EOFException();
    277                 }
    278             }
    279             currentEntry.setExtra(e);
    280         }
    281         return currentEntry;
    282     }
    283 
    284     /* Read 4 bytes from the buffer and store it as an int */
    285 
    286     /**
    287      * Reads up to the specified number of uncompressed bytes into the buffer
    288      * starting at the offset.
    289      *
    290      * @param buffer
    291      *            a byte array
    292      * @param start
    293      *            the starting offset into the buffer
    294      * @param length
    295      *            the number of bytes to read
    296      * @return the number of bytes read
    297      */
    298     @Override
    299     public int read(byte[] buffer, int start, int length) throws IOException {
    300         checkClosed();
    301         if (inf.finished() || currentEntry == null) {
    302             return -1;
    303         }
    304         // avoid int overflow, check null buffer
    305         if (start > buffer.length || length < 0 || start < 0
    306                 || buffer.length - start < length) {
    307             throw new ArrayIndexOutOfBoundsException();
    308         }
    309 
    310         if (currentEntry.compressionMethod == STORED) {
    311             int csize = (int) currentEntry.size;
    312             if (inRead >= csize) {
    313                 return -1;
    314             }
    315             if (lastRead >= len) {
    316                 lastRead = 0;
    317                 if ((len = in.read(buf)) == -1) {
    318                     eof = true;
    319                     return -1;
    320                 }
    321                 entryIn += len;
    322             }
    323             int toRead = length > (len - lastRead) ? len - lastRead : length;
    324             if ((csize - inRead) < toRead) {
    325                 toRead = csize - inRead;
    326             }
    327             System.arraycopy(buf, lastRead, buffer, start, toRead);
    328             lastRead += toRead;
    329             inRead += toRead;
    330             crc.update(buffer, start, toRead);
    331             return toRead;
    332         }
    333         if (inf.needsInput()) {
    334             fill();
    335             if (len > 0) {
    336                 entryIn += len;
    337             }
    338         }
    339         int read;
    340         try {
    341             read = inf.inflate(buffer, start, length);
    342         } catch (DataFormatException e) {
    343             throw new ZipException(e.getMessage());
    344         }
    345         if (read == 0 && inf.finished()) {
    346             return -1;
    347         }
    348         crc.update(buffer, start, read);
    349         return read;
    350     }
    351 
    352     /**
    353      * Skips up to the specified number of bytes in the current ZIP entry.
    354      *
    355      * @param value
    356      *            the number of bytes to skip.
    357      * @return the number of bytes skipped.
    358      * @throws IOException
    359      *             if an {@code IOException} occurs.
    360      */
    361     @Override
    362     public long skip(long value) throws IOException {
    363         if (value < 0) {
    364             throw new IllegalArgumentException();
    365         }
    366 
    367         long skipped = 0;
    368         byte[] b = new byte[(int)Math.min(value, 2048L)];
    369         while (skipped != value) {
    370             long rem = value - skipped;
    371             int x = read(b, 0, (int) (b.length > rem ? rem : b.length));
    372             if (x == -1) {
    373                 return skipped;
    374             }
    375             skipped += x;
    376         }
    377         return skipped;
    378     }
    379 
    380     @Override
    381     public int available() throws IOException {
    382         checkClosed();
    383         // The InflaterInputStream contract says we must only return 0 or 1.
    384         return (currentEntry == null || inRead < currentEntry.size) ? 1 : 0;
    385     }
    386 
    387     /**
    388      * creates a {@link ZipEntry } with the given name.
    389      *
    390      * @param name
    391      *            the name of the entry.
    392      * @return the created {@code ZipEntry}.
    393      */
    394     protected ZipEntry createZipEntry(String name) {
    395         return new ZipEntry(name);
    396     }
    397 
    398     private int getShort(byte[] buffer, int off) {
    399         return (buffer[off] & 0xFF) | ((buffer[off + 1] & 0xFF) << 8);
    400     }
    401 
    402     private long getLong(byte[] buffer, int off) {
    403         long l = 0;
    404         l |= (buffer[off] & 0xFF);
    405         l |= (buffer[off + 1] & 0xFF) << 8;
    406         l |= (buffer[off + 2] & 0xFF) << 16;
    407         l |= ((long) (buffer[off + 3] & 0xFF)) << 24;
    408         return l;
    409     }
    410 
    411     private void checkClosed() throws IOException {
    412         if (closed) {
    413             throw new IOException("Stream is closed");
    414         }
    415     }
    416 }
    417