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