package org.glassfish.grizzly.compression.zip;
import java.nio.ByteBuffer;
import java.util.zip.CRC32;
import java.util.zip.DataFormatException;
import java.util.zip.Inflater;
import org.glassfish.grizzly.AbstractTransformer;
import org.glassfish.grizzly.Buffer;
import org.glassfish.grizzly.TransformationException;
import org.glassfish.grizzly.TransformationResult;
import org.glassfish.grizzly.attributes.AttributeStorage;
import org.glassfish.grizzly.memory.Buffers;
import org.glassfish.grizzly.memory.ByteBufferArray;
import org.glassfish.grizzly.memory.MemoryManager;
public class GZipDecoder extends AbstractTransformer<Buffer, Buffer> {
protected enum DecodeStatus {
INITIAL, FEXTRA1, FEXTRA2, FNAME, FCOMMENT, FHCRC, PAYLOAD, TRAILER, DONE
}
private static final int GZIP_MAGIC = 0x8b1f;
private final static int FTEXT = 1;
private final static int FHCRC = 2;
private final static int = 4;
private final static int FNAME = 8;
private final static int = 16;
private final int bufferSize;
public GZipDecoder() {
this(512);
}
public GZipDecoder(int bufferSize) {
this.bufferSize = bufferSize;
}
@Override
public String getName() {
return "gzip-decoder";
}
@Override
public boolean hasInputRemaining(AttributeStorage storage, Buffer input) {
return input.hasRemaining();
}
@Override
protected GZipInputState createStateObject() {
return new GZipInputState();
}
@Override
protected TransformationResult<Buffer, Buffer> transformImpl(
AttributeStorage storage, Buffer input) throws TransformationException {
final MemoryManager memoryManager = obtainMemoryManager(storage);
final GZipInputState state = (GZipInputState) obtainStateObject(storage);
if (!state.isInitialized()) {
if (!initializeInput(input, state)) {
return TransformationResult.createIncompletedResult(input);
}
}
Buffer decodedBuffer = null;
if (state.getDecodeStatus() == DecodeStatus.PAYLOAD) {
if (input.hasRemaining()) {
decodedBuffer = decodeBuffer(memoryManager, input, state);
}
}
if (state.getDecodeStatus() == DecodeStatus.TRAILER && input.hasRemaining()) {
if (decodeTrailer(input, state)) {
state.setDecodeStatus(DecodeStatus.DONE);
state.setInitialized(false);
}
}
final boolean hasRemainder = input.hasRemaining();
if (decodedBuffer == null || !decodedBuffer.hasRemaining()) {
return TransformationResult.createIncompletedResult(hasRemainder ? input : null);
}
return TransformationResult.createCompletedResult(decodedBuffer,
hasRemainder ? input : null);
}
private Buffer decodeBuffer(MemoryManager memoryManager, Buffer buffer,
GZipInputState state) {
final Inflater inflater = state.getInflater();
final CRC32 inCrc32 = state.getCrc32();
final ByteBufferArray byteBufferArray = buffer.toByteBufferArray();
final ByteBuffer[] byteBuffers = byteBufferArray.getArray();
final int size = byteBufferArray.size();
Buffer resultBuffer = null;
for (int i = 0; i < size; i++) {
final ByteBuffer byteBuffer = byteBuffers[i];
final int len = byteBuffer.remaining();
final byte[] array;
final int offset;
if (byteBuffer.hasArray()) {
array = byteBuffer.array();
offset = byteBuffer.arrayOffset() + byteBuffer.position();
} else {
array = new byte[len];
offset = 0;
byteBuffer.get(array);
byteBuffer.position(byteBuffer.position() - len);
}
inflater.setInput(array, offset, len);
int lastInflated;
do {
final Buffer decodedBuffer = memoryManager.allocate(bufferSize);
final ByteBuffer decodedBB = decodedBuffer.toByteBuffer();
final byte[] decodedArray = decodedBB.array();
final int decodedArrayOffs = decodedBB.arrayOffset() + decodedBB.position();
try {
lastInflated = inflater.inflate(decodedArray, decodedArrayOffs, bufferSize);
} catch (DataFormatException e) {
decodedBuffer.dispose();
String s = e.getMessage();
throw new IllegalStateException(s != null ? s : "Invalid ZLIB data format");
}
if (lastInflated > 0) {
inCrc32.update(decodedArray, decodedArrayOffs, lastInflated);
decodedBuffer.position(lastInflated);
decodedBuffer.trim();
resultBuffer = Buffers.appendBuffers(memoryManager,
resultBuffer, decodedBuffer);
} else {
decodedBuffer.dispose();
if (inflater.finished() || inflater.needsDictionary()) {
final int remainder = inflater.getRemaining();
final int remaining = byteBuffer.remaining();
byteBufferArray.restore();
byteBufferArray.recycle();
buffer.position(
buffer.position() + remaining - remainder);
state.setDecodeStatus(DecodeStatus.TRAILER);
return resultBuffer;
}
}
} while (lastInflated > 0);
final int remaining = byteBuffer.remaining();
byteBufferArray.restore();
byteBufferArray.recycle();
buffer.position(buffer.position() + remaining);
}
return resultBuffer;
}
private boolean initializeInput(final Buffer buffer,
final GZipInputState state) {
Inflater inflater = state.getInflater();
if (inflater == null) {
inflater = new Inflater(true);
final CRC32 crc32 = new CRC32();
crc32.reset();
state.setInflater(inflater);
state.setCrc32(crc32);
} else if (state.getDecodeStatus() == DecodeStatus.DONE) {
state.setDecodeStatus(DecodeStatus.INITIAL);
inflater.reset();
state.getCrc32().reset();
}
if (!parseHeader(buffer, state)) {
return false;
}
state.getCrc32().reset();
state.setInitialized(true);
return true;
}
private boolean (Buffer buffer, GZipInputState state) {
final CRC32 crc32 = state.getCrc32();
DecodeStatus decodeStatus;
while((decodeStatus = state.getDecodeStatus()) != DecodeStatus.PAYLOAD) {
switch (decodeStatus) {
case INITIAL: {
if (buffer.remaining() < 10) {
return false;
}
if (getUShort(buffer, crc32) != GZIP_MAGIC) {
throw new IllegalStateException("Not in GZIP format");
}
if (getUByte(buffer, crc32) != 8) {
throw new IllegalStateException("Unsupported compression method");
}
final int flg = getUByte(buffer, crc32);
state.setHeaderFlag(flg);
skipBytes(buffer, 6, crc32);
state.setDecodeStatus(DecodeStatus.FEXTRA1);
}
case FEXTRA1: {
if ((state.getHeaderFlag() & FEXTRA) != FEXTRA) {
state.setDecodeStatus(DecodeStatus.FNAME);
break;
}
if (buffer.remaining() < 2) {
return false;
}
state.setHeaderParseStateValue(getUShort(buffer, crc32));
state.setDecodeStatus(DecodeStatus.FEXTRA2);
}
case FEXTRA2: {
final int fextraSize = state.getHeaderParseStateValue();
if (buffer.remaining() < fextraSize) {
return false;
}
skipBytes(buffer, fextraSize, crc32);
state.setHeaderParseStateValue(0);
state.setDecodeStatus(DecodeStatus.FNAME);
}
case FNAME: {
if ((state.getHeaderFlag() & FNAME) == FNAME) {
boolean found = false;
while (buffer.hasRemaining()) {
if (getUByte(buffer, crc32) == 0) {
found = true;
break;
}
}
if (!found) return false;
}
state.setDecodeStatus(DecodeStatus.FCOMMENT);
}
case FCOMMENT: {
if ((state.getHeaderFlag() & FCOMMENT) == FCOMMENT) {
boolean found = false;
while (buffer.hasRemaining()) {
if (getUByte(buffer, crc32) == 0) {
found = true;
break;
}
}
if (!found) return false;
}
state.setDecodeStatus(DecodeStatus.FHCRC);
}
case FHCRC: {
if ((state.getHeaderFlag() & FHCRC) == FHCRC) {
if (buffer.remaining() < 2) {
return false;
}
final int myCrc = (int) state.getCrc32().getValue() & 0xffff;
final int passedCrc = getUShort(buffer, crc32);
if (myCrc != passedCrc) {
throw new IllegalStateException("Corrupt GZIP header");
}
}
state.setDecodeStatus(DecodeStatus.PAYLOAD);
}
}
}
return true;
}
private boolean decodeTrailer(Buffer buffer, GZipInputState state)
throws TransformationException {
if (buffer.remaining() < 8) {
return false;
}
final Inflater inflater = state.getInflater();
final CRC32 crc32 = state.getCrc32();
final long inCrc32Value = crc32.getValue();
if ((getUInt(buffer, crc32) != inCrc32Value) ||
(getUInt(buffer, crc32) != (inflater.getBytesWritten() & 0xffffffffL)))
throw new TransformationException("Corrupt GZIP trailer");
return true;
}
private static long getUInt(Buffer buffer, CRC32 crc32) {
final int short1 = getUShort(buffer, crc32);
final int short2 = getUShort(buffer, crc32);
return (((long) short2 << 16) | short1);
}
private static int getUShort(Buffer buffer, CRC32 crc32) {
final int b1 = getUByte(buffer, crc32);
final int b2 = getUByte(buffer, crc32);
return (b2 << 8) | b1;
}
private static int getUByte(Buffer buffer, CRC32 crc32) {
final byte b = buffer.get();
crc32.update(b);
return b & 0xff;
}
private static void skipBytes(Buffer buffer, int num, CRC32 crc32) {
for (int i=0; i<num; i++) {
getUByte(buffer, crc32);
}
}
protected static final class GZipInputState
extends LastResultAwareState<Buffer, Buffer> {
private boolean isInitialized;
private CRC32 crc32;
private Inflater inflater;
private DecodeStatus decodeStatus = DecodeStatus.INITIAL;
private int ;
private int ;
public boolean isInitialized() {
return isInitialized;
}
public void setInitialized(boolean isInitialized) {
this.isInitialized = isInitialized;
}
public Inflater getInflater() {
return inflater;
}
public void setInflater(Inflater inflater) {
this.inflater = inflater;
}
public CRC32 getCrc32() {
return crc32;
}
public void setCrc32(CRC32 crc32) {
this.crc32 = crc32;
}
public DecodeStatus getDecodeStatus() {
return decodeStatus;
}
public void setDecodeStatus(DecodeStatus decodeStatus) {
this.decodeStatus = decodeStatus;
}
public int () {
return headerFlag;
}
public void (int headerFlag) {
this.headerFlag = headerFlag;
}
public int () {
return headerParseStateValue;
}
public void (int headerParseStateValue) {
this.headerParseStateValue = headerParseStateValue;
}
}
}