
net.dongliu.cute.http.internal.AsyncInflater Maven / Gradle / Ivy
The newest version!
package net.dongliu.cute.http.internal;
import net.dongliu.commons.io.Buffers;
import java.nio.ByteBuffer;
import java.util.function.Consumer;
import java.util.zip.CRC32;
import java.util.zip.DataFormatException;
import java.util.zip.Deflater;
import java.util.zip.Inflater;
import static java.lang.Byte.toUnsignedInt;
import static java.lang.Byte.toUnsignedLong;
/**
* Copy from netty JdkZlibDecoder.
*/
public class AsyncInflater {
private static final int FHCRC = 0x02;
private static final int FEXTRA = 0x04;
private static final int FNAME = 0x08;
private static final int FCOMMENT = 0x10;
private static final int FRESERVED = 0xE0;
private Inflater inflater;
// private final byte[] dictionary;
// GZIP related
private final CRC32 crc;
private enum GzipState {
HEADER_START,
HEADER_END,
FLG_READ,
XLEN_READ,
SKIP_FNAME,
SKIP_COMMENT,
PROCESS_FHCRC,
FOOTER_START,
}
private GzipState gzipState = GzipState.HEADER_START;
private int flags = -1;
private int xlen = -1;
private volatile boolean finished;
private boolean decideZlibOrNone;
public static final int GZIP = 1;
public static final int ZLIB = 2;
public AsyncInflater(int wrapper) {
switch (wrapper) {
case GZIP:
inflater = new Inflater(true);
crc = new CRC32();
break;
case ZLIB:
inflater = new Inflater();
crc = null;
break;
default:
throw new IllegalArgumentException("unknown inflate type:" + wrapper);
}
}
public boolean finished() {
return finished;
}
/**
* Decode piece of data.
*
* @param in the input data
* @param consumer notified when new decompressed ByteBuffer is filled.
*/
public void decode(ByteBuffer in, Consumer consumer) {
if (finished) {
// Skip data received after finished.
in.position(in.limit());
return;
}
if (in.remaining() <= 0) {
return;
}
if (decideZlibOrNone) {
// First two bytes are needed to decide if it's a ZLIB stream.
if (in.remaining() < 2) {
return;
}
boolean nowrap = !looksLikeZlib(in.getShort(0));
inflater = new Inflater(nowrap);
decideZlibOrNone = false;
}
if (crc != null) {
switch (gzipState) {
case FOOTER_START:
if (readGZIPFooter(in)) {
finished = true;
}
return;
default:
if (gzipState != GzipState.HEADER_END) {
if (!readGZIPHeader(in)) {
return;
}
}
}
}
int readableBytes = in.remaining();
if (in.hasArray()) {
inflater.setInput(in.array(), in.arrayOffset() + in.position(), in.remaining());
} else {
byte[] array = new byte[in.remaining()];
in.duplicate().get(array);
inflater.setInput(array);
}
int maxOutputLength = inflater.getRemaining() << 1;
ByteBuffer decompressed = ByteBuffer.allocate(maxOutputLength);
try {
boolean readFooter = false;
byte[] outArray = decompressed.array();
while (!inflater.needsInput()) {
int outIndex = decompressed.arrayOffset() + decompressed.position();
int length = outArray.length - outIndex;
if (length == 0) {
// completely filled the buffer allocate a new one and start to fill it
consumer.accept(decompressed.flip());
decompressed = ByteBuffer.allocate(maxOutputLength);
outArray = decompressed.array();
continue;
}
int outputLength = inflater.inflate(outArray, outIndex, length);
if (outputLength > 0) {
Buffers.advance(decompressed, outputLength);
if (crc != null) {
crc.update(outArray, outIndex, outputLength);
}
} else {
if (inflater.needsDictionary()) {
throw new RuntimeException("no dictionary was specified");
// inflater.setDictionary(dictionary);
}
}
if (inflater.finished()) {
if (crc == null) {
finished = true; // Do not decode anymore.
} else {
readFooter = true;
}
break;
}
}
Buffers.advance(in, readableBytes - inflater.getRemaining());
if (readFooter) {
gzipState = GzipState.FOOTER_START;
if (readGZIPFooter(in)) {
finished = true;
}
}
} catch (DataFormatException e) {
throw new RuntimeException("decompression failure", e);
} finally {
decompressed.flip();
if (decompressed.limit() > 0) {
consumer.accept(decompressed);
}
}
}
/**
* Called when input data finished.
*/
public void onFinish() {
if (inflater != null) {
inflater.end();
}
}
private boolean readGZIPHeader(ByteBuffer in) {
switch (gzipState) {
case HEADER_START:
if (in.remaining() < 10) {
return false;
}
// read magic numbers
int magic0 = in.get();
int magic1 = in.get();
if (magic0 != 31) {
throw new RuntimeException("Input is not in the GZIP format");
}
crc.update(magic0);
crc.update(magic1);
int method = toUnsignedInt(in.get());
if (method != Deflater.DEFLATED) {
throw new RuntimeException("Unsupported compression method "
+ method + " in the GZIP header");
}
crc.update(method);
flags = toUnsignedInt(in.get());
crc.update(flags);
if ((flags & FRESERVED) != 0) {
throw new RuntimeException(
"Reserved flags are set in the GZIP header");
}
// mtime (int)
crc.update(in.get());
crc.update(in.get());
crc.update(in.get());
crc.update(in.get());
crc.update(toUnsignedInt(in.get())); // extra flags
crc.update(toUnsignedInt(in.get())); // operating system
gzipState = GzipState.FLG_READ;
case FLG_READ:
if ((flags & FEXTRA) != 0) {
if (in.remaining() < 2) {
return false;
}
int xlen1 = toUnsignedInt(in.get());
int xlen2 = toUnsignedInt(in.get());
crc.update(xlen1);
crc.update(xlen2);
xlen |= xlen1 << 8 | xlen2;
}
gzipState = GzipState.XLEN_READ;
case XLEN_READ:
if (xlen != -1) {
if (in.remaining() < xlen) {
return false;
}
byte[] xtra = new byte[xlen];
in.get(xtra);
crc.update(xtra);
}
gzipState = GzipState.SKIP_FNAME;
case SKIP_FNAME:
if ((flags & FNAME) != 0) {
if (in.remaining() <= 0) {
return false;
}
do {
int b = toUnsignedInt(in.get());
crc.update(b);
if (b == 0x00) {
break;
}
} while (in.remaining() > 0);
}
gzipState = GzipState.SKIP_COMMENT;
case SKIP_COMMENT:
if ((flags & FCOMMENT) != 0) {
if (in.remaining() <= 0) {
return false;
}
do {
int b = toUnsignedInt(in.get());
crc.update(b);
if (b == 0x00) {
break;
}
} while (in.remaining() > 0);
}
gzipState = GzipState.PROCESS_FHCRC;
case PROCESS_FHCRC:
if ((flags & FHCRC) != 0) {
if (in.remaining() < 4) {
return false;
}
verifyCrc(in);
}
crc.reset();
gzipState = GzipState.HEADER_END;
case HEADER_END:
return true;
default:
throw new IllegalStateException();
}
}
private boolean readGZIPFooter(ByteBuffer buf) {
if (buf.remaining() < 8) {
return false;
}
verifyCrc(buf);
// read ISIZE and verify
int dataLength = 0;
for (int i = 0; i < 4; ++i) {
dataLength |= toUnsignedInt(buf.get()) << i * 8;
}
int readLength = inflater.getTotalOut();
if (dataLength != readLength) {
throw new RuntimeException(
"Number of bytes mismatch. Expected: " + dataLength + ", Got: " + readLength);
}
return true;
}
private void verifyCrc(ByteBuffer in) {
long crcValue = 0;
for (int i = 0; i < 4; ++i) {
crcValue |= toUnsignedLong(in.get()) << i * 8;
}
long readCrc = crc.getValue();
if (crcValue != readCrc) {
throw new RuntimeException("CRC value missmatch. Expected: " + crcValue + ", Got: " + readCrc);
}
}
/*
* Returns true if the cmf_flg parameter (think: first two bytes of a zlib stream)
* indicates that this is a zlib stream.
*
* You can lookup the details in the ZLIB RFC:
* RFC 1950.
*/
private static boolean looksLikeZlib(short cmf_flg) {
return (cmf_flg & 0x7800) == 0x7800 &&
cmf_flg % 31 == 0;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy