214 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			Haxe
		
	
	
	
	
	
		
		
			
		
	
	
			214 lines
		
	
	
		
			6.1 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.Entry; | ||
|  | import haxe.ds.List; | ||
|  | 
 | ||
|  | // see http://www.pkware.com/documents/casestudies/APPNOTE.TXT | ||
|  | class Reader { | ||
|  | 	var i:haxe.io.Input; | ||
|  | 
 | ||
|  | 	public function new(i) { | ||
|  | 		this.i = i; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	function readZipDate() { | ||
|  | 		var t = i.readUInt16(); | ||
|  | 		var hour = (t >> 11) & 31; | ||
|  | 		var min = (t >> 5) & 63; | ||
|  | 		var sec = t & 31; | ||
|  | 		var d = i.readUInt16(); | ||
|  | 		var year = d >> 9; | ||
|  | 		var month = (d >> 5) & 15; | ||
|  | 		var day = d & 31; | ||
|  | 		return new Date(year + 1980, month - 1, day, hour, min, sec << 1); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	function readExtraFields(length) { | ||
|  | 		var fields = new List(); | ||
|  | 		while (length > 0) { | ||
|  | 			if (length < 4) | ||
|  | 				throw "Invalid extra fields data"; | ||
|  | 			var tag = i.readUInt16(); | ||
|  | 			var len = i.readUInt16(); | ||
|  | 			if (length < len) | ||
|  | 				throw "Invalid extra fields data"; | ||
|  | 			switch (tag) { | ||
|  | 				case 0x7075: | ||
|  | 					var version = i.readByte(); | ||
|  | 					if (version != 1) { | ||
|  | 						var data = new haxe.io.BytesBuffer(); | ||
|  | 						data.addByte(version); | ||
|  | 						data.add(i.read(len - 1)); | ||
|  | 						fields.add(FUnknown(tag, data.getBytes())); | ||
|  | 					} else { | ||
|  | 						var crc = i.readInt32(); | ||
|  | 						var name = i.read(len - 5).toString(); | ||
|  | 						fields.add(FInfoZipUnicodePath(name, crc)); | ||
|  | 					} | ||
|  | 				default: | ||
|  | 					fields.add(FUnknown(tag, i.read(len))); | ||
|  | 			} | ||
|  | 			length -= 4 + len; | ||
|  | 		} | ||
|  | 		return fields; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public function readEntryHeader():Entry { | ||
|  | 		var i = this.i; | ||
|  | 		var h = i.readInt32(); | ||
|  | 		if (h == 0x02014B50 || h == 0x06054B50) | ||
|  | 			return null; | ||
|  | 		if (h != 0x04034B50) | ||
|  | 			throw "Invalid Zip Data"; | ||
|  | 		var version = i.readUInt16(); | ||
|  | 		var flags = i.readUInt16(); | ||
|  | 		var utf8 = flags & 0x800 != 0; | ||
|  | 		if ((flags & 0xF7F1) != 0) | ||
|  | 			throw "Unsupported flags " + flags; | ||
|  | 		var compression = i.readUInt16(); | ||
|  | 		var compressed = (compression != 0); | ||
|  | 		if (compressed && compression != 8) | ||
|  | 			throw "Unsupported compression " + compression; | ||
|  | 		var mtime = readZipDate(); | ||
|  | 		var crc32:Null<Int> = i.readInt32(); | ||
|  | 		var csize = i.readInt32(); | ||
|  | 		var usize = i.readInt32(); | ||
|  | 		var fnamelen = i.readInt16(); | ||
|  | 		var elen = i.readInt16(); | ||
|  | 		var fname = i.readString(fnamelen); | ||
|  | 		var fields = readExtraFields(elen); | ||
|  | 		if (utf8) | ||
|  | 			fields.push(FUtf8); | ||
|  | 		var data = null; | ||
|  | 		// we have a data descriptor that store the real crc/sizes | ||
|  | 		// after the compressed data, let's wait for it | ||
|  | 		if ((flags & 8) != 0) | ||
|  | 			crc32 = null; | ||
|  | 		return { | ||
|  | 			fileName: fname, | ||
|  | 			fileSize: usize, | ||
|  | 			fileTime: mtime, | ||
|  | 			compressed: compressed, | ||
|  | 			dataSize: csize, | ||
|  | 			data: data, | ||
|  | 			crc32: crc32, | ||
|  | 			extraFields: fields, | ||
|  | 		}; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public function read():List<Entry> { | ||
|  | 		var l = new List(); | ||
|  | 		var buf = null; | ||
|  | 		var tmp = null; | ||
|  | 		while (true) { | ||
|  | 			var e = readEntryHeader(); | ||
|  | 			if (e == null) | ||
|  | 				break; | ||
|  | 			// do we have a data descriptor? (see readEntryHeader) | ||
|  | 			if (e.crc32 == null) { | ||
|  | 				if (e.compressed) { | ||
|  | 					#if neko | ||
|  | 					// enter progressive mode : we use a different input which has | ||
|  | 					// a temporary buffer, this is necessary since we have to uncompress | ||
|  | 					// progressively, and after that we might have pending read data | ||
|  | 					// that needs to be processed | ||
|  | 					var bufSize = 65536; | ||
|  | 					if (buf == null) { | ||
|  | 						buf = new haxe.io.BufferInput(i, haxe.io.Bytes.alloc(bufSize)); | ||
|  | 						tmp = haxe.io.Bytes.alloc(bufSize); | ||
|  | 						i = buf; | ||
|  | 					} | ||
|  | 					var out = new haxe.io.BytesBuffer(); | ||
|  | 					var z = new neko.zip.Uncompress(-15); | ||
|  | 					z.setFlushMode(neko.zip.Flush.SYNC); | ||
|  | 					while (true) { | ||
|  | 						if (buf.available == 0) | ||
|  | 							buf.refill(); | ||
|  | 						var p = bufSize - buf.available; | ||
|  | 						if (p != buf.pos) { | ||
|  | 							// because of lack of "srcLen" in zip api, we need to always be stuck to the buffer end | ||
|  | 							buf.buf.blit(p, buf.buf, buf.pos, buf.available); | ||
|  | 							buf.pos = p; | ||
|  | 						} | ||
|  | 						var r = z.execute(buf.buf, buf.pos, tmp, 0); | ||
|  | 						out.addBytes(tmp, 0, r.write); | ||
|  | 						buf.pos += r.read; | ||
|  | 						buf.available -= r.read; | ||
|  | 						if (r.done) | ||
|  | 							break; | ||
|  | 					} | ||
|  | 					e.data = out.getBytes(); | ||
|  | 					#else | ||
|  | 					var bufSize = 65536; | ||
|  | 					if (tmp == null) | ||
|  | 						tmp = haxe.io.Bytes.alloc(bufSize); | ||
|  | 					var out = new haxe.io.BytesBuffer(); | ||
|  | 					var z = new InflateImpl(i, false, false); | ||
|  | 					while (true) { | ||
|  | 						var n = z.readBytes(tmp, 0, bufSize); | ||
|  | 						out.addBytes(tmp, 0, n); | ||
|  | 						if (n < bufSize) | ||
|  | 							break; | ||
|  | 					} | ||
|  | 					e.data = out.getBytes(); | ||
|  | 					#end | ||
|  | 				} else | ||
|  | 					e.data = i.read(e.dataSize); | ||
|  | 				e.crc32 = i.readInt32(); | ||
|  | 				if (e.crc32 == 0x08074b50) | ||
|  | 					e.crc32 = i.readInt32(); | ||
|  | 				e.dataSize = i.readInt32(); | ||
|  | 				e.fileSize = i.readInt32(); | ||
|  | 				// set data to uncompressed | ||
|  | 				e.dataSize = e.fileSize; | ||
|  | 				e.compressed = false; | ||
|  | 			} else | ||
|  | 				e.data = i.read(e.dataSize); | ||
|  | 			l.add(e); | ||
|  | 		} | ||
|  | 		return l; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public static function readZip(i:haxe.io.Input) { | ||
|  | 		var r = new Reader(i); | ||
|  | 		return r.read(); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public static function unzip(f:Entry) { | ||
|  | 		if (!f.compressed) | ||
|  | 			return f.data; | ||
|  | 		var c = new haxe.zip.Uncompress(-15); | ||
|  | 		var s = haxe.io.Bytes.alloc(f.fileSize); | ||
|  | 		var r = c.execute(f.data, 0, s, 0); | ||
|  | 		c.close(); | ||
|  | 		if (!r.done || r.read != f.data.length || r.write != f.fileSize) | ||
|  | 			throw "Invalid compressed data for " + f.fileName; | ||
|  | 		f.compressed = false; | ||
|  | 		f.dataSize = f.fileSize; | ||
|  | 		f.data = s; | ||
|  | 		return f.data; | ||
|  | 	} | ||
|  | } |