/*
* ImageI/O-Ext - OpenSource Java Image translation Library
* http://www.geo-solutions.it/
* http://java.net/projects/imageio-ext/
* (C) 2007 - 2009, GeoSolutions
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* either version 3 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*/
package it.geosolutions.imageio.stream.input.compressed;
import it.geosolutions.io.input.adapter.InputStreamAdapter;
import java.io.ByteArrayInputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.SequenceInputStream;
import java.util.zip.CRC32;
import java.util.zip.CheckedInputStream;
import java.util.zip.Inflater;
import javax.imageio.stream.ImageInputStream;
/**
* This class implements a stream filter for reading compressed data in the GZIP
* format.
*
* @author Simone Giannecchini, GeoSolutions
*
*/
public class GZIPImageInputStream extends InflaterImageInputStream {
private byte[] tmpbuf = new byte[128];
/**
* GZIP header magic number.
*/
public final static int GZIP_MAGIC = 0x8b1f;
/**
* File header flags.
*/
private final static int FHCRC = 2; // Header CRC
private final static int FEXTRA = 4; // Extra field
private final static int FNAME = 8; // File name
private final static int FCOMMENT = 16; // File comment
/**
* CRC-32 for uncompressed data.
*/
protected CRC32 crc = new CRC32();
/**
* Indicates end of input stream.
*/
protected boolean eos;
private boolean closed = false;
/**
* Creates a new input stream with the specified buffer size.
*
* @param in
* the input stream
* @param size
* the input buffer size
* @exception IOException
* if an I/O error has occurred
* @exception IllegalArgumentException
* if size is <= 0
*/
public GZIPImageInputStream(ImageInputStream iis) throws IOException {
super(iis, new Inflater(true), 8192);
usesDefaultInflater = true;
readHeader();
crc.reset();
}
/**
* Reads GZIP member header.
*/
private void readHeader() throws IOException {
CheckedInputStream in = new CheckedInputStream(new InputStreamAdapter(
this.iis), crc);
crc.reset();
// Check header magic
if (readUShort(in) != GZIP_MAGIC) {
throw new IOException("Not in GZIP format");
}
// Check compression method
if (readUByte(in) != 8) {
throw new IOException("Unsupported compression method");
}
// Read flags
int flg = readUByte(in);
// Skip MTIME, XFL, and OS fields
skipBytes(in, 6);
// Skip optional extra field
if ((flg & FEXTRA) == FEXTRA) {
skipBytes(in, readUShort(in));
}
// Skip optional eraf name
if ((flg & FNAME) == FNAME) {
while (readUByte(in) != 0)
;
}
// Skip optional eraf comment
if ((flg & FCOMMENT) == FCOMMENT) {
while (readUByte(in) != 0)
;
}
// Check optional header CRC
if ((flg & FHCRC) == FHCRC) {
int v = (int) crc.getValue() & 0xffff;
if (readUShort(in) != v) {
throw new IOException("Corrupt GZIP header");
}
}
}
/**
* Reads GZIP member trailer.
*/
private void readTrailer() throws IOException {
InputStream in = new InputStreamAdapter(this.iis);
int n = inf.getRemaining();
if (n > 0) {
in = new SequenceInputStream(new ByteArrayInputStream(buf, len - n,
n), in);
}
// Uses left-to-right evaluation order
if ((readUInt(in) != crc.getValue()) ||
// rfc1952; ISIZE is the input size modulo 2^32
// TODO: improve this test (getBytesWritten() method from 1.5
// should be preferred)
(readUInt(in) != (inf.getTotalOut() & 0xffffffffL)))
throw new IOException("Corrupt GZIP trailer");
}
/**
* Reads uncompressed data into an array of bytes. Blocks until enough input
* is available for decompression.
*
* @param buf
* the buffer into which the data is read
* @param off
* the start offset of the data
* @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
* @exception IOException
* if an I/O error has occurred or the compressed input
* data is corrupt
*/
public int read(byte[] buf, int off, int len) throws IOException {
checkClosed();
if (eos) {
return -1;
}
len = super.read(buf, off, len);
if (len == -1) {
readTrailer();
eos = true;
} else {
crc.update(buf, off, len);
}
return len;
}
/**
* 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();
eos = true;
closed = true;
}
}
/**
* Reads unsigned integer in Intel byte order.
*/
private long readUInt(InputStream in) throws IOException {
long a = readUShort(in);
long b = readUShort(in);
return ((b & 0xffff) << 16) | (a & 0xffff);
}
/**
* Reads unsigned short in Intel byte order.
*/
private int readUShort(InputStream in) throws IOException {
int a = readUByte(in);
int b = readUByte(in);
return ((b & 0xff) << 8) | (a & 0xff);
}
/**
* Reads unsigned byte.
*/
private int readUByte(InputStream in) throws IOException {
int b = in.read();
if (b == -1) {
throw new EOFException();
}
return b & 0xff;
}
/**
* 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;
}
}
}