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