--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/java/util/zip/ZipInputStream.java Tue Sep 12 19:03:39 2017 +0200
@@ -0,0 +1,426 @@
+/*
+ * Copyright (c) 1996, 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package java.util.zip;
+
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.EOFException;
+import java.io.PushbackInputStream;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import static java.util.zip.ZipConstants64.*;
+import static java.util.zip.ZipUtils.*;
+
+/**
+ * This class implements an input stream filter for reading files in the
+ * ZIP file format. Includes support for both compressed and uncompressed
+ * entries.
+ *
+ * @author David Connelly
+ * @since 1.1
+ */
+public
+class ZipInputStream extends InflaterInputStream implements ZipConstants {
+ private ZipEntry entry;
+ private int flag;
+ private CRC32 crc = new CRC32();
+ private long remaining;
+ private byte[] tmpbuf = new byte[512];
+
+ private static final int STORED = ZipEntry.STORED;
+ private static final int DEFLATED = ZipEntry.DEFLATED;
+
+ private boolean closed = false;
+ // this flag is set to true after EOF has reached for
+ // one entry
+ private boolean entryEOF = false;
+
+ private ZipCoder zc;
+
+ /**
+ * Check to make sure that this stream has not been closed
+ */
+ private void ensureOpen() throws IOException {
+ if (closed) {
+ throw new IOException("Stream closed");
+ }
+ }
+
+ /**
+ * Creates a new ZIP input stream.
+ *
+ * <p>The UTF-8 {@link java.nio.charset.Charset charset} is used to
+ * decode the entry names.
+ *
+ * @param in the actual input stream
+ */
+ public ZipInputStream(InputStream in) {
+ this(in, StandardCharsets.UTF_8);
+ }
+
+ /**
+ * Creates a new ZIP input stream.
+ *
+ * @param in the actual input stream
+ *
+ * @param charset
+ * The {@linkplain java.nio.charset.Charset charset} to be
+ * used to decode the ZIP entry name (ignored if the
+ * <a href="package-summary.html#lang_encoding"> language
+ * encoding bit</a> of the ZIP entry's general purpose bit
+ * flag is set).
+ *
+ * @since 1.7
+ */
+ public ZipInputStream(InputStream in, Charset charset) {
+ super(new PushbackInputStream(in, 512), new Inflater(true), 512);
+ usesDefaultInflater = true;
+ if(in == null) {
+ throw new NullPointerException("in is null");
+ }
+ if (charset == null)
+ throw new NullPointerException("charset is null");
+ this.zc = ZipCoder.get(charset);
+ }
+
+ /**
+ * Reads the next ZIP file entry and positions the stream at the
+ * beginning of the entry data.
+ * @return the next ZIP file entry, or null if there are no more entries
+ * @exception ZipException if a ZIP file error has occurred
+ * @exception IOException if an I/O error has occurred
+ */
+ public ZipEntry getNextEntry() throws IOException {
+ ensureOpen();
+ if (entry != null) {
+ closeEntry();
+ }
+ crc.reset();
+ inf.reset();
+ if ((entry = readLOC()) == null) {
+ return null;
+ }
+ if (entry.method == STORED) {
+ remaining = entry.size;
+ }
+ entryEOF = false;
+ return entry;
+ }
+
+ /**
+ * Closes the current ZIP entry and positions the stream for reading the
+ * next entry.
+ * @exception ZipException if a ZIP file error has occurred
+ * @exception IOException if an I/O error has occurred
+ */
+ public void closeEntry() throws IOException {
+ ensureOpen();
+ while (read(tmpbuf, 0, tmpbuf.length) != -1) ;
+ entryEOF = true;
+ }
+
+ /**
+ * Returns 0 after EOF has reached for the current entry data,
+ * otherwise always return 1.
+ * <p>
+ * Programs should not count on this method to return the actual number
+ * of bytes that could be read without blocking.
+ *
+ * @return 1 before EOF and 0 after EOF has reached for current entry.
+ * @exception IOException if an I/O error occurs.
+ *
+ */
+ public int available() throws IOException {
+ ensureOpen();
+ if (entryEOF) {
+ return 0;
+ } else {
+ return 1;
+ }
+ }
+
+ /**
+ * Reads from the current ZIP entry into an array of bytes.
+ * If <code>len</code> is not zero, the method
+ * blocks until some input is available; otherwise, no
+ * bytes are read and <code>0</code> is returned.
+ * @param b the buffer into which the data is read
+ * @param off the start offset in the destination array <code>b</code>
+ * @param len the maximum number of bytes read
+ * @return the actual number of bytes read, or -1 if the end of the
+ * entry is reached
+ * @exception NullPointerException if <code>b</code> is <code>null</code>.
+ * @exception IndexOutOfBoundsException if <code>off</code> is negative,
+ * <code>len</code> is negative, or <code>len</code> is greater than
+ * <code>b.length - off</code>
+ * @exception ZipException if a ZIP file error has occurred
+ * @exception IOException if an I/O error has occurred
+ */
+ public int read(byte[] b, int off, int len) throws IOException {
+ ensureOpen();
+ if (off < 0 || len < 0 || off > b.length - len) {
+ throw new IndexOutOfBoundsException();
+ } else if (len == 0) {
+ return 0;
+ }
+
+ if (entry == null) {
+ return -1;
+ }
+ switch (entry.method) {
+ case DEFLATED:
+ len = super.read(b, off, len);
+ if (len == -1) {
+ readEnd(entry);
+ entryEOF = true;
+ entry = null;
+ } else {
+ crc.update(b, off, len);
+ }
+ return len;
+ case STORED:
+ if (remaining <= 0) {
+ entryEOF = true;
+ entry = null;
+ return -1;
+ }
+ if (len > remaining) {
+ len = (int)remaining;
+ }
+ len = in.read(b, off, len);
+ if (len == -1) {
+ throw new ZipException("unexpected EOF");
+ }
+ crc.update(b, off, len);
+ remaining -= len;
+ if (remaining == 0 && entry.crc != crc.getValue()) {
+ throw new ZipException(
+ "invalid entry CRC (expected 0x" + Long.toHexString(entry.crc) +
+ " but got 0x" + Long.toHexString(crc.getValue()) + ")");
+ }
+ return len;
+ default:
+ throw new ZipException("invalid compression method");
+ }
+ }
+
+ /**
+ * Skips specified number of bytes in the current ZIP entry.
+ * @param n the number of bytes to skip
+ * @return the actual number of bytes skipped
+ * @exception ZipException if a ZIP file error has occurred
+ * @exception IOException if an I/O error has occurred
+ * @exception IllegalArgumentException if {@code n < 0}
+ */
+ public long skip(long n) throws IOException {
+ if (n < 0) {
+ throw new IllegalArgumentException("negative skip length");
+ }
+ ensureOpen();
+ int max = (int)Math.min(n, Integer.MAX_VALUE);
+ int total = 0;
+ while (total < max) {
+ int len = max - total;
+ if (len > tmpbuf.length) {
+ len = tmpbuf.length;
+ }
+ len = read(tmpbuf, 0, len);
+ if (len == -1) {
+ entryEOF = true;
+ break;
+ }
+ total += len;
+ }
+ return total;
+ }
+
+ /**
+ * Closes this input stream and releases any system resources associated
+ * with the stream.
+ * @exception IOException if an I/O error has occurred
+ */
+ public void close() throws IOException {
+ if (!closed) {
+ super.close();
+ closed = true;
+ }
+ }
+
+ private byte[] b = new byte[256];
+
+ /*
+ * Reads local file (LOC) header for next entry.
+ */
+ private ZipEntry readLOC() throws IOException {
+ try {
+ readFully(tmpbuf, 0, LOCHDR);
+ } catch (EOFException e) {
+ return null;
+ }
+ if (get32(tmpbuf, 0) != LOCSIG) {
+ return null;
+ }
+ // get flag first, we need check EFS.
+ flag = get16(tmpbuf, LOCFLG);
+ // get the entry name and create the ZipEntry first
+ int len = get16(tmpbuf, LOCNAM);
+ int blen = b.length;
+ if (len > blen) {
+ do {
+ blen = blen * 2;
+ } while (len > blen);
+ b = new byte[blen];
+ }
+ readFully(b, 0, len);
+ // Force to use UTF-8 if the EFS bit is ON, even the cs is NOT UTF-8
+ ZipEntry e = createZipEntry(((flag & EFS) != 0)
+ ? zc.toStringUTF8(b, len)
+ : zc.toString(b, len));
+ // now get the remaining fields for the entry
+ if ((flag & 1) == 1) {
+ throw new ZipException("encrypted ZIP entry not supported");
+ }
+ e.method = get16(tmpbuf, LOCHOW);
+ e.xdostime = get32(tmpbuf, LOCTIM);
+ if ((flag & 8) == 8) {
+ /* "Data Descriptor" present */
+ if (e.method != DEFLATED) {
+ throw new ZipException(
+ "only DEFLATED entries can have EXT descriptor");
+ }
+ } else {
+ e.crc = get32(tmpbuf, LOCCRC);
+ e.csize = get32(tmpbuf, LOCSIZ);
+ e.size = get32(tmpbuf, LOCLEN);
+ }
+ len = get16(tmpbuf, LOCEXT);
+ if (len > 0) {
+ byte[] extra = new byte[len];
+ readFully(extra, 0, len);
+ e.setExtra0(extra,
+ e.csize == ZIP64_MAGICVAL || e.size == ZIP64_MAGICVAL);
+ }
+ return e;
+ }
+
+ /**
+ * Creates a new <code>ZipEntry</code> object for the specified
+ * entry name.
+ *
+ * @param name the ZIP file entry name
+ * @return the ZipEntry just created
+ */
+ protected ZipEntry createZipEntry(String name) {
+ return new ZipEntry(name);
+ }
+
+ /**
+ * Reads end of deflated entry as well as EXT descriptor if present.
+ *
+ * Local headers for DEFLATED entries may optionally be followed by a
+ * data descriptor, and that data descriptor may optionally contain a
+ * leading signature (EXTSIG).
+ *
+ * From the zip spec http://www.pkware.com/documents/casestudies/APPNOTE.TXT
+ *
+ * """Although not originally assigned a signature, the value 0x08074b50
+ * has commonly been adopted as a signature value for the data descriptor
+ * record. Implementers should be aware that ZIP files may be
+ * encountered with or without this signature marking data descriptors
+ * and should account for either case when reading ZIP files to ensure
+ * compatibility."""
+ */
+ private void readEnd(ZipEntry e) throws IOException {
+ int n = inf.getRemaining();
+ if (n > 0) {
+ ((PushbackInputStream)in).unread(buf, len - n, n);
+ }
+ if ((flag & 8) == 8) {
+ /* "Data Descriptor" present */
+ if (inf.getBytesWritten() > ZIP64_MAGICVAL ||
+ inf.getBytesRead() > ZIP64_MAGICVAL) {
+ // ZIP64 format
+ readFully(tmpbuf, 0, ZIP64_EXTHDR);
+ long sig = get32(tmpbuf, 0);
+ if (sig != EXTSIG) { // no EXTSIG present
+ e.crc = sig;
+ e.csize = get64(tmpbuf, ZIP64_EXTSIZ - ZIP64_EXTCRC);
+ e.size = get64(tmpbuf, ZIP64_EXTLEN - ZIP64_EXTCRC);
+ ((PushbackInputStream)in).unread(
+ tmpbuf, ZIP64_EXTHDR - ZIP64_EXTCRC, ZIP64_EXTCRC);
+ } else {
+ e.crc = get32(tmpbuf, ZIP64_EXTCRC);
+ e.csize = get64(tmpbuf, ZIP64_EXTSIZ);
+ e.size = get64(tmpbuf, ZIP64_EXTLEN);
+ }
+ } else {
+ readFully(tmpbuf, 0, EXTHDR);
+ long sig = get32(tmpbuf, 0);
+ if (sig != EXTSIG) { // no EXTSIG present
+ e.crc = sig;
+ e.csize = get32(tmpbuf, EXTSIZ - EXTCRC);
+ e.size = get32(tmpbuf, EXTLEN - EXTCRC);
+ ((PushbackInputStream)in).unread(
+ tmpbuf, EXTHDR - EXTCRC, EXTCRC);
+ } else {
+ e.crc = get32(tmpbuf, EXTCRC);
+ e.csize = get32(tmpbuf, EXTSIZ);
+ e.size = get32(tmpbuf, EXTLEN);
+ }
+ }
+ }
+ if (e.size != inf.getBytesWritten()) {
+ throw new ZipException(
+ "invalid entry size (expected " + e.size +
+ " but got " + inf.getBytesWritten() + " bytes)");
+ }
+ if (e.csize != inf.getBytesRead()) {
+ throw new ZipException(
+ "invalid entry compressed size (expected " + e.csize +
+ " but got " + inf.getBytesRead() + " bytes)");
+ }
+ if (e.crc != crc.getValue()) {
+ throw new ZipException(
+ "invalid entry CRC (expected 0x" + Long.toHexString(e.crc) +
+ " but got 0x" + Long.toHexString(crc.getValue()) + ")");
+ }
+ }
+
+ /*
+ * Reads bytes, blocking until all bytes are read.
+ */
+ private void readFully(byte[] b, int off, int len) throws IOException {
+ while (len > 0) {
+ int n = in.read(b, off, len);
+ if (n == -1) {
+ throw new EOFException();
+ }
+ off += n;
+ len -= n;
+ }
+ }
+
+}