blob: 468f8fe580d296c545595bd7e165a270a8f9d2d1 [file]
import 'dart:typed_data';
import '../util/input_stream.dart';
import '../util/output_stream.dart';
import 'huffman_table.dart';
class Inflate {
late InputStreamBase input;
bool inputSet = false;
dynamic output;
Inflate(List<int> bytes, [int? uncompressedSize])
: input = InputStream(bytes),
output = OutputStream(size: uncompressedSize) {
inputSet = true;
_inflate();
}
Inflate.buffer(this.input, [int? uncompressedSize])
: output = OutputStream(size: uncompressedSize) {
inputSet = true;
_inflate();
}
Inflate.stream([InputStreamBase? input, dynamic outputStream])
: output = outputStream ?? OutputStream() {
if (input != null) {
this.input = input;
inputSet = true;
}
_inflate();
}
void streamInput(List<int> bytes) {
if (inputSet && input is InputStream) {
var i = input as InputStream;
i.offset = _blockPos;
final inputLen = input.length;
final newLen = inputLen + bytes.length;
final newBytes = Uint8List(newLen);
newBytes.setRange(0, inputLen, i.buffer, i.offset);
newBytes.setRange(inputLen, newLen, bytes, 0);
input = InputStream(newBytes);
} else {
input = InputStream(bytes);
}
inputSet = true;
}
List<int>? inflateNext() {
_bitBuffer = 0;
_bitBufferLen = 0;
if (output is OutputStream) {
output.clear();
}
if (!inputSet || input.isEOS) {
return null;
}
try {
if (input is InputStream) {
var i = input as InputStream;
_blockPos = i.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() as List<int>;
}
return null;
}
/// Get the decompressed data.
List<int> getBytes() {
return output.getBytes() as List<int>;
}
void _inflate() {
_bitBuffer = 0;
_bitBufferLen = 0;
if (!inputSet) {
return;
}
while (!input.isEOS) {
if (!_parseBlock()) {
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
final blockHeader = _readBits(3);
// BFINAL (is this the final block)?
final finalBlock = (blockHeader & 0x1) != 0;
// BTYPE (the type of block)
final blockType = blockHeader >> 1;
switch (blockType) {
case 0: // Uncompressed block
if (_parseUncompressedBlock() == -1) {
return false;
}
break;
case 1: // Fixed huffman block
if (_parseFixedHuffmanBlock() == -1) {
return false;
}
break;
case 2: // Dynamic huffman block
if (_parseDynamicHuffmanBlock() == -1) {
return false;
}
break;
default:
return false;
}
// Continue while not the final block
return !finalBlock;
}
/// 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) {
return -1;
}
// input byte
final octet = input.readByte();
// concat octet
_bitBuffer |= octet << _bitBufferLen;
_bitBufferLen += 8;
}
// output byte
final octet = _bitBuffer & ((1 << length) - 1);
_bitBuffer >>= length;
_bitBufferLen -= length;
return octet;
}
/// Read huffman code using [table].
int _readCodeByTable(HuffmanTable table) {
final codeTable = table.table;
final maxCodeLength = table.maxCodeLength;
// Not enough buffer
while (_bitBufferLen < maxCodeLength) {
if (input.isEOS) {
return -1;
}
final octet = input.readByte();
_bitBuffer |= octet << _bitBufferLen;
_bitBufferLen += 8;
}
// read max length
final codeWithLength = codeTable[_bitBuffer & ((1 << maxCodeLength) - 1)];
final codeLength = codeWithLength >> 16;
_bitBuffer >>= codeLength;
_bitBufferLen -= codeLength;
return codeWithLength & 0xffff;
}
int _parseUncompressedBlock() {
// skip buffered header bits
_bitBuffer = 0;
_bitBufferLen = 0;
final len = _readBits(16);
final nlen = _readBits(16) ^ 0xffff;
// Make sure the block size checksum is valid.
if (len != 0 && len != nlen) {
return -1;
}
// check size
if (len > input.length) {
return -1;
}
output.writeInputStream(input.readBytes(len));
return 0;
}
int _parseFixedHuffmanBlock() {
return _decodeHuffman(_fixedLiteralLengthTable, _fixedDistanceTable);
}
int _parseDynamicHuffmanBlock() {
// number of literal and length codes.
var numLitLengthCodes = _readBits(5);
if (numLitLengthCodes == -1) {
return -1;
}
numLitLengthCodes += 257;
if (numLitLengthCodes > 288) {
return - 1;
}
// number of distance codes.
var numDistanceCodes = _readBits(5);
if (numDistanceCodes == -1) {
return -1;
}
numDistanceCodes += 1;
if (numDistanceCodes > 32) {
return -1;
}
// number of code lengths.
var numCodeLengths = _readBits(4);
if (numCodeLengths == -1) {
return -1;
}
numCodeLengths += 4;
if (numCodeLengths > 19) {
return -1;
}
// decode code lengths
final codeLengths = Uint8List(_order.length);
for (var i = 0; i < numCodeLengths; ++i) {
final len = _readBits(3);
if (len == -1) {
return -1;
}
codeLengths[_order[i]] = len;
}
final codeLengthsTable = HuffmanTable(codeLengths);
final litLenDistLengths = Uint8List(numLitLengthCodes + numDistanceCodes);
// literal and length code
final litlenLengths = Uint8List.view(litLenDistLengths.buffer, 0, numLitLengthCodes);
// distance code
final distLengths = Uint8List.view(litLenDistLengths.buffer, numLitLengthCodes, numDistanceCodes);
if (_decode(litLenDistLengths.length, codeLengthsTable, litLenDistLengths) == -1) {
return -1;
}
return _decodeHuffman(HuffmanTable(litlenLengths), HuffmanTable(distLengths));
}
int _decodeHuffman(HuffmanTable litlen, HuffmanTable dist) {
while (true) {
final code = _readCodeByTable(litlen);
if (code < 0 || code > 285) {
return -1;
}
// 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
final ti = code - 257;
var codeLength =
_lengthCodeTable[ti] + _readBits(_lengthExtraTable[ti]);
// distance code
final distCode = _readCodeByTable(dist);
if (distCode < 0 || distCode > 29) {
return -1;
}
final distance =
_distCodeTable[distCode] + _readBits(_distExtraTable[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));
}
}
while (_bitBufferLen >= 8) {
_bitBufferLen -= 8;
input.rewind(1);
}
return 0;
}
int _decode(int num, HuffmanTable table, List<int> codeLengths) {
var prev = 0;
var i = 0;
while (i < num) {
final code = _readCodeByTable(table);
if (code == -1) {
return -1;
}
switch (code) {
case 16:
// Repeat last code
var repeat = _readBits(2);
if (repeat == -1) {
return -1;
}
repeat += 3;
while (repeat-- > 0) {
codeLengths[i++] = prev;
}
break;
case 17:
// Repeat 0
var repeat = _readBits(3);
if (repeat == -1) {
return -1;
}
repeat += 3;
while (repeat-- > 0) {
codeLengths[i++] = 0;
}
prev = 0;
break;
case 18:
// Repeat lots of 0s.
var repeat = _readBits(7);
if (repeat == -1) {
return - 1;
}
repeat += 11;
while (repeat-- > 0) {
codeLengths[i++] = 0;
}
prev = 0;
break;
default: // [0, 15]
// Literal bitlength for this code.
if (code < 0 || code > 15) {
return -1;
}
codeLengths[i++] = code;
prev = code;
break;
}
}
return 0;
}
int _bitBuffer = 0;
int _bitBufferLen = 0;
int _blockPos = 0;
/// Fixed huffman length code table
static const List<int> _fixedLiteralLengths = [
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 =
HuffmanTable(_fixedLiteralLengths);
/// Fixed huffman distance code table
static const List<int> _fixedDistanceTableData = [
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 = HuffmanTable(_fixedDistanceTableData);
/// Huffman order
static const List<int> _order = [
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> _lengthCodeTable = [
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> _lengthExtraTable = [
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> _distCodeTable = [
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> _distExtraTable = [
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
];
}