UNPKG

@gitcobra/anzip

Version:

super easy way to create an uncompressed zip file.

255 lines (222 loc) 7.3 kB
var AnZip = (function () { 'use strict'; /** * AnZip constructor * @class */ function AnZip() { this.clear(); } /** * initialize data * @return {void} */ AnZip.prototype.clear = function() { /** * directory record * @private { [string]: boolean } */ this._d = {}; /** * local file headers * @private {number[]} */ this._lfh = []; /** * current lfh offset * @private {number} */ this._curLFHind = 0; /** * central directory headers * @private {number[]} */ this._cdh = []; /** * whole central directory header size * @private {number} */ this._cdhLen = 0; /** * file count * @private {number} */ this._c = 0; }; /** * add path and data * @param {string} path * @param {Uint8Array | number[] | ArrayBuffer | Buffer | string} [dat] */ AnZip.prototype.add = function (path, dat) { // check path if (!path) throw new Error('path is empty'); // replace backslash with forward slash path = String(path).replace(/\\/g, '/'); if (/\/{2,}|\\|^\/|^[a-z]+:/i.test(path)) throw new Error('invalid path. containing a drive letter, a leading slash, or empty directory name: "' + path + '"'); // check file var size = 0, crc = 0; if (typeof dat !== 'undefined') { // file name has to be specified if (!/[^/]+$/.test(path)) throw new Error('needs a file name: "' + path + '"'); // convert string to utf-8 binary if (typeof dat === 'string') dat = strToUTF8Array(dat); // check for type other than TypedArray if ( !(dat instanceof A8) ) try { if (dat.buffer || dat instanceof Array || dat instanceof ArrayBuffer || dat instanceof Buffer) dat = new Uint8Array(dat.buffer || dat); else throw new Error; } catch (e) { throw new Error('data must be one of the following types Array, TypedArray, ArrayBuffer, Buffer, or string.'); } // check for duplication if (this.has(path)) throw new Error('the path already exists: ' + path); size = dat.length; crc = getCRC32(dat); } // generate time stamp var d = new Date(); var date = getLE32((d.getFullYear() - 1980) << 25 | (d.getMonth() + 1) << 21 | d.getDate() << 16 | d.getHours() << 11 | d.getMinutes() << 5 | d.getSeconds() / 2); // construct directories var dirs = path.replace(/\/+$/, '').split("/"); var pathstack = ''; while (dirs.length) { // check whether the path already exists pathstack += dirs.shift(); var isFile = dat && (dirs.length === 0); pathstack += (isFile ? '' : '/'); if (this._d[pathstack]) continue; // it's a new path this._d[pathstack] = true; this._c++; var pathbin = strToUTF8Array(pathstack); var pathLen = pathbin.length; var dsize = isFile ? size : 0; var dsizeLE = getLE32(dsize); // local file header // signature(4) this._lfh.push([0x50, 0x4B, 0x03, 0x04]); // *same part as cdh* // version(2) flag(2) comp_method(2) time(4) CRC(4) size(4) size(4) pathLength(2) extraLength(2) var dup = [0x0A, 0x00, 0x00, 0x08, 0x00, 0x00].concat(date, getLE32(isFile ? crc : 0), dsizeLE, dsizeLE, [pathLen & 0xFF, pathLen >> 8 & 0xFF, 0x00, 0x00]); this._lfh.push(dup, pathbin); // file name(*)... // central directory header // signature(4) app version(2) dup(*) comment length(2) disk(2) in-attr(2) ex-attr(4) offset(4) filename(*) ... this._cdh.push([0x50, 0x4B, 0x01, 0x02, 0x0A, 0x00].concat(dup, [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, isFile ? 0x00 : 0x10, 0x00, 0x00, 0x00], getLE32(this._curLFHind)), pathbin); this._cdhLen += 46 + pathLen; this._curLFHind += 30 + pathLen + dsize; } // add file data if (dat) this._lfh.push(dat); }; /** * return whether the path already exists * @param {string} path * @return {boolean} */ AnZip.prototype.has = function (path) { return !!this._d[path.replace(/\/+$/, '')]; }; /** * output as new Uint8Array (or Array) * @return {Uint8Array} */ AnZip.prototype.zip = function () { // End of central directory record // signature(4) // number of this disk(2), file count on disk(2), file count(2), size(4), offset(4), comment length(2) var ecd = [0x50, 0x4B, 0x05, 0x06, 0x00, 0x00, 0x00, 0x00, this._c & 0xFF, this._c >> 8, this._c & 0xFF, this._c >> 8] .concat(getLE32(this._cdhLen), getLE32(this._curLFHind), [0x00, 0x00]); // join all binary data var output; var arrayChain = this._lfh.concat(this._cdh, [ecd]); // Array if ( A8 === Array ) { output = Array.prototype.concat.apply([], arrayChain); } // Uint8Array else { var offset = 0; output = new A8(this._curLFHind + this._cdhLen + ecd.length); for (var i = 0; i < arrayChain.length; i++) { var n = arrayChain[i]; output.set(n, offset); offset += n.length; } } return output; }; /** * @return {ArrayBuffer | Buffer} */ AnZip.prototype.buffer = function() { return UseTA ? this.zip().buffer : null; }; /** * @return {Blob} */ AnZip.prototype.blob = function() { if( typeof Blob !== 'function') return null; return new Blob([this.zip()], {type: 'application/zip'}); }; /** * output as dataURL * @return {string} */ AnZip.prototype.url = function() { var blob = this.blob(); if( !blob || typeof URL !== 'function' || typeof URL.createObjectURL !== 'function' ) return ''; var src = URL.createObjectURL(this.blob()); return src; }; // use TypedArray or Array? var UseTA = typeof Uint8Array !== 'undefined'; var A8 = UseTA ? Uint8Array : Array, A32 = UseTA ? Uint32Array : Array; // create CRC32 table var CRC32Table = new A32(256); for (var i = 0; i < 256; i++) { var val = i; for (var j = 0; j < 8; j++) { val = (val & 1) ? (0xEDB88320 ^ (val >>> 1)) : (val >>> 1); } CRC32Table[i] = val; } function getCRC32(dat) { var crc = 0xFFFFFFFF; for (var i = 0, len = dat.length; i < len; i++) { crc = CRC32Table[(crc ^ dat[i]) & 0xFF] ^ (crc >>> 8); } return (crc ^ 0xFFFFFFFF) >>> 0; } // encode to UTF-8 function strToUTF8Array(str) { var a; // for modern browsers if( typeof TextEncoder === 'function' ) { a = new TextEncoder('utf-8').encode(str); } // for the old environments else { a = []; encodeURI(str).replace(/%(..)|(.)/g, function (m, $hex, $chr) { a.push($hex ? parseInt($hex, 16) : $chr.charCodeAt(0)); }); } return a; } // 32bit number to little-endian byte array function getLE32(num) { return [num & 0xFF, num >> 8 & 0xFF, num >> 16 & 0xFF, num >> 24 & 0xFF]; } return AnZip; })();