/*-- Copyright 2009-2010 by Stefan Rusterholz. All rights reserved. You can choose between MIT and BSD-3-Clause license. License file will be added later. --*/ /** * See cc.Codec.GZip.gunzip. * @param {Array | String} data The bytestream to decompress * Constructor */ var GZip = function Jacob__GZip(data) { this.data = data; this.debug = false; this.gpflags = undefined; this.files = 0; this.unzipped = []; this.buf32k = new Array(32768); this.bIdx = 0; this.modeZIP = false; this.bytepos = 0; this.bb = 1; this.bits = 0; this.nameBuf = []; this.fileout = undefined; this.literalTree = new Array(GZip.LITERALS); this.distanceTree = new Array(32); this.treepos = 0; this.Places = null; this.len = 0; this.fpos = new Array(17); this.fpos[0] = 0; this.flens = undefined; this.fmax = undefined; }; /** * Unzips the gzipped data of the 'data' argument. * @param string The bytestream to decompress. Either an array of Integers between 0 and 255, or a String. * @return {String} */ GZip.gunzip = function (string) { if (string.constructor === Array) { } else if (string.constructor === String) { } var gzip = new GZip(string); return gzip.gunzip()[0][0]; }; GZip.HufNode = function () { this.b0 = 0; this.b1 = 0; this.jump = null; this.jumppos = -1; }; /** * @constant * @type Number */ GZip.LITERALS = 288; /** * @constant * @type Number */ GZip.NAMEMAX = 256; GZip.bitReverse = [ 0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0, 0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0, 0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8, 0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8, 0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4, 0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4, 0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec, 0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc, 0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2, 0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2, 0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea, 0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa, 0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6, 0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6, 0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee, 0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe, 0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1, 0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1, 0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9, 0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9, 0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5, 0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5, 0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed, 0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd, 0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3, 0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3, 0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb, 0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb, 0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7, 0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7, 0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef, 0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff ]; GZip.cplens = [ 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, 0, 0 ]; GZip.cplext = [ 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, 99, 99 ]; /* 99==invalid */ GZip.cpdist = [ 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0007, 0x0009, 0x000d, 0x0011, 0x0019, 0x0021, 0x0031, 0x0041, 0x0061, 0x0081, 0x00c1, 0x0101, 0x0181, 0x0201, 0x0301, 0x0401, 0x0601, 0x0801, 0x0c01, 0x1001, 0x1801, 0x2001, 0x3001, 0x4001, 0x6001 ]; GZip.cpdext = [ 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 ]; GZip.border = [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]; /** * gunzip * @return {Array} */ GZip.prototype.gunzip = function () { this.outputArr = []; //convertToByteArray(input); //if (this.debug) alert(this.data); this.nextFile(); return this.unzipped; }; GZip.prototype.readByte = function () { this.bits += 8; if (this.bytepos < this.data.length) { //return this.data[this.bytepos++]; // Array return this.data.charCodeAt(this.bytepos++); } else { return -1; } }; GZip.prototype.byteAlign = function () { this.bb = 1; }; GZip.prototype.readBit = function () { var carry; this.bits++; carry = (this.bb & 1); this.bb >>= 1; if (this.bb === 0) { this.bb = this.readByte(); carry = (this.bb & 1); this.bb = (this.bb >> 1) | 0x80; } return carry; }; GZip.prototype.readBits = function (a) { var res = 0, i = a; while (i--) res = (res << 1) | this.readBit(); if (a) res = GZip.bitReverse[res] >> (8 - a); return res; }; GZip.prototype.flushBuffer = function () { this.bIdx = 0; }; GZip.prototype.addBuffer = function (a) { this.buf32k[this.bIdx++] = a; this.outputArr.push(String.fromCharCode(a)); if (this.bIdx === 0x8000) this.bIdx = 0; }; GZip.prototype.IsPat = function () { while (1) { if (this.fpos[this.len] >= this.fmax) return -1; if (this.flens[this.fpos[this.len]] === this.len) return this.fpos[this.len]++; this.fpos[this.len]++; } }; GZip.prototype.Rec = function () { var curplace = this.Places[this.treepos]; var tmp; //if (this.debug) document.write("
len:"+this.len+" treepos:"+this.treepos); if (this.len === 17) { //war 17 return -1; } this.treepos++; this.len++; tmp = this.IsPat(); //if (this.debug) document.write("
IsPat "+tmp); if (tmp >= 0) { curplace.b0 = tmp; /* leaf cell for 0-bit */ //if (this.debug) document.write("
b0 "+curplace.b0); } else { /* Not a Leaf cell */ curplace.b0 = 0x8000; //if (this.debug) document.write("
b0 "+curplace.b0); if (this.Rec()) return -1; } tmp = this.IsPat(); if (tmp >= 0) { curplace.b1 = tmp; /* leaf cell for 1-bit */ //if (this.debug) document.write("
b1 "+curplace.b1); curplace.jump = null; /* Just for the display routine */ } else { /* Not a Leaf cell */ curplace.b1 = 0x8000; //if (this.debug) document.write("
b1 "+curplace.b1); curplace.jump = this.Places[this.treepos]; curplace.jumppos = this.treepos; if (this.Rec()) return -1; } this.len--; return 0; }; GZip.prototype.CreateTree = function (currentTree, numval, lengths, show) { var i; /* Create the Huffman decode tree/table */ //if (this.debug) document.write("currentTree "+currentTree+" numval "+numval+" lengths "+lengths+" show "+show); this.Places = currentTree; this.treepos = 0; this.flens = lengths; this.fmax = numval; for (i = 0; i < 17; i++) this.fpos[i] = 0; this.len = 0; if (this.Rec()) { //if (this.debug) alert("invalid huffman tree\n"); return -1; } // if (this.debug) { // document.write('
Tree: '+this.Places.length); // for (var a=0;a<32;a++){ // document.write("Places["+a+"].b0="+this.Places[a].b0+"
"); // document.write("Places["+a+"].b1="+this.Places[a].b1+"
"); // } // } return 0; }; GZip.prototype.DecodeValue = function (currentTree) { var len, i, xtreepos = 0, X = currentTree[xtreepos], b; /* decode one symbol of the data */ while (1) { b = this.readBit(); // if (this.debug) document.write("b="+b); if (b) { if (!(X.b1 & 0x8000)) { // if (this.debug) document.write("ret1"); return X.b1; /* If leaf node, return data */ } X = X.jump; len = currentTree.length; for (i = 0; i < len; i++) { if (currentTree[i] === X) { xtreepos = i; break; } } } else { if (!(X.b0 & 0x8000)) { // if (this.debug) document.write("ret2"); return X.b0; /* If leaf node, return data */ } xtreepos++; X = currentTree[xtreepos]; } } // if (this.debug) document.write("ret3"); return -1; }; GZip.prototype.DeflateLoop = function () { var last, c, type, i, len; do { last = this.readBit(); type = this.readBits(2); if (type === 0) { var blockLen, cSum; // Stored this.byteAlign(); blockLen = this.readByte(); blockLen |= (this.readByte() << 8); cSum = this.readByte(); cSum |= (this.readByte() << 8); if (((blockLen ^ ~cSum) & 0xffff)) { document.write("BlockLen checksum mismatch\n"); // FIXME: use throw } while (blockLen--) { c = this.readByte(); this.addBuffer(c); } } else if (type === 1) { var j; /* Fixed Huffman tables -- fixed decode routine */ while (1) { /* 256 0000000 0 : : : 279 0010111 23 0 00110000 48 : : : 143 10111111 191 280 11000000 192 : : : 287 11000111 199 144 110010000 400 : : : 255 111111111 511 Note the bit order! */ j = (GZip.bitReverse[this.readBits(7)] >> 1); if (j > 23) { j = (j << 1) | this.readBit(); /* 48..255 */ if (j > 199) { /* 200..255 */ j -= 128; /* 72..127 */ j = (j << 1) | this.readBit(); /* 144..255 << */ } else { /* 48..199 */ j -= 48; /* 0..151 */ if (j > 143) { j = j + 136; /* 280..287 << */ /* 0..143 << */ } } } else { /* 0..23 */ j += 256; /* 256..279 << */ } if (j < 256) { this.addBuffer(j); } else if (j === 256) { /* EOF */ break; // FIXME: make this the loop-condition } else { var len, dist; j -= 256 + 1; /* bytes + EOF */ len = this.readBits(GZip.cplext[j]) + GZip.cplens[j]; j = GZip.bitReverse[this.readBits(5)] >> 3; if (GZip.cpdext[j] > 8) { dist = this.readBits(8); dist |= (this.readBits(GZip.cpdext[j] - 8) << 8); } else { dist = this.readBits(GZip.cpdext[j]); } dist += GZip.cpdist[j]; for (j = 0; j < len; j++) { var c = this.buf32k[(this.bIdx - dist) & 0x7fff]; this.addBuffer(c); } } } // while } else if (type === 2) { var j, n, literalCodes, distCodes, lenCodes; var ll = new Array(288 + 32); // "static" just to preserve stack // Dynamic Huffman tables literalCodes = 257 + this.readBits(5); distCodes = 1 + this.readBits(5); lenCodes = 4 + this.readBits(4); for (j = 0; j < 19; j++) { ll[j] = 0; } // Get the decode tree code lengths for (j = 0; j < lenCodes; j++) { ll[GZip.border[j]] = this.readBits(3); } len = this.distanceTree.length; for (i = 0; i < len; i++) this.distanceTree[i] = new GZip.HufNode(); if (this.CreateTree(this.distanceTree, 19, ll, 0)) { this.flushBuffer(); return 1; } // if (this.debug) { // document.write("
distanceTree"); // for(var a=0;a"+this.distanceTree[a].b0+" "+this.distanceTree[a].b1+" "+this.distanceTree[a].jump+" "+this.distanceTree[a].jumppos); // } // } //read in literal and distance code lengths n = literalCodes + distCodes; i = 0; var z = -1; // if (this.debug) document.write("
n="+n+" bits: "+this.bits+"
"); while (i < n) { z++; j = this.DecodeValue(this.distanceTree); // if (this.debug) document.write("
"+z+" i:"+i+" decode: "+j+" bits "+this.bits+"
"); if (j < 16) { // length of code in bits (0..15) ll[i++] = j; } else if (j === 16) { // repeat last length 3 to 6 times var l; j = 3 + this.readBits(2); if (i + j > n) { this.flushBuffer(); return 1; } l = i ? ll[i - 1] : 0; while (j--) { ll[i++] = l; } } else { if (j === 17) { // 3 to 10 zero length codes j = 3 + this.readBits(3); } else { // j == 18: 11 to 138 zero length codes j = 11 + this.readBits(7); } if (i + j > n) { this.flushBuffer(); return 1; } while (j--) { ll[i++] = 0; } } } // while // Can overwrite tree decode tree as it is not used anymore len = this.literalTree.length; for (i = 0; i < len; i++) this.literalTree[i] = new GZip.HufNode(); if (this.CreateTree(this.literalTree, literalCodes, ll, 0)) { this.flushBuffer(); return 1; } len = this.literalTree.length; for (i = 0; i < len; i++) this.distanceTree[i] = new GZip.HufNode(); var ll2 = new Array(); for (i = literalCodes; i < ll.length; i++) ll2[i - literalCodes] = ll[i]; if (this.CreateTree(this.distanceTree, distCodes, ll2, 0)) { this.flushBuffer(); return 1; } // if (this.debug) document.write("
literalTree"); while (1) { j = this.DecodeValue(this.literalTree); if (j >= 256) { // In C64: if carry set var len, dist; j -= 256; if (j === 0) { // EOF break; } j--; len = this.readBits(GZip.cplext[j]) + GZip.cplens[j]; j = this.DecodeValue(this.distanceTree); if (GZip.cpdext[j] > 8) { dist = this.readBits(8); dist |= (this.readBits(GZip.cpdext[j] - 8) << 8); } else { dist = this.readBits(GZip.cpdext[j]); } dist += GZip.cpdist[j]; while (len--) { var c = this.buf32k[(this.bIdx - dist) & 0x7fff]; this.addBuffer(c); } } else { this.addBuffer(j); } } // while } } while (!last); this.flushBuffer(); this.byteAlign(); return 0; }; GZip.prototype.unzipFile = function (name) { var i; this.gunzip(); for (i = 0; i < this.unzipped.length; i++) { if (this.unzipped[i][1] === name) { return this.unzipped[i][0]; } } }; GZip.prototype.nextFile = function () { // if (this.debug) alert("NEXTFILE"); this.outputArr = []; this.modeZIP = false; var tmp = []; tmp[0] = this.readByte(); tmp[1] = this.readByte(); // if (this.debug) alert("type: "+tmp[0]+" "+tmp[1]); if (tmp[0] === 0x78 && tmp[1] === 0xda) { //GZIP // if (this.debug) alert("GEONExT-GZIP"); this.DeflateLoop(); // if (this.debug) alert(this.outputArr.join('')); this.unzipped[this.files] = [this.outputArr.join(''), "geonext.gxt"]; this.files++; } if (tmp[0] === 0x1f && tmp[1] === 0x8b) { //GZIP // if (this.debug) alert("GZIP"); this.skipdir(); // if (this.debug) alert(this.outputArr.join('')); this.unzipped[this.files] = [this.outputArr.join(''), "file"]; this.files++; } if (tmp[0] === 0x50 && tmp[1] === 0x4b) { //ZIP this.modeZIP = true; tmp[2] = this.readByte(); tmp[3] = this.readByte(); if (tmp[2] === 0x03 && tmp[3] === 0x04) { //MODE_ZIP tmp[0] = this.readByte(); tmp[1] = this.readByte(); // if (this.debug) alert("ZIP-Version: "+tmp[1]+" "+tmp[0]/10+"."+tmp[0]%10); this.gpflags = this.readByte(); this.gpflags |= (this.readByte() << 8); // if (this.debug) alert("gpflags: "+this.gpflags); var method = this.readByte(); method |= (this.readByte() << 8); // if (this.debug) alert("method: "+method); this.readByte(); this.readByte(); this.readByte(); this.readByte(); // var crc = this.readByte(); // crc |= (this.readByte()<<8); // crc |= (this.readByte()<<16); // crc |= (this.readByte()<<24); var compSize = this.readByte(); compSize |= (this.readByte() << 8); compSize |= (this.readByte() << 16); compSize |= (this.readByte() << 24); var size = this.readByte(); size |= (this.readByte() << 8); size |= (this.readByte() << 16); size |= (this.readByte() << 24); // if (this.debug) alert("local CRC: "+crc+"\nlocal Size: "+size+"\nlocal CompSize: "+compSize); var filelen = this.readByte(); filelen |= (this.readByte() << 8); var extralen = this.readByte(); extralen |= (this.readByte() << 8); // if (this.debug) alert("filelen "+filelen); i = 0; this.nameBuf = []; while (filelen--) { var c = this.readByte(); if (c === "/" | c === ":") { i = 0; } else if (i < GZip.NAMEMAX - 1) { this.nameBuf[i++] = String.fromCharCode(c); } } // if (this.debug) alert("nameBuf: "+this.nameBuf); if (!this.fileout) this.fileout = this.nameBuf; var i = 0; while (i < extralen) { c = this.readByte(); i++; } // if (size = 0 && this.fileOut.charAt(this.fileout.length-1)=="/"){ // //skipdir // // if (this.debug) alert("skipdir"); // } if (method === 8) { this.DeflateLoop(); // if (this.debug) alert(this.outputArr.join('')); this.unzipped[this.files] = [this.outputArr.join(''), this.nameBuf.join('')]; this.files++; } this.skipdir(); } } }; GZip.prototype.skipdir = function () { var tmp = []; var compSize, size, os, i, c; if ((this.gpflags & 8)) { tmp[0] = this.readByte(); tmp[1] = this.readByte(); tmp[2] = this.readByte(); tmp[3] = this.readByte(); // if (tmp[0] == 0x50 && tmp[1] == 0x4b && tmp[2] == 0x07 && tmp[3] == 0x08) { // crc = this.readByte(); // crc |= (this.readByte()<<8); // crc |= (this.readByte()<<16); // crc |= (this.readByte()<<24); // } else { // crc = tmp[0] | (tmp[1]<<8) | (tmp[2]<<16) | (tmp[3]<<24); // } compSize = this.readByte(); compSize |= (this.readByte() << 8); compSize |= (this.readByte() << 16); compSize |= (this.readByte() << 24); size = this.readByte(); size |= (this.readByte() << 8); size |= (this.readByte() << 16); size |= (this.readByte() << 24); } if (this.modeZIP) this.nextFile(); tmp[0] = this.readByte(); if (tmp[0] !== 8) { // if (this.debug) alert("Unknown compression method!"); return 0; } this.gpflags = this.readByte(); // if (this.debug && (this.gpflags & ~(0x1f))) alert("Unknown flags set!"); this.readByte(); this.readByte(); this.readByte(); this.readByte(); this.readByte(); os = this.readByte(); if ((this.gpflags & 4)) { tmp[0] = this.readByte(); tmp[2] = this.readByte(); this.len = tmp[0] + 256 * tmp[1]; // if (this.debug) alert("Extra field size: "+this.len); for (i = 0; i < this.len; i++) this.readByte(); } if ((this.gpflags & 8)) { i = 0; this.nameBuf = []; while (c = this.readByte()) { if (c === "7" || c === ":") i = 0; if (i < GZip.NAMEMAX - 1) this.nameBuf[i++] = c; } //this.nameBuf[i] = "\0"; // if (this.debug) alert("original file name: "+this.nameBuf); } if ((this.gpflags & 16)) { while (c = this.readByte()) { // FIXME: looks like they read to the end of the stream, should be doable more efficiently //FILE COMMENT } } if ((this.gpflags & 2)) { this.readByte(); this.readByte(); } this.DeflateLoop(); // crc = this.readByte(); // crc |= (this.readByte()<<8); // crc |= (this.readByte()<<16); // crc |= (this.readByte()<<24); size = this.readByte(); size |= (this.readByte() << 8); size |= (this.readByte() << 16); size |= (this.readByte() << 24); if (this.modeZIP) this.nextFile(); }; module.exports = GZip;