2025-01-22 16:18:30 +01:00

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();
}
}