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