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