/*
* Copyright (c) 1996, 2015, Oracle and/or its affiliates. All rights reserved.
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
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
*/
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.
*/
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 - 1,
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 - 1,
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;
}
}
}