blob: 4c865f05f5fe35f7567d6084c6b3de74db3b89d8 [file] [log] [blame]
part of archive;
class Inflate {
dynamic input;
final dynamic output;
Inflate(List<int> bytes, [int uncompressedSize]) :
input = new InputStream(bytes),
output = new OutputStream(size: uncompressedSize) {
_inflate();
}
Inflate.buffer(this.input, [int uncompressedSize]) :
output = new OutputStream(size: uncompressedSize) {
_inflate();
}
Inflate.stream([this.input, dynamic output_stream]) :
output = output_stream != null ? output_stream : new OutputStream() {
if (input == null || input is List<int>) {
_bitBuffer = 0;
_bitBufferLen = 0;
if (input != null) {
input = new InputStream(input);
}
return;
}
_inflate();
}
void streamInput(List<int> bytes) {
if (input != null && input is InputStream) {
input.offset = _blockPos;
}
int inputLen = input == null ? 0 : input.length;
int newLen = inputLen + bytes.length;
if (newLen < 0) {
print(newLen);
}
Uint8List newBytes = new Uint8List(newLen);
if (input != null) {
newBytes.setRange(0, input.length, input.buffer, input.offset);
}
newBytes.setRange(inputLen, newLen, bytes, 0);
input = new InputStream(newBytes);
}
List<int> inflateNext() {
_bitBuffer = 0;
_bitBufferLen = 0;
if (output is OutputStream) {
output.clear();
}
if (input == null || input.isEOS) {
return null;
}
try {
if (input is InputStream) {
_blockPos = input.offset;
}
_parseBlock();
// If it didn't finish reading the block, it will have thrown an exception
_blockPos = 0;
} catch (e) {
return null;
}
if (output is OutputStream) {
return output.getBytes();
}
return null;
}
/**
* Get the decompressed data.
*/
List<int> getBytes() {
return output.getBytes();
}
void _inflate() {
_bitBuffer = 0;
_bitBufferLen = 0;
while (!input.isEOS) {
bool more = _parseBlock();
if (!more) {
break;
}
}
}
/**
* Parse deflated block. Returns true if there is more to read, false
* if we're done.
*/
bool _parseBlock() {
if (input.isEOS) {
return false;
}
// Each block has a 3-bit header
int hdr = _readBits(3);
// BFINAL (is this the final block)?
bool bfinal = (hdr & 0x1) != 0;
// BTYPE (the type of block)
int btype = hdr >> 1;
switch (btype) {
case _BLOCK_UNCOMPRESSED:
_parseUncompressedBlock();
break;
case _BLOCK_FIXED_HUFFMAN:
_parseFixedHuffmanBlock();
break;
case _BLOCK_DYNAMIC_HUFFMAN:
_parseDynamicHuffmanBlock();
break;
default:
// reserved or other
throw new ArchiveException('unknown BTYPE: $btype');
}
// Continue while not the final block
return !bfinal;
}
/**
* Read a number of bits from the input stream.
*/
int _readBits(int length) {
if (length == 0) {
return 0;
}
// not enough buffer
while (_bitBufferLen < length) {
if (input.isEOS) {
throw new ArchiveException('input buffer is broken');
}
// input byte
int octet = input.readByte();
// concat octet
_bitBuffer |= octet << _bitBufferLen;
_bitBufferLen += 8;
}
// output byte
int octet = _bitBuffer & ((1 << length) - 1);
_bitBuffer >>= length;
_bitBufferLen -= length;
return octet;
}
/**
* Read huffman code using [table].
*/
int _readCodeByTable(HuffmanTable table) {
List<int> codeTable = table.table;
int maxCodeLength = table.maxCodeLength;
// Not enough buffer
while (_bitBufferLen < maxCodeLength) {
if (input.isEOS) {
break;
}
int octet = input.readByte();
_bitBuffer |= octet << _bitBufferLen;
_bitBufferLen += 8;
}
// read max length
int codeWithLength = codeTable[_bitBuffer & ((1 << maxCodeLength) - 1)];
int codeLength = codeWithLength >> 16;
_bitBuffer >>= codeLength;
_bitBufferLen -= codeLength;
return codeWithLength & 0xffff;
}
/**
* Parse uncompressed block.
*/
void _parseUncompressedBlock() {
// skip buffered header bits
_bitBuffer = 0;
_bitBufferLen = 0;
int len = _readBits(16);
int nlen = _readBits(16) ^ 0xffff;
// Make sure the block size checksum is valid.
if (len != 0 && len != nlen) {
throw new ArchiveException('Invalid uncompressed block header');
}
// check size
if (len > input.length) {
throw new ArchiveException('Input buffer is broken');
}
output.writeInputStream(input.readBytes(len));
}
/**
* Parse a fixed huffman block.
*/
void _parseFixedHuffmanBlock() {
_decodeHuffman(_fixedLiteralLengthTable, _fixedDistanceTable);
}
/**
* Parse a dynamic huffman block. Most blocks will be of this type.
*/
void _parseDynamicHuffmanBlock() {
// number of literal and length codes.
int numLitLengthCodes = _readBits(5) + 257;
// number of distance codes.
int numDistanceCodes = _readBits(5) + 1;
// number of code lengths.
int numCodeLengths = _readBits(4) + 4;
// decode code lengths
Uint8List codeLengths = new Uint8List(_ORDER.length);
for (int i = 0; i < numCodeLengths; ++i) {
codeLengths[_ORDER[i]] = _readBits(3);
}
HuffmanTable codeLengthsTable = new HuffmanTable(codeLengths);
// literal and length code
Uint8List litlenLengths = new Uint8List(numLitLengthCodes);
// distance code
Uint8List distLengths = new Uint8List(numDistanceCodes);
List<int> litlen =
_decode(numLitLengthCodes, codeLengthsTable, litlenLengths);
List<int> dist =
_decode(numDistanceCodes, codeLengthsTable, distLengths);
_decodeHuffman(new HuffmanTable(litlen), new HuffmanTable(dist));
}
void _decodeHuffman(HuffmanTable litlen, HuffmanTable dist) {
while (true) {
int code = _readCodeByTable(litlen);
if (code < 0 || code > 285) {
throw new ArchiveException('Invalid Huffman Code $code');
}
// 256 - End of Huffman block
if (code == 256) {
break;
}
// [0, 255] - Literal
if (code < 256) {
output.writeByte(code & 0xff);
continue;
}
// [257, 285] Dictionary Lookup
// length code
int ti = code - 257;
int codeLength = _LENGTH_CODE_TABLE[ti] +
_readBits(_LENGTH_EXTRA_TABLE[ti]);
// distance code
int distCode = _readCodeByTable(dist);
if (distCode >= 0 && distCode <= 29) {
int distance = _DIST_CODE_TABLE[distCode] +
_readBits(_DIST_EXTRA_TABLE[distCode]);
// lz77 decode
while (codeLength > distance) {
output.writeBytes(output.subset(-distance));
codeLength -= distance;
}
if (codeLength == distance) {
output.writeBytes(output.subset(-distance));
} else {
output.writeBytes(output.subset(-distance, codeLength - distance));
}
} else {
throw new ArchiveException('Illegal unused distance symbol');
}
}
while (_bitBufferLen >= 8) {
_bitBufferLen -= 8;
input.rewind(1);
}
}
/**
* decode function
*/
List<int> _decode(int num, HuffmanTable table, List<int> lengths) {
int prev = 0;
int i = 0;
while (i < num) {
int code = _readCodeByTable(table);
switch (code) {
case 16:
// Repeat last code
int repeat = 3 + _readBits(2);
while (repeat-- > 0) {
lengths[i++] = prev;
}
break;
case 17:
// Repeat 0
int repeat = 3 + _readBits(3);
while (repeat-- > 0) {
lengths[i++] = 0;
}
prev = 0;
break;
case 18:
// Repeat lots of 0s.
int repeat = 11 + _readBits(7);
while (repeat-- > 0) {
lengths[i++] = 0;
}
prev = 0;
break;
default: // [0, 15]
// Literal bitlength for this code.
if (code < 0 || code > 15) {
throw new ArchiveException('Invalid Huffman Code: $code');
}
lengths[i++] = code;
prev = code;
break;
}
}
return lengths;
}
int _bitBuffer = 0;
int _bitBufferLen = 0;
int _blockPos = 0;
// enum BlockType
static const int _BLOCK_UNCOMPRESSED = 0;
static const int _BLOCK_FIXED_HUFFMAN = 1;
static const int _BLOCK_DYNAMIC_HUFFMAN = 2;
/// Fixed huffman length code table
static const List<int> _FIXED_LITERAL_LENGTHS = const [
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 7, 7, 7, 7, 7, 7, 7, 7,
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8 ];
final HuffmanTable _fixedLiteralLengthTable =
new HuffmanTable(_FIXED_LITERAL_LENGTHS);
/// Fixed huffman distance code table
static const List<int> _FIXED_DISTANCE_TABLE = const [
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
5, 5, 5, 5, 5, 5 ];
final HuffmanTable _fixedDistanceTable =
new HuffmanTable(_FIXED_DISTANCE_TABLE);
/// Max backward length for LZ77.
static const int _MAX_BACKWARD_LENGTH = 32768; // ignore: unused_field
/// Max copy length for LZ77.
static const int _MAX_COPY_LENGTH = 258; // ignore: unused_field
/// Huffman order
static const List<int> _ORDER = const [
16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 ];
/// Huffman length code table.
static const List<int> _LENGTH_CODE_TABLE = const [
3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31,
35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258 ];
/// Huffman length extra-bits table.
static const List<int> _LENGTH_EXTRA_TABLE = const [
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3,
4, 4, 4, 4, 5, 5, 5, 5, 0, 0, 0 ];
/// Huffman dist code table.
static const List<int> _DIST_CODE_TABLE = const [
1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193,
257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145,
8193, 12289, 16385, 24577 ];
/// Huffman dist extra-bits table.
static const List<int> _DIST_EXTRA_TABLE = const [
0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10,
11, 11, 12, 12, 13, 13 ];
}