401 lines
9.4 KiB
Haxe
401 lines
9.4 KiB
Haxe
|
/*
|
||
|
* Copyright (C)2005-2019 Haxe Foundation
|
||
|
*
|
||
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
||
|
* copy of this software and associated documentation files (the "Software"),
|
||
|
* to deal in the Software without restriction, including without limitation
|
||
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||
|
* and/or sell copies of the Software, and to permit persons to whom the
|
||
|
* Software is furnished to do so, subject to the following conditions:
|
||
|
*
|
||
|
* The above copyright notice and this permission notice shall be included in
|
||
|
* all copies or substantial portions of the Software.
|
||
|
*
|
||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||
|
* DEALINGS IN THE SOFTWARE.
|
||
|
*/
|
||
|
|
||
|
package haxe.zip;
|
||
|
|
||
|
import haxe.zip.Huffman;
|
||
|
import haxe.crypto.Adler32;
|
||
|
|
||
|
private class Window {
|
||
|
public static inline var SIZE = 1 << 15;
|
||
|
public static inline var BUFSIZE = 1 << 16;
|
||
|
|
||
|
public var buffer:haxe.io.Bytes;
|
||
|
public var pos:Int;
|
||
|
|
||
|
var crc:Adler32;
|
||
|
|
||
|
public function new(hasCrc) {
|
||
|
buffer = haxe.io.Bytes.alloc(BUFSIZE);
|
||
|
pos = 0;
|
||
|
if (hasCrc)
|
||
|
crc = new Adler32();
|
||
|
}
|
||
|
|
||
|
public function slide() {
|
||
|
if (crc != null)
|
||
|
crc.update(buffer, 0, SIZE);
|
||
|
var b = haxe.io.Bytes.alloc(BUFSIZE);
|
||
|
pos -= SIZE;
|
||
|
b.blit(0, buffer, SIZE, pos);
|
||
|
buffer = b;
|
||
|
}
|
||
|
|
||
|
public function addBytes(b, p, len) {
|
||
|
if (pos + len > BUFSIZE)
|
||
|
slide();
|
||
|
buffer.blit(pos, b, p, len);
|
||
|
pos += len;
|
||
|
}
|
||
|
|
||
|
public function addByte(c) {
|
||
|
if (pos == BUFSIZE)
|
||
|
slide();
|
||
|
buffer.set(pos, c);
|
||
|
pos++;
|
||
|
}
|
||
|
|
||
|
public function getLastChar() {
|
||
|
return buffer.get(pos - 1);
|
||
|
}
|
||
|
|
||
|
public function available() {
|
||
|
return pos;
|
||
|
}
|
||
|
|
||
|
public function checksum() {
|
||
|
if (crc != null)
|
||
|
crc.update(buffer, 0, pos);
|
||
|
return crc;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private enum State {
|
||
|
Head;
|
||
|
Block;
|
||
|
CData;
|
||
|
Flat;
|
||
|
Crc;
|
||
|
Dist;
|
||
|
DistOne;
|
||
|
Done;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
A pure Haxe implementation of the ZLIB Inflate algorithm which allows reading compressed data without any platform-specific support.
|
||
|
**/
|
||
|
class InflateImpl {
|
||
|
static var LEN_EXTRA_BITS_TBL = [
|
||
|
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, -1, -1
|
||
|
];
|
||
|
static var LEN_BASE_VAL_TBL = [
|
||
|
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
|
||
|
];
|
||
|
static var DIST_EXTRA_BITS_TBL = [
|
||
|
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, -1, -1
|
||
|
];
|
||
|
static var DIST_BASE_VAL_TBL = [
|
||
|
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
|
||
|
];
|
||
|
static var CODE_LENGTHS_POS = [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15];
|
||
|
|
||
|
var nbits:Int;
|
||
|
var bits:Int;
|
||
|
var state:State;
|
||
|
var isFinal:Bool;
|
||
|
var huffman:Huffman;
|
||
|
var huffdist:Null<Huffman>;
|
||
|
var htools:HuffTools;
|
||
|
var len:Int;
|
||
|
var dist:Int;
|
||
|
var needed:Int;
|
||
|
var output:haxe.io.Bytes;
|
||
|
var outpos:Int;
|
||
|
var input:haxe.io.Input;
|
||
|
var lengths:Array<Int>;
|
||
|
var window:Window;
|
||
|
|
||
|
static var FIXED_HUFFMAN = null;
|
||
|
|
||
|
public function new(i, ?header = true, ?crc = true) {
|
||
|
isFinal = false;
|
||
|
htools = new HuffTools();
|
||
|
huffman = buildFixedHuffman();
|
||
|
huffdist = null;
|
||
|
len = 0;
|
||
|
dist = 0;
|
||
|
state = header ? Head : Block;
|
||
|
input = i;
|
||
|
bits = 0;
|
||
|
nbits = 0;
|
||
|
needed = 0;
|
||
|
output = null;
|
||
|
outpos = 0;
|
||
|
lengths = new Array();
|
||
|
for (i in 0...19)
|
||
|
lengths.push(-1);
|
||
|
window = new Window(crc);
|
||
|
}
|
||
|
|
||
|
function buildFixedHuffman() {
|
||
|
if (FIXED_HUFFMAN != null)
|
||
|
return FIXED_HUFFMAN;
|
||
|
var a = new Array();
|
||
|
for (n in 0...288)
|
||
|
a.push(if (n <= 143) 8 else if (n <= 255) 9 else if (n <= 279) 7 else 8);
|
||
|
FIXED_HUFFMAN = htools.make(a, 0, 288, 10);
|
||
|
return FIXED_HUFFMAN;
|
||
|
}
|
||
|
|
||
|
public function readBytes(b, pos, len) {
|
||
|
needed = len;
|
||
|
outpos = pos;
|
||
|
output = b;
|
||
|
if (len > 0)
|
||
|
while (inflateLoop()) {}
|
||
|
return len - needed;
|
||
|
}
|
||
|
|
||
|
function getBits(n) {
|
||
|
while (nbits < n) {
|
||
|
bits |= input.readByte() << nbits;
|
||
|
nbits += 8;
|
||
|
}
|
||
|
var b = bits & ((1 << n) - 1);
|
||
|
nbits -= n;
|
||
|
bits >>= n;
|
||
|
return b;
|
||
|
}
|
||
|
|
||
|
function getBit() {
|
||
|
if (nbits == 0) {
|
||
|
nbits = 8;
|
||
|
bits = input.readByte();
|
||
|
}
|
||
|
var b = bits & 1 == 1;
|
||
|
nbits--;
|
||
|
bits >>= 1;
|
||
|
return b;
|
||
|
}
|
||
|
|
||
|
function getRevBits(n) {
|
||
|
return if (n == 0)
|
||
|
0
|
||
|
else if (getBit())
|
||
|
(1 << (n - 1)) | getRevBits(n - 1)
|
||
|
else
|
||
|
getRevBits(n - 1);
|
||
|
}
|
||
|
|
||
|
function resetBits() {
|
||
|
bits = 0;
|
||
|
nbits = 0;
|
||
|
}
|
||
|
|
||
|
function addBytes(b, p, len) {
|
||
|
window.addBytes(b, p, len);
|
||
|
output.blit(outpos, b, p, len);
|
||
|
needed -= len;
|
||
|
outpos += len;
|
||
|
}
|
||
|
|
||
|
function addByte(b) {
|
||
|
window.addByte(b);
|
||
|
output.set(outpos, b);
|
||
|
needed--;
|
||
|
outpos++;
|
||
|
}
|
||
|
|
||
|
function addDistOne(n) {
|
||
|
var c = window.getLastChar();
|
||
|
for (i in 0...n)
|
||
|
addByte(c);
|
||
|
}
|
||
|
|
||
|
function addDist(d, len) {
|
||
|
addBytes(window.buffer, window.pos - d, len);
|
||
|
}
|
||
|
|
||
|
function applyHuffman(h) {
|
||
|
return switch (h) {
|
||
|
case Found(n): n;
|
||
|
case NeedBit(a, b): applyHuffman(getBit() ? b : a);
|
||
|
case NeedBits(n, tbl): applyHuffman(tbl[getBits(n)]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function inflateLengths(a, max) {
|
||
|
var i = 0;
|
||
|
var prev = 0;
|
||
|
while (i < max) {
|
||
|
var n = applyHuffman(huffman);
|
||
|
switch (n) {
|
||
|
case 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15:
|
||
|
prev = n;
|
||
|
a[i] = n;
|
||
|
i++;
|
||
|
case 16:
|
||
|
var end = i + 3 + getBits(2);
|
||
|
if (end > max)
|
||
|
throw "Invalid data";
|
||
|
while (i < end) {
|
||
|
a[i] = prev;
|
||
|
i++;
|
||
|
}
|
||
|
case 17:
|
||
|
i += 3 + getBits(3);
|
||
|
if (i > max)
|
||
|
throw "Invalid data";
|
||
|
case 18:
|
||
|
i += 11 + getBits(7);
|
||
|
if (i > max)
|
||
|
throw "Invalid data";
|
||
|
default:
|
||
|
throw "Invalid data";
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function inflateLoop() {
|
||
|
switch (state) {
|
||
|
case Head:
|
||
|
var cmf = input.readByte();
|
||
|
var cm = cmf & 15;
|
||
|
var cinfo = cmf >> 4;
|
||
|
if (cm != 8)
|
||
|
throw "Invalid data";
|
||
|
var flg = input.readByte();
|
||
|
// var fcheck = flg & 31;
|
||
|
var fdict = flg & 32 != 0;
|
||
|
// var flevel = flg >> 6;
|
||
|
if (((cmf << 8) + flg) % 31 != 0)
|
||
|
throw "Invalid data";
|
||
|
if (fdict)
|
||
|
throw "Unsupported dictionary";
|
||
|
state = Block;
|
||
|
return true;
|
||
|
case Crc:
|
||
|
var calc = window.checksum();
|
||
|
if (calc == null) {
|
||
|
state = Done;
|
||
|
return true;
|
||
|
}
|
||
|
var crc = Adler32.read(input);
|
||
|
if (!calc.equals(crc))
|
||
|
throw "Invalid CRC";
|
||
|
state = Done;
|
||
|
return true;
|
||
|
case Done:
|
||
|
// nothing
|
||
|
return false;
|
||
|
case Block:
|
||
|
isFinal = getBit();
|
||
|
switch (getBits(2)) {
|
||
|
case 0: // no compression
|
||
|
len = input.readUInt16();
|
||
|
var nlen = input.readUInt16();
|
||
|
if (nlen != 0xFFFF - len) throw "Invalid data";
|
||
|
state = Flat;
|
||
|
var r = inflateLoop();
|
||
|
resetBits();
|
||
|
return r;
|
||
|
case 1: // fixed Huffman
|
||
|
huffman = buildFixedHuffman();
|
||
|
huffdist = null;
|
||
|
state = CData;
|
||
|
return true;
|
||
|
case 2: // dynamic Huffman
|
||
|
var hlit = getBits(5) + 257;
|
||
|
var hdist = getBits(5) + 1;
|
||
|
var hclen = getBits(4) + 4;
|
||
|
for (i in 0...hclen)
|
||
|
lengths[CODE_LENGTHS_POS[i]] = getBits(3);
|
||
|
for (i in hclen...19)
|
||
|
lengths[CODE_LENGTHS_POS[i]] = 0;
|
||
|
huffman = htools.make(lengths, 0, 19, 8);
|
||
|
var lengths = new Array();
|
||
|
for (i in 0...hlit + hdist)
|
||
|
lengths.push(0);
|
||
|
inflateLengths(lengths, hlit + hdist);
|
||
|
huffdist = htools.make(lengths, hlit, hdist, 16);
|
||
|
huffman = htools.make(lengths, 0, hlit, 16);
|
||
|
state = CData;
|
||
|
return true;
|
||
|
default:
|
||
|
throw "Invalid data";
|
||
|
}
|
||
|
case Flat:
|
||
|
var rlen = (len < needed) ? len : needed;
|
||
|
var bytes = input.read(rlen);
|
||
|
len -= rlen;
|
||
|
addBytes(bytes, 0, rlen);
|
||
|
if (len == 0)
|
||
|
state = isFinal ? Crc : Block;
|
||
|
return needed > 0;
|
||
|
case DistOne:
|
||
|
var rlen = (len < needed) ? len : needed;
|
||
|
addDistOne(rlen);
|
||
|
len -= rlen;
|
||
|
if (len == 0)
|
||
|
state = CData;
|
||
|
return needed > 0;
|
||
|
case Dist:
|
||
|
while (len > 0 && needed > 0) {
|
||
|
var rdist = (len < dist) ? len : dist;
|
||
|
var rlen = (needed < rdist) ? needed : rdist;
|
||
|
addDist(dist, rlen);
|
||
|
len -= rlen;
|
||
|
}
|
||
|
if (len == 0)
|
||
|
state = CData;
|
||
|
return needed > 0;
|
||
|
case CData:
|
||
|
var n = applyHuffman(huffman);
|
||
|
if (n < 256) {
|
||
|
addByte(n);
|
||
|
return needed > 0;
|
||
|
} else if (n == 256) {
|
||
|
state = isFinal ? Crc : Block;
|
||
|
return true;
|
||
|
} else {
|
||
|
n -= 257;
|
||
|
var extra_bits = LEN_EXTRA_BITS_TBL[n];
|
||
|
if (extra_bits == -1)
|
||
|
throw "Invalid data";
|
||
|
len = LEN_BASE_VAL_TBL[n] + getBits(extra_bits);
|
||
|
var dist_code = if (huffdist == null) getRevBits(5) else applyHuffman(huffdist);
|
||
|
extra_bits = DIST_EXTRA_BITS_TBL[dist_code];
|
||
|
if (extra_bits == -1)
|
||
|
throw "Invalid data";
|
||
|
dist = DIST_BASE_VAL_TBL[dist_code] + getBits(extra_bits);
|
||
|
if (dist > window.available())
|
||
|
throw "Invalid data";
|
||
|
state = (dist == 1) ? DistOne : Dist;
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public static function run(i:haxe.io.Input, ?bufsize = 65536) {
|
||
|
var buf = haxe.io.Bytes.alloc(bufsize);
|
||
|
var output = new haxe.io.BytesBuffer();
|
||
|
var inflate = new InflateImpl(i);
|
||
|
while (true) {
|
||
|
var len = inflate.readBytes(buf, 0, bufsize);
|
||
|
output.addBytes(buf, 0, len);
|
||
|
if (len < bufsize)
|
||
|
break;
|
||
|
}
|
||
|
return output.getBytes();
|
||
|
}
|
||
|
}
|