/*
* JLibs: Common Utilities for Java
* Copyright (C) 2009 Santhosh Kumar T <santhosh.tekuri@gmail.com>
*
* 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 2.1 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 jlibs.nio.filters;
import jlibs.nio.Input;
import jlibs.nio.Reactor;
import java.io.EOFException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.zip.*;
/**
* @author Santhosh Kumar Tekuri
*/
public class GZIPInput extends InflaterInput{
public GZIPInput(Input peer){
super(new Inflater(true), peer);
}
private static final int STATE_GZIP_MAGIC = 0;
private static final int STATE_COMPRESSION_METHOD = 1;
private static final int STATE_FLAG = 2;
private static final int STATE_MTIME_XFL_OS = 3;
private static final int STATE_READ_EXTRA = 4;
private static final int STATE_SKIP_EXTRA = 5;
private static final int STATE_SKIP_FNAME = 6;
private static final int STATE_SKIP_FCOMMENT = 7;
private static final int STATE_SKIP_FHCRC = 8;
private static final int STATE_CONTENT = 9;
private static final int STATE_TRAILER = 10;
private static final int STATE_FINISHED = 11;
private int state = STATE_GZIP_MAGIC;
private CRC32 crc = new CRC32();
@Override
protected int inflate(byte[] bytes, int offset, int len) throws DataFormatException{
int uncompressed = super.inflate(bytes, offset, len);
crc.update(bytes, offset, uncompressed);
return uncompressed;
}
@Override
public int read(ByteBuffer dst) throws IOException{
if(state==STATE_CONTENT){
int pos = dst.position();
int read = super.read(dst);
if(read==-1){
eof = false;
state = STATE_TRAILER;
readTrailer();
}else
return read;
}
if(state==STATE_FINISHED){
eof = true;
return -1;
}
int read = peer.read(buffer);
if(read==0)
return 0;
if(read==-1)
throw new EOFException();
switch(state){
case STATE_GZIP_MAGIC:
if(canRead(USHORT)){
if(readUShort()!= GZIPInputStream.GZIP_MAGIC)
throw new ZipException("Not in GZIP format");
state++;
}else
return 0;
case STATE_COMPRESSION_METHOD:
if(canRead(UBYTE)){
if(readUByte()!= Deflater.DEFLATED)
throw new ZipException("Unsupported compression method");
state++;
}else
return 0;
case STATE_FLAG:
if(canRead(UBYTE)){
flag = readUByte();
state++;
}else
return 0;
case STATE_MTIME_XFL_OS:
if(canRead(6)){
readPos += 6;
state++;
}else
return 0;
case STATE_READ_EXTRA:
if((flag&FEXTRA)==FEXTRA){
if(canRead(USHORT)){
extraLen = readUShort();
state++;
}else
return 0;
}else
state++;
case STATE_SKIP_EXTRA:
if((flag&FEXTRA)==FEXTRA){
if(canRead(extraLen)){
readPos += extraLen;
state++;
}else
return 0;
}else
state++;
case STATE_SKIP_FNAME:
if((flag&FNAME)==FNAME){
while(canRead(UBYTE)){
if(readUByte()==0){
state++;
break;
}
}
if(state==STATE_SKIP_FNAME)
return 0;
}else
state++;
case STATE_SKIP_FCOMMENT:
if((flag&FCOMMENT)==FCOMMENT){
while(canRead(UBYTE)){
if(readUByte()==0){
state++;
break;
}
}
if(state==STATE_SKIP_FCOMMENT)
return 0;
}else
state++;
case STATE_SKIP_FHCRC:
if((flag&FHCRC)==FHCRC){
if(canRead(USHORT)){
crc.update(buffer.array(), 0, readPos);
int crcValue = (int)crc.getValue() & 0xffff;
if(crcValue!=readUShort())
throw new ZipException("Corrupt GZIP header");
crc.reset();
state++;
}else
return 0;
}else
state++;
if(readPos<buffer.position())
inflater.setInput(buffer.array(), readPos, buffer.position()-readPos);
return read(dst);
default:
assert state==STATE_TRAILER;
readTrailer();
return state==STATE_FINISHED ? -1 : 0;
}
}
private long isize;
@Override
protected void endInflater(){
isize = inflater.getBytesWritten() & 0xffffffffL; // rfc1952; ISIZE is the input size modulo 2^32
if(inflater.getRemaining()>0){
if(inflater.getRemaining()+buffer.remaining()>=4)
readPos = buffer.position()-inflater.getRemaining();
else{
buffer.limit(buffer.position());
buffer.position(buffer.position()-inflater.getRemaining());
buffer.compact();
readPos = 0;
}
}else{
buffer.clear();
readPos = 0;
}
inflater.end();
inflater = null;
}
private void readTrailer() throws IOException{
if(canRead(UINT+UINT)){
if(readUInt()!=crc.getValue() || readUInt()!=isize)
throw new ZipException("Corrupt GZIP trailer");
if(readPos<buffer.position()){
buffer.limit(buffer.position());
buffer.position(readPos);
}else{
Reactor.current().allocator.free(buffer);
buffer = null;
}
buffer = null;
state = STATE_FINISHED;
}
}
@Override
protected ByteBuffer detached(){
if(state!=STATE_FINISHED){
Reactor.current().allocator.free(buffer);
buffer = null;
}
return buffer;
}
@Override
protected boolean readReady(){
return state==STATE_FINISHED ||
(state==STATE_CONTENT && !inflater.needsInput());
}
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
private static final int UBYTE = 1;
private static final int USHORT = UBYTE+UBYTE;
private static final int UINT = USHORT+USHORT;
private int readPos = 0;
private int flag;
private int extraLen = -1;
private boolean canRead(int size){
return buffer.position()-readPos>=size;
}
private int readUByte() throws IOException{
int b = buffer.get(readPos++) & 0xff;
if(b<-1 || b>255)
throw new ZipException("value out of range -1..255: " + b);
return b;
}
private int readUShort() throws IOException{
int b = readUByte();
return (readUByte()<<8)|b;
}
private long readUInt() throws IOException{
long s = readUShort();
return ((long)readUShort()<<16)|s;
}
}