311 lines
10 KiB
Java
311 lines
10 KiB
Java
![]() |
/*
|
||
|
* Copyright (c) 1996, 2021, 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.SequenceInputStream;
|
||
|
import java.io.ByteArrayInputStream;
|
||
|
import java.io.FilterInputStream;
|
||
|
import java.io.InputStream;
|
||
|
import java.io.IOException;
|
||
|
import java.io.EOFException;
|
||
|
|
||
|
/**
|
||
|
* This class implements a stream filter for reading compressed data in
|
||
|
* the GZIP file format.
|
||
|
*
|
||
|
* @see InflaterInputStream
|
||
|
* @author David Connelly
|
||
|
* @since 1.1
|
||
|
*
|
||
|
*/
|
||
|
public class GZIPInputStream extends InflaterInputStream {
|
||
|
/**
|
||
|
* CRC-32 for uncompressed data.
|
||
|
*/
|
||
|
protected CRC32 crc = new CRC32();
|
||
|
|
||
|
/**
|
||
|
* Indicates end of input stream.
|
||
|
*/
|
||
|
protected boolean eos;
|
||
|
|
||
|
private boolean closed = false;
|
||
|
|
||
|
/**
|
||
|
* 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 input stream with the specified buffer size.
|
||
|
*
|
||
|
* Android-note: Android limits the number of UnbufferedIO operations that can be performed, so
|
||
|
* consider using buffered inputs with this class. More information can be found in the
|
||
|
* <a href="https://developer.android.com/reference/android/os/StrictMode.ThreadPolicy.Builder#detectUnbufferedIo()">
|
||
|
* UnbufferedIO</a> and
|
||
|
* <a href="https://developer.android.com/reference/android/os/StrictMode"> StrictMode</a>
|
||
|
* documentation.
|
||
|
*
|
||
|
* @param in the input stream
|
||
|
* @param size the input buffer size
|
||
|
*
|
||
|
* @throws ZipException if a GZIP format error has occurred or the
|
||
|
* compression method used is unsupported
|
||
|
* @throws IOException if an I/O error has occurred
|
||
|
* @throws IllegalArgumentException if {@code size <= 0}
|
||
|
*/
|
||
|
public GZIPInputStream(InputStream in, int size) throws IOException {
|
||
|
super(in, in != null ? new Inflater(true) : null, size);
|
||
|
// Android-removed: Unconditionally close external inflaters (b/26462400)
|
||
|
// usesDefaultInflater = true;
|
||
|
// BEGIN Android-changed: Do not rely on finalization to inf.end().
|
||
|
// readHeader(in);
|
||
|
try {
|
||
|
readHeader(in);
|
||
|
} catch (Exception e) {
|
||
|
inf.end();
|
||
|
throw e;
|
||
|
}
|
||
|
// END Android-changed: Do not rely on finalization to inf.end().
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates a new input stream with a default buffer size.
|
||
|
* @param in the input stream
|
||
|
*
|
||
|
* @throws ZipException if a GZIP format error has occurred or the
|
||
|
* compression method used is unsupported
|
||
|
* @throws IOException if an I/O error has occurred
|
||
|
*/
|
||
|
public GZIPInputStream(InputStream in) throws IOException {
|
||
|
this(in, 512);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Reads uncompressed data into an array of bytes. If {@code len} is not
|
||
|
* zero, the method will block until some input can be decompressed; otherwise,
|
||
|
* no bytes are read and {@code 0} is returned.
|
||
|
* @param buf the buffer into which the data is read
|
||
|
* @param off the start offset in the destination array {@code b}
|
||
|
* @param len the maximum number of bytes read
|
||
|
* @return the actual number of bytes read, or -1 if the end of the
|
||
|
* compressed input stream is reached
|
||
|
*
|
||
|
* @throws NullPointerException If {@code buf} is {@code null}.
|
||
|
* @throws IndexOutOfBoundsException If {@code off} is negative,
|
||
|
* {@code len} is negative, or {@code len} is greater than
|
||
|
* {@code buf.length - off}
|
||
|
* @throws ZipException if the compressed input data is corrupt.
|
||
|
* @throws IOException if an I/O error has occurred.
|
||
|
*
|
||
|
*/
|
||
|
public int read(byte[] buf, int off, int len) throws IOException {
|
||
|
ensureOpen();
|
||
|
if (eos) {
|
||
|
return -1;
|
||
|
}
|
||
|
int n = super.read(buf, off, len);
|
||
|
if (n == -1) {
|
||
|
if (readTrailer())
|
||
|
eos = true;
|
||
|
else
|
||
|
return this.read(buf, off, len);
|
||
|
} else {
|
||
|
crc.update(buf, off, n);
|
||
|
}
|
||
|
return n;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Closes this input stream and releases any system resources associated
|
||
|
* with the stream.
|
||
|
* @throws IOException if an I/O error has occurred
|
||
|
*/
|
||
|
public void close() throws IOException {
|
||
|
if (!closed) {
|
||
|
super.close();
|
||
|
eos = true;
|
||
|
closed = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* GZIP header magic number.
|
||
|
*/
|
||
|
public static final int GZIP_MAGIC = 0x8b1f;
|
||
|
|
||
|
/*
|
||
|
* File header flags.
|
||
|
*/
|
||
|
private static final int FTEXT = 1; // Extra text
|
||
|
private static final int FHCRC = 2; // Header CRC
|
||
|
private static final int FEXTRA = 4; // Extra field
|
||
|
private static final int FNAME = 8; // File name
|
||
|
private static final int FCOMMENT = 16; // File comment
|
||
|
|
||
|
/*
|
||
|
* Reads GZIP member header and returns the total byte number
|
||
|
* of this member header.
|
||
|
*/
|
||
|
private int readHeader(InputStream this_in) throws IOException {
|
||
|
CheckedInputStream in = new CheckedInputStream(this_in, crc);
|
||
|
crc.reset();
|
||
|
// Check header magic
|
||
|
if (readUShort(in) != GZIP_MAGIC) {
|
||
|
throw new ZipException("Not in GZIP format");
|
||
|
}
|
||
|
// Check compression method
|
||
|
if (readUByte(in) != 8) {
|
||
|
throw new ZipException("Unsupported compression method");
|
||
|
}
|
||
|
// Read flags
|
||
|
int flg = readUByte(in);
|
||
|
// Skip MTIME, XFL, and OS fields
|
||
|
skipBytes(in, 6);
|
||
|
int n = 2 + 2 + 6;
|
||
|
// Skip optional extra field
|
||
|
if ((flg & FEXTRA) == FEXTRA) {
|
||
|
int m = readUShort(in);
|
||
|
skipBytes(in, m);
|
||
|
n += m + 2;
|
||
|
}
|
||
|
// Skip optional file name
|
||
|
if ((flg & FNAME) == FNAME) {
|
||
|
do {
|
||
|
n++;
|
||
|
} while (readUByte(in) != 0);
|
||
|
}
|
||
|
// Skip optional file comment
|
||
|
if ((flg & FCOMMENT) == FCOMMENT) {
|
||
|
do {
|
||
|
n++;
|
||
|
} while (readUByte(in) != 0);
|
||
|
}
|
||
|
// Check optional header CRC
|
||
|
if ((flg & FHCRC) == FHCRC) {
|
||
|
int v = (int)crc.getValue() & 0xffff;
|
||
|
if (readUShort(in) != v) {
|
||
|
throw new ZipException("Corrupt GZIP header");
|
||
|
}
|
||
|
n += 2;
|
||
|
}
|
||
|
crc.reset();
|
||
|
return n;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Reads GZIP member trailer and returns true if the eos
|
||
|
* reached, false if there are more (concatenated gzip
|
||
|
* data set)
|
||
|
*/
|
||
|
private boolean readTrailer() throws IOException {
|
||
|
InputStream in = this.in;
|
||
|
int n = inf.getRemaining();
|
||
|
if (n > 0) {
|
||
|
in = new SequenceInputStream(
|
||
|
new ByteArrayInputStream(buf, len - n, n),
|
||
|
new FilterInputStream(in) {
|
||
|
public void close() throws IOException {}
|
||
|
});
|
||
|
}
|
||
|
// Uses left-to-right evaluation order
|
||
|
if ((readUInt(in) != crc.getValue()) ||
|
||
|
// rfc1952; ISIZE is the input size modulo 2^32
|
||
|
(readUInt(in) != (inf.getBytesWritten() & 0xffffffffL)))
|
||
|
throw new ZipException("Corrupt GZIP trailer");
|
||
|
|
||
|
// If there are more bytes available in "in" or
|
||
|
// the leftover in the "inf" is > 26 bytes:
|
||
|
// this.trailer(8) + next.header.min(10) + next.trailer(8)
|
||
|
// try concatenated case
|
||
|
if (this.in.available() > 0 || n > 26) {
|
||
|
int m = 8; // this.trailer
|
||
|
try {
|
||
|
m += readHeader(in); // next.header
|
||
|
} catch (IOException ze) {
|
||
|
return true; // ignore any malformed, do nothing
|
||
|
}
|
||
|
inf.reset();
|
||
|
if (n > m)
|
||
|
inf.setInput(buf, len - n + m, n - m);
|
||
|
return false;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Reads unsigned integer in Intel byte order.
|
||
|
*/
|
||
|
private long readUInt(InputStream in) throws IOException {
|
||
|
long s = readUShort(in);
|
||
|
return ((long)readUShort(in) << 16) | s;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Reads unsigned short in Intel byte order.
|
||
|
*/
|
||
|
private int readUShort(InputStream in) throws IOException {
|
||
|
int b = readUByte(in);
|
||
|
return (readUByte(in) << 8) | b;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Reads unsigned byte.
|
||
|
*/
|
||
|
private int readUByte(InputStream in) throws IOException {
|
||
|
int b = in.read();
|
||
|
if (b == -1) {
|
||
|
throw new EOFException();
|
||
|
}
|
||
|
if (b < -1 || b > 255) {
|
||
|
// Report on this.in, not argument in; see read{Header, Trailer}.
|
||
|
throw new IOException(this.in.getClass().getName()
|
||
|
+ ".read() returned value out of range -1..255: " + b);
|
||
|
}
|
||
|
return b;
|
||
|
}
|
||
|
|
||
|
private byte[] tmpbuf = new byte[128];
|
||
|
|
||
|
/*
|
||
|
* Skips bytes of input data blocking until all bytes are skipped.
|
||
|
* Does not assume that the input stream is capable of seeking.
|
||
|
*/
|
||
|
private void skipBytes(InputStream in, int n) throws IOException {
|
||
|
while (n > 0) {
|
||
|
int len = in.read(tmpbuf, 0, n < tmpbuf.length ? n : tmpbuf.length);
|
||
|
if (len == -1) {
|
||
|
throw new EOFException();
|
||
|
}
|
||
|
n -= len;
|
||
|
}
|
||
|
}
|
||
|
}
|