// package net.sf.zipme; import java.io.EOFException; import java.io.IOException; import java.io.InputStream; /** * This filter stream is used to decompress a "GZIP" format stream. * The "GZIP" format is described in RFC 1952. * @author John Leuner * @author Tom Tromey * @since JDK 1.1 */ public class GZIPInputStream extends InflaterInputStream { /** * The magic number found at the start of a GZIP stream. */ public static final int GZIP_MAGIC=0x8b1f; /** * The mask for bit 0 of the flag byte. */ static final int FTEXT=0x1; /** * The mask for bit 1 of the flag byte. */ static final int FHCRC=0x2; /** * The mask for bit 2 of the flag byte. */ static final int FEXTRA=0x4; /** * The mask for bit 3 of the flag byte. */ static final int FNAME=0x8; /** * The mask for bit 4 of the flag byte. */ static final int FCOMMENT=0x10; /** * The CRC-32 checksum value for uncompressed data. */ protected CRC32 crc; /** * Indicates whether or not the end of the stream has been reached. */ protected boolean eos; /** * Indicates whether or not the GZIP header has been read in. */ private boolean readGZIPHeader; /** * Creates a GZIPInputStream with the default buffer size. * @param in The stream to read compressed data from * (in GZIP format). * @throws IOException if an error occurs during an I/O operation. */ public GZIPInputStream( InputStream in) throws IOException { this(in,4096); } /** * Creates a GZIPInputStream with the specified buffer size. * @param in The stream to read compressed data from * (in GZIP format). * @param size The size of the buffer to use. * @throws IOException if an error occurs during an I/O operation. * @throws IllegalArgumentException if <code>size</code> * is less than or equal to 0. */ public GZIPInputStream( InputStream in, int size) throws IOException { super(in,new Inflater(true),size); hook(); readHeader(); } public void hook(){ } /** * Closes the input stream. * @throws IOException if an error occurs during an I/O operation. */ public void close() throws IOException { super.close(); } /** * Reads in GZIP-compressed data and stores it in uncompressed form * into an array of bytes. The method will block until either * enough input data becomes available or the compressed stream * reaches its end. * @param buf the buffer into which the uncompressed data will * be stored. * @param offset the offset indicating where in <code>buf</code> * the uncompressed data should be placed. * @param len the number of uncompressed bytes to be read. */ public int read( byte[] buf, int offset, int len) throws IOException { if (!readGZIPHeader) readHeader(); if (eos) return -1; int numRead=super.read(buf,offset,len); this.hook30(buf,offset,numRead); if (inf.finished()) readFooter(); return numRead; } /** * Reads in the GZIP header. */ private void readHeader() throws IOException { hook1(); int magic=in.read(); if (magic < 0) { eos=true; return; } int magic2=in.read(); if ((magic + (magic2 << 8)) != GZIP_MAGIC) throw new IOException("Error in GZIP header, bad magic code"); hook2(magic); hook2(magic2); int CM=in.read(); if (CM != Deflater.DEFLATED) throw new IOException("Error in GZIP header, data not in deflate format"); hook2(CM); int flags=in.read(); if (flags < 0) throw new EOFException("Early EOF in GZIP header"); hook2(flags); if ((flags & 0xd0) != 0) throw new IOException("Reserved flag bits in GZIP header != 0"); for (int i=0; i < 6; i++) { int readByte=in.read(); if (readByte < 0) throw new EOFException("Early EOF in GZIP header"); hook2(readByte); } if ((flags & FEXTRA) != 0) { for (int i=0; i < 2; i++) { int readByte=in.read(); if (readByte < 0) throw new EOFException("Early EOF in GZIP header"); hook2(readByte); } if (in.read() < 0 || in.read() < 0) throw new EOFException("Early EOF in GZIP header"); int len1, len2, extraLen; len1=in.read(); len2=in.read(); if ((len1 < 0) || (len2 < 0)) throw new EOFException("Early EOF in GZIP header"); hook2(len1); hook2(len2); extraLen=(len1 << 8) | len2; for (int i=0; i < extraLen; i++) { int readByte=in.read(); if (readByte < 0) throw new EOFException("Early EOF in GZIP header"); hook2(readByte); } } if ((flags & FNAME) != 0) { int readByte; while ((readByte=in.read()) > 0) hook2(readByte); if (readByte < 0) throw new EOFException("Early EOF in GZIP file name"); hook2(readByte); } if ((flags & FCOMMENT) != 0) { int readByte; while ((readByte=in.read()) > 0) hook2(readByte); if (readByte < 0) throw new EOFException("Early EOF in GZIP comment"); hook2(readByte); } if ((flags & FHCRC) != 0) { int tempByte; int crcval=in.read(); if (crcval < 0) throw new EOFException("Early EOF in GZIP header"); tempByte=in.read(); if (tempByte < 0) throw new EOFException("Early EOF in GZIP header"); crcval=(crcval << 8) | tempByte; hook3(crcval); } readGZIPHeader=true; } public void hook3( int crcval) throws IOException { } public void hook2( int CM){ } private void hook1(){ } private void readFooter() throws IOException { byte[] footer=new byte[8]; int avail=inf.getRemaining(); if (avail > 8) avail=8; System.arraycopy(buf,len - inf.getRemaining(),footer,0,avail); int needed=8 - avail; while (needed > 0) { int count=in.read(footer,8 - needed,needed); if (count <= 0) throw new EOFException("Early EOF in GZIP footer"); needed-=count; } int crcval=(footer[0] & 0xff) | ((footer[1] & 0xff) << 8) | ((footer[2] & 0xff) << 16)| (footer[3] << 24); hook4(crcval); int total=(footer[4] & 0xff) | ((footer[5] & 0xff) << 8) | ((footer[6] & 0xff) << 16)| (footer[7] << 24); if (total != inf.getTotalOut()) throw new IOException("Number of bytes mismatch"); eos=true; } public void hook4( int crcval) throws IOException { } protected void hook30( byte[] buf, int offset, int numRead) throws IOException { } }