| /* |
| * 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 org.apache.harmony.archive.internal.nls.Messages; |
| |
| /** |
| * 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. |
| */ |
| 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 final static 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); |
| if (getShort(header, 0) != GZIP_MAGIC) { |
| throw new IOException(Messages.getString("archive.1F")); //$NON-NLS-1$; |
| } |
| 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 = getShort(header, 0); |
| 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); |
| int crc16 = getShort(header, 0); |
| if ((crc.getValue() & 0xffff) != crc16) { |
| throw new IOException(Messages.getString("archive.20")); //$NON-NLS-1$ |
| } |
| crc.reset(); |
| } |
| } |
| |
| /** |
| * Closes this stream and any underlying streams. |
| */ |
| @Override |
| public void close() throws IOException { |
| eos = true; |
| super.close(); |
| } |
| |
| private long getLong(byte[] buffer, int off) { |
| long l = 0; |
| l |= (buffer[off] & 0xFF); |
| l |= (buffer[off + 1] & 0xFF) << 8; |
| l |= (buffer[off + 2] & 0xFF) << 16; |
| l |= ((long) (buffer[off + 3] & 0xFF)) << 24; |
| return l; |
| } |
| |
| private int getShort(byte[] buffer, int off) { |
| return (buffer[off] & 0xFF) | ((buffer[off + 1] & 0xFF) << 8); |
| } |
| |
| /** |
| * Reads and decompresses GZIP data from the underlying stream into the |
| * given buffer. |
| * |
| * @param buffer |
| * Buffer to receive data |
| * @param off |
| * Offset in buffer to store data |
| * @param nbytes |
| * Number of bytes to read |
| */ |
| @Override |
| public int read(byte[] buffer, int off, int nbytes) throws IOException { |
| if (closed) { |
| throw new IOException(Messages.getString("archive.1E")); //$NON-NLS-1$ |
| } |
| // BEGIN android-changed |
| if (eos) { |
| return -1; |
| } |
| // avoid int overflow, check null buffer |
| if (off > buffer.length || nbytes < 0 || off < 0 |
| || buffer.length - off < nbytes) { |
| throw new ArrayIndexOutOfBoundsException(); |
| } |
| |
| int bytesRead; |
| try { |
| bytesRead = super.read(buffer, off, nbytes); |
| } finally { |
| eos = eof; // update eos after every read(), even when it throws |
| } |
| |
| if (bytesRead != -1) { |
| crc.update(buffer, off, bytesRead); |
| } |
| |
| if (eos) { |
| verifyCrc(); |
| } |
| |
| return bytesRead; |
| // END android-changed |
| } |
| |
| // BEGIN android-added |
| 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 (getLong(b, 0) != crc.getValue()) { |
| throw new IOException(Messages.getString("archive.20")); //$NON-NLS-1$ |
| } |
| if ((int) getLong(b, 4) != inf.getTotalOut()) { |
| throw new IOException(Messages.getString("archive.21")); //$NON-NLS-1$ |
| } |
| } |
| // END android-added |
| |
| 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); |
| } |
| } |
| } |