emul/mini/src/main/java/java/util/zip/ZipInputStream.java
author Jaroslav Tulach <jaroslav.tulach@apidesign.org>
Wed, 30 Jan 2013 14:03:49 +0100
branchemul
changeset 611 9839e9a75bcf
parent 609 48ef38e9677e
child 694 0d277415ed02
permissions -rw-r--r--
Implementation of ZipInputStream
     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 static java.util.zip.ZipConstants64.*;
    33 import org.apidesign.bck2brwsr.core.JavaScriptBody;
    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     /**
    59      * Check to make sure that this stream has not been closed
    60      */
    61     private void ensureOpen() throws IOException {
    62         if (closed) {
    63             throw new IOException("Stream closed");
    64         }
    65     }
    66 
    67     /**
    68      * Creates a new ZIP input stream.
    69      *
    70      * <p>The UTF-8 {@link java.nio.charset.Charset charset} is used to
    71      * decode the entry names.
    72      *
    73      * @param in the actual input stream
    74      */
    75     public ZipInputStream(InputStream in) {
    76 //        this(in, "UTF-8");
    77         super(new PushbackInputStream(in, 512), new Inflater(true), 512);
    78         usesDefaultInflater = true;
    79         if(in == null) {
    80             throw new NullPointerException("in is null");
    81         }
    82     }
    83 
    84     /**
    85      * Creates a new ZIP input stream.
    86      *
    87      * @param in the actual input stream
    88      *
    89      * @param charset
    90      *        The {@linkplain java.nio.charset.Charset charset} to be
    91      *        used to decode the ZIP entry name (ignored if the
    92      *        <a href="package-summary.html#lang_encoding"> language
    93      *        encoding bit</a> of the ZIP entry's general purpose bit
    94      *        flag is set).
    95      *
    96      * @since 1.7
    97      *
    98     public ZipInputStream(InputStream in, Charset charset) {
    99         super(new PushbackInputStream(in, 512), new Inflater(true), 512);
   100         usesDefaultInflater = true;
   101         if(in == null) {
   102             throw new NullPointerException("in is null");
   103         }
   104         if (charset == null)
   105             throw new NullPointerException("charset is null");
   106         this.zc = ZipCoder.get(charset);
   107     }
   108     */
   109 
   110     /**
   111      * Reads the next ZIP file entry and positions the stream at the
   112      * beginning of the entry data.
   113      * @return the next ZIP file entry, or null if there are no more entries
   114      * @exception ZipException if a ZIP file error has occurred
   115      * @exception IOException if an I/O error has occurred
   116      */
   117     public ZipEntry getNextEntry() throws IOException {
   118         ensureOpen();
   119         if (entry != null) {
   120             closeEntry();
   121         }
   122         crc.reset();
   123         inf.reset();
   124         if ((entry = readLOC()) == null) {
   125             return null;
   126         }
   127         if (entry.method == STORED) {
   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         if (entryEOF) {
   160             return 0;
   161         } else {
   162             return 1;
   163         }
   164     }
   165 
   166     /**
   167      * Reads from the current ZIP entry into an array of bytes.
   168      * If <code>len</code> is not zero, the method
   169      * blocks until some input is available; otherwise, no
   170      * bytes are read and <code>0</code> is returned.
   171      * @param b the buffer into which the data is read
   172      * @param off the start offset in the destination array <code>b</code>
   173      * @param len the maximum number of bytes read
   174      * @return the actual number of bytes read, or -1 if the end of the
   175      *         entry is reached
   176      * @exception  NullPointerException if <code>b</code> is <code>null</code>.
   177      * @exception  IndexOutOfBoundsException if <code>off</code> is negative,
   178      * <code>len</code> is negative, or <code>len</code> is greater than
   179      * <code>b.length - off</code>
   180      * @exception ZipException if a ZIP file error has occurred
   181      * @exception IOException if an I/O error has occurred
   182      */
   183     public int read(byte[] b, int off, int len) throws IOException {
   184         ensureOpen();
   185         if (off < 0 || len < 0 || off > b.length - len) {
   186             throw new IndexOutOfBoundsException();
   187         } else if (len == 0) {
   188             return 0;
   189         }
   190 
   191         if (entry == null) {
   192             return -1;
   193         }
   194         switch (entry.method) {
   195         case DEFLATED:
   196             len = super.read(b, off, len);
   197             if (len == -1) {
   198                 readEnd(entry);
   199                 entryEOF = true;
   200                 entry = null;
   201             } else {
   202                 crc.update(b, off, len);
   203             }
   204             return len;
   205         case STORED:
   206             if (remaining <= 0) {
   207                 entryEOF = true;
   208                 entry = null;
   209                 return -1;
   210             }
   211             if (len > remaining) {
   212                 len = (int)remaining;
   213             }
   214             len = in.read(b, off, len);
   215             if (len == -1) {
   216                 throw new ZipException("unexpected EOF");
   217             }
   218             crc.update(b, off, len);
   219             remaining -= len;
   220             if (remaining == 0 && entry.crc != crc.getValue()) {
   221                 throw new ZipException(
   222                     "invalid entry CRC (expected 0x" + Long.toHexString(entry.crc) +
   223                     " but got 0x" + Long.toHexString(crc.getValue()) + ")");
   224             }
   225             return len;
   226         default:
   227             throw new ZipException("invalid compression method");
   228         }
   229     }
   230 
   231     /**
   232      * Skips specified number of bytes in the current ZIP entry.
   233      * @param n the number of bytes to skip
   234      * @return the actual number of bytes skipped
   235      * @exception ZipException if a ZIP file error has occurred
   236      * @exception IOException if an I/O error has occurred
   237      * @exception IllegalArgumentException if n < 0
   238      */
   239     public long skip(long n) throws IOException {
   240         if (n < 0) {
   241             throw new IllegalArgumentException("negative skip length");
   242         }
   243         ensureOpen();
   244         int max = (int)Math.min(n, Integer.MAX_VALUE);
   245         int total = 0;
   246         while (total < max) {
   247             int len = max - total;
   248             if (len > tmpbuf.length) {
   249                 len = tmpbuf.length;
   250             }
   251             len = read(tmpbuf, 0, len);
   252             if (len == -1) {
   253                 entryEOF = true;
   254                 break;
   255             }
   256             total += len;
   257         }
   258         return total;
   259     }
   260 
   261     /**
   262      * Closes this input stream and releases any system resources associated
   263      * with the stream.
   264      * @exception IOException if an I/O error has occurred
   265      */
   266     public void close() throws IOException {
   267         if (!closed) {
   268             super.close();
   269             closed = true;
   270         }
   271     }
   272 
   273     private byte[] b = new byte[256];
   274 
   275     /*
   276      * Reads local file (LOC) header for next entry.
   277      */
   278     private ZipEntry readLOC() throws IOException {
   279         try {
   280             readFully(tmpbuf, 0, LOCHDR);
   281         } catch (EOFException e) {
   282             return null;
   283         }
   284         if (get32(tmpbuf, 0) != LOCSIG) {
   285             return null;
   286         }
   287         // get flag first, we need check EFS.
   288         flag = get16(tmpbuf, LOCFLG);
   289         // get the entry name and create the ZipEntry first
   290         int len = get16(tmpbuf, LOCNAM);
   291         int blen = b.length;
   292         if (len > blen) {
   293             do
   294                 blen = blen * 2;
   295             while (len > blen);
   296             b = new byte[blen];
   297         }
   298         readFully(b, 0, len);
   299         // Force to use UTF-8 if the EFS bit is ON, even the cs is NOT UTF-8
   300         ZipEntry e = createZipEntry(((flag & EFS) != 0)
   301                                     ? toStringUTF8(b, len)
   302                                     : toString(b, len));
   303         // now get the remaining fields for the entry
   304         if ((flag & 1) == 1) {
   305             throw new ZipException("encrypted ZIP entry not supported");
   306         }
   307         e.method = get16(tmpbuf, LOCHOW);
   308         e.time = get32(tmpbuf, LOCTIM);
   309         if ((flag & 8) == 8) {
   310             /* "Data Descriptor" present */
   311             if (e.method != DEFLATED) {
   312                 throw new ZipException(
   313                         "only DEFLATED entries can have EXT descriptor");
   314             }
   315         } else {
   316             e.crc = get32(tmpbuf, LOCCRC);
   317             e.csize = get32(tmpbuf, LOCSIZ);
   318             e.size = get32(tmpbuf, LOCLEN);
   319         }
   320         len = get16(tmpbuf, LOCEXT);
   321         if (len > 0) {
   322             byte[] bb = new byte[len];
   323             readFully(bb, 0, len);
   324             e.setExtra(bb);
   325             // extra fields are in "HeaderID(2)DataSize(2)Data... format
   326             if (e.csize == ZIP64_MAGICVAL || e.size == ZIP64_MAGICVAL) {
   327                 int off = 0;
   328                 while (off + 4 < len) {
   329                     int sz = get16(bb, off + 2);
   330                     if (get16(bb, off) == ZIP64_EXTID) {
   331                         off += 4;
   332                         // LOC extra zip64 entry MUST include BOTH original and
   333                         // compressed file size fields
   334                         if (sz < 16 || (off + sz) > len ) {
   335                             // Invalid zip64 extra fields, simply skip. Even it's
   336                             // rare, it's possible the entry size happens to be
   337                             // the magic value and it "accidnetly" has some bytes
   338                             // in extra match the id.
   339                             return e;
   340                         }
   341                         e.size = get64(bb, off);
   342                         e.csize = get64(bb, off + 8);
   343                         break;
   344                     }
   345                     off += (sz + 4);
   346                 }
   347             }
   348         }
   349         return e;
   350     }
   351 
   352     /**
   353      * Creates a new <code>ZipEntry</code> object for the specified
   354      * entry name.
   355      *
   356      * @param name the ZIP file entry name
   357      * @return the ZipEntry just created
   358      */
   359     protected ZipEntry createZipEntry(String name) {
   360         return new ZipEntry(name);
   361     }
   362 
   363     /*
   364      * Reads end of deflated entry as well as EXT descriptor if present.
   365      */
   366     private void readEnd(ZipEntry e) throws IOException {
   367         int n = inf.getRemaining();
   368         if (n > 0) {
   369             ((PushbackInputStream)in).unread(buf, len - n, n);
   370         }
   371         if ((flag & 8) == 8) {
   372             /* "Data Descriptor" present */
   373             if (inf.getBytesWritten() > ZIP64_MAGICVAL ||
   374                 inf.getBytesRead() > ZIP64_MAGICVAL) {
   375                 // ZIP64 format
   376                 readFully(tmpbuf, 0, ZIP64_EXTHDR);
   377                 long sig = get32(tmpbuf, 0);
   378                 if (sig != EXTSIG) { // no EXTSIG present
   379                     e.crc = sig;
   380                     e.csize = get64(tmpbuf, ZIP64_EXTSIZ - ZIP64_EXTCRC);
   381                     e.size = get64(tmpbuf, ZIP64_EXTLEN - ZIP64_EXTCRC);
   382                     ((PushbackInputStream)in).unread(
   383                         tmpbuf, ZIP64_EXTHDR - ZIP64_EXTCRC - 1, ZIP64_EXTCRC);
   384                 } else {
   385                     e.crc = get32(tmpbuf, ZIP64_EXTCRC);
   386                     e.csize = get64(tmpbuf, ZIP64_EXTSIZ);
   387                     e.size = get64(tmpbuf, ZIP64_EXTLEN);
   388                 }
   389             } else {
   390                 readFully(tmpbuf, 0, EXTHDR);
   391                 long sig = get32(tmpbuf, 0);
   392                 if (sig != EXTSIG) { // no EXTSIG present
   393                     e.crc = sig;
   394                     e.csize = get32(tmpbuf, EXTSIZ - EXTCRC);
   395                     e.size = get32(tmpbuf, EXTLEN - EXTCRC);
   396                     ((PushbackInputStream)in).unread(
   397                                                tmpbuf, EXTHDR - EXTCRC - 1, EXTCRC);
   398                 } else {
   399                     e.crc = get32(tmpbuf, EXTCRC);
   400                     e.csize = get32(tmpbuf, EXTSIZ);
   401                     e.size = get32(tmpbuf, EXTLEN);
   402                 }
   403             }
   404         }
   405         if (e.size != inf.getBytesWritten()) {
   406             throw new ZipException(
   407                 "invalid entry size (expected " + e.size +
   408                 " but got " + inf.getBytesWritten() + " bytes)");
   409         }
   410         if (e.csize != inf.getBytesRead()) {
   411             throw new ZipException(
   412                 "invalid entry compressed size (expected " + e.csize +
   413                 " but got " + inf.getBytesRead() + " bytes)");
   414         }
   415         if (e.crc != crc.getValue()) {
   416             throw new ZipException(
   417                 "invalid entry CRC (expected 0x" + Long.toHexString(e.crc) +
   418                 " but got 0x" + Long.toHexString(crc.getValue()) + ")");
   419         }
   420     }
   421 
   422     /*
   423      * Reads bytes, blocking until all bytes are read.
   424      */
   425     private void readFully(byte[] b, int off, int len) throws IOException {
   426         while (len > 0) {
   427             int n = in.read(b, off, len);
   428             if (n == -1) {
   429                 throw new EOFException();
   430             }
   431             off += n;
   432             len -= n;
   433         }
   434     }
   435 
   436     /*
   437      * Fetches unsigned 16-bit value from byte array at specified offset.
   438      * The bytes are assumed to be in Intel (little-endian) byte order.
   439      */
   440     private static final int get16(byte b[], int off) {
   441         return (b[off] & 0xff) | ((b[off+1] & 0xff) << 8);
   442     }
   443 
   444     /*
   445      * Fetches unsigned 32-bit value from byte array at specified offset.
   446      * The bytes are assumed to be in Intel (little-endian) byte order.
   447      */
   448     private static final long get32(byte b[], int off) {
   449         return (get16(b, off) | ((long)get16(b, off+2) << 16)) & 0xffffffffL;
   450     }
   451 
   452     /*
   453      * Fetches signed 64-bit value from byte array at specified offset.
   454      * The bytes are assumed to be in Intel (little-endian) byte order.
   455      */
   456     private static final long get64(byte b[], int off) {
   457         return get32(b, off) | (get32(b, off+4) << 32);
   458     }
   459 
   460     private static String toStringUTF8(byte[] arr, int len) {
   461         return new String(arr, 0, len);
   462     }
   463     
   464     private static String toString(byte[] b, int len) {
   465         return new String(b, 0, len);
   466     }
   467 }