/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package java.util.zip; import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.nio.ByteOrder; import java.util.Arrays; import libcore.io.Memory; /** * The {@code GZIPInputStream} class is used to read data stored in the GZIP * format, reading and decompressing GZIP data from the underlying stream into * its buffer. * * <h3>Example</h3> * <p>Using {@code GZIPInputStream} is easier than {@link ZipInputStream} * because GZIP is only for compression, and is not a container for multiple files. * This code decompresses the data from a GZIP stream, similar to the {@code gunzip(1)} utility. * <pre> * InputStream is = ... * GZIPInputStream zis = new GZIPInputStream(new BufferedInputStream(is)); * try { * // Reading from 'zis' gets you the uncompressed bytes... * processStream(zis); * } finally { * zis.close(); * } * </pre> */ public class GZIPInputStream extends InflaterInputStream { private static final int FCOMMENT = 16; private static final int FEXTRA = 4; private static final int FHCRC = 2; private static final int FNAME = 8; /** * The magic header for the GZIP format. */ public static final int GZIP_MAGIC = 0x8b1f; /** * The checksum algorithm used when handling uncompressed data. */ protected CRC32 crc = new CRC32(); /** * Indicates the end of the input stream. */ protected boolean eos = false; /** * Construct a {@code GZIPInputStream} to read from GZIP data from the * underlying stream. * * @param is * the {@code InputStream} to read data from. * @throws IOException * if an {@code IOException} occurs. */ public GZIPInputStream(InputStream is) throws IOException { this(is, BUF_SIZE); } /** * Construct a {@code GZIPInputStream} to read from GZIP data from the * underlying stream. Set the internal buffer size to {@code size}. * * @param is * the {@code InputStream} to read data from. * @param size * the internal read buffer size. * @throws IOException * if an {@code IOException} occurs. */ public GZIPInputStream(InputStream is, int size) throws IOException { super(is, new Inflater(true), size); byte[] header = new byte[10]; readFully(header, 0, header.length); short magic = Memory.peekShort(header, 0, ByteOrder.LITTLE_ENDIAN); if (magic != (short) GZIP_MAGIC) { throw new IOException(String.format("unknown format (magic number %x)", magic)); } int flags = header[3]; boolean hcrc = (flags & FHCRC) != 0; if (hcrc) { crc.update(header, 0, header.length); } if ((flags & FEXTRA) != 0) { readFully(header, 0, 2); if (hcrc) { crc.update(header, 0, 2); } int length = Memory.peekShort(header, 0, ByteOrder.LITTLE_ENDIAN) & 0xffff; while (length > 0) { int max = length > buf.length ? buf.length : length; int result = in.read(buf, 0, max); if (result == -1) { throw new EOFException(); } if (hcrc) { crc.update(buf, 0, result); } length -= result; } } if ((flags & FNAME) != 0) { readZeroTerminated(hcrc); } if ((flags & FCOMMENT) != 0) { readZeroTerminated(hcrc); } if (hcrc) { readFully(header, 0, 2); short crc16 = Memory.peekShort(header, 0, ByteOrder.LITTLE_ENDIAN); if ((short) crc.getValue() != crc16) { throw new IOException("CRC mismatch"); } crc.reset(); } } /** * Closes this stream and any underlying streams. */ @Override public void close() throws IOException { eos = true; super.close(); } /** * Reads and decompresses GZIP data from the underlying stream into the * given buffer. */ @Override public int read(byte[] buffer, int offset, int byteCount) throws IOException { if (closed) { throw new IOException("Stream is closed"); } if (eos) { return -1; } Arrays.checkOffsetAndCount(buffer.length, offset, byteCount); int bytesRead; try { bytesRead = super.read(buffer, offset, byteCount); } finally { eos = eof; // update eos after every read(), even when it throws } if (bytesRead != -1) { crc.update(buffer, offset, bytesRead); } if (eos) { verifyCrc(); } return bytesRead; } private void verifyCrc() throws IOException { // Get non-compressed bytes read by fill int size = inf.getRemaining(); final int trailerSize = 8; // crc (4 bytes) + total out (4 bytes) byte[] b = new byte[trailerSize]; int copySize = (size > trailerSize) ? trailerSize : size; System.arraycopy(buf, len - size, b, 0, copySize); readFully(b, copySize, trailerSize - copySize); if (Memory.peekInt(b, 0, ByteOrder.LITTLE_ENDIAN) != (int) crc.getValue()) { throw new IOException("CRC mismatch"); } if (Memory.peekInt(b, 4, ByteOrder.LITTLE_ENDIAN) != inf.getTotalOut()) { throw new IOException("Size mismatch"); } } private void readFully(byte[] buffer, int offset, int length) throws IOException { int result; while (length > 0) { result = in.read(buffer, offset, length); if (result == -1) { throw new EOFException(); } offset += result; length -= result; } } private void readZeroTerminated(boolean hcrc) throws IOException { int result; while ((result = in.read()) > 0) { if (hcrc) { crc.update(result); } } if (result == -1) { throw new EOFException(); } // Add the zero if (hcrc) { crc.update(result); } } }