UNPKG

gzip-js

Version:

GZIP in pure JavaScript (works in the browser)

281 lines (234 loc) 5.85 kB
(function () { 'use strict'; var crc32 = require('crc32'), deflate = require('deflate-js'), // magic numbers marking this file as GZIP ID1 = 0x1F, ID2 = 0x8B, compressionMethods = { 'deflate': 8 }, possibleFlags = { 'FTEXT': 0x01, 'FHCRC': 0x02, 'FEXTRA': 0x04, 'FNAME': 0x08, 'FCOMMENT': 0x10 }, osMap = { 'fat': 0, // FAT file system (DOS, OS/2, NT) + PKZIPW 2.50 VFAT, NTFS 'amiga': 1, // Amiga 'vmz': 2, // VMS (VAX or Alpha AXP) 'unix': 3, // Unix 'vm/cms': 4, // VM/CMS 'atari': 5, // Atari 'hpfs': 6, // HPFS file system (OS/2, NT 3.x) 'macintosh': 7, // Macintosh 'z-system': 8, // Z-System 'cplm': 9, // CP/M 'tops-20': 10, // TOPS-20 'ntfs': 11, // NTFS file system (NT) 'qdos': 12, // SMS/QDOS 'acorn': 13, // Acorn RISC OS 'vfat': 14, // VFAT file system (Win95, NT) 'vms': 15, // MVS (code also taken for PRIMOS) 'beos': 16, // BeOS (BeBox or PowerMac) 'tandem': 17, // Tandem/NSK 'theos': 18 // THEOS }, os = 'unix', DEFAULT_LEVEL = 6; function putByte(n, arr) { arr.push(n & 0xFF); } // LSB first function putShort(n, arr) { arr.push(n & 0xFF); arr.push(n >>> 8); } // LSB first function putLong(n, arr) { putShort(n & 0xffff, arr); putShort(n >>> 16, arr); } function putString(s, arr) { var i, len = s.length; for (i = 0; i < len; i += 1) { putByte(s.charCodeAt(i), arr); } } function readByte(arr) { return arr.shift(); } function readShort(arr) { return arr.shift() | (arr.shift() << 8); } function readLong(arr) { var n1 = readShort(arr), n2 = readShort(arr); // JavaScript can't handle bits in the position 32 // we'll emulate this by removing the left-most bit (if it exists) // and add it back in via multiplication, which does work if (n2 > 32768) { n2 -= 32768; return ((n2 << 16) | n1) + 32768 * Math.pow(2, 16); } return (n2 << 16) | n1; } function readString(arr) { var charArr = []; // turn all bytes into chars until the terminating null while (arr[0] !== 0) { charArr.push(String.fromCharCode(arr.shift())); } // throw away terminating null arr.shift(); // join all characters into a cohesive string return charArr.join(''); } /* * Reads n number of bytes and return as an array. * * @param arr- Array of bytes to read from * @param n- Number of bytes to read */ function readBytes(arr, n) { var i, ret = []; for (i = 0; i < n; i += 1) { ret.push(arr.shift()); } return ret; } /* * ZIPs a file in GZIP format. The format is as given by the spec, found at: * http://www.gzip.org/zlib/rfc-gzip.html * * Omitted parts in this implementation: */ function zip(data, options) { var flags = 0, level, crc, out = []; if (!options) { options = {}; } level = options.level || DEFAULT_LEVEL; if (typeof data === 'string') { data = Array.prototype.map.call(data, function (char) { return char.charCodeAt(0); }); } // magic number marking this file as GZIP putByte(ID1, out); putByte(ID2, out); putByte(compressionMethods['deflate'], out); if (options.name) { flags |= possibleFlags['FNAME']; } putByte(flags, out); putLong(options.timestamp || parseInt(Date.now() / 1000, 10), out); // put deflate args (extra flags) if (level === 1) { // fastest algorithm putByte(4, out); } else if (level === 9) { // maximum compression (fastest algorithm) putByte(2, out); } else { putByte(0, out); } // OS identifier putByte(osMap[os], out); if (options.name) { // ignore the directory part putString(options.name.substring(options.name.lastIndexOf('/') + 1), out); // terminating null putByte(0, out); } deflate.deflate(data, level).forEach(function (byte) { putByte(byte, out); }); putLong(parseInt(crc32(data), 16), out); putLong(data.length, out); return out; } function unzip(data, options) { // start with a copy of the array var arr = Array.prototype.slice.call(data, 0), t, compressionMethod, flags, mtime, xFlags, key, os, crc, size, res; // check the first two bytes for the magic numbers if (readByte(arr) !== ID1 || readByte(arr) !== ID2) { throw 'Not a GZIP file'; } t = readByte(arr); t = Object.keys(compressionMethods).some(function (key) { compressionMethod = key; return compressionMethods[key] === t; }); if (!t) { throw 'Unsupported compression method'; } flags = readByte(arr); mtime = readLong(arr); xFlags = readByte(arr); t = readByte(arr); Object.keys(osMap).some(function (key) { if (osMap[key] === t) { os = key; return true; } }); // just throw away the bytes for now if (flags & possibleFlags['FEXTRA']) { t = readShort(arr); readBytes(arr, t); } // just throw away for now if (flags & possibleFlags['FNAME']) { readString(arr); } // just throw away for now if (flags & possibleFlags['FCOMMENT']) { readString(arr); } // just throw away for now if (flags & possibleFlags['FHCRC']) { readShort(arr); } if (compressionMethod === 'deflate') { // give deflate everything but the last 8 bytes // the last 8 bytes are for the CRC32 checksum and filesize res = deflate.inflate(arr.splice(0, arr.length - 8)); } if (flags & possibleFlags['FTEXT']) { res = Array.prototype.map.call(res, function (byte) { return String.fromCharCode(byte); }).join(''); } crc = readLong(arr); if (crc !== parseInt(crc32(res), 16)) { throw 'Checksum does not match'; } size = readLong(arr); if (size !== res.length) { throw 'Size of decompressed file not correct'; } return res; } module.exports = { zip: zip, unzip: unzip, get DEFAULT_LEVEL() { return DEFAULT_LEVEL; } }; }());