rt/emul/mini/src/main/java/org/apidesign/bck2brwsr/emul/zip/ZipInputStream.java
author Jaroslav Tulach <jaroslav.tulach@apidesign.org>
Tue, 26 Feb 2013 16:54:16 +0100
changeset 772 d382dacfd73f
parent 694 emul/mini/src/main/java/org/apidesign/bck2brwsr/emul/zip/ZipInputStream.java@0d277415ed02
permissions -rw-r--r--
Moving modules around so the runtime is under one master pom and can be built without building other modules that are in the repository
     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 }