var zip = zip || {}; var zlib = zlib || {}; zip.Archive = class { static open(data) { const stream = data instanceof Uint8Array ? new zip.BinaryReader(data) : data; if (stream.length > 2) { const buffer = stream.peek(2); if (buffer[0] === 0x78) { // zlib const check = (buffer[0] << 8) + buffer[1]; if (check % 31 === 0) { return new zlib.Archive(stream); } } const signature = buffer[0] === 0x50 && buffer[1] === 0x4B; const position = stream.position; const seek = (content) => { let position = stream.length; do { position = Math.max(0, position - 66000); stream.seek(position); const length = Math.min(stream.length - position, 66000 + 4); const buffer = stream.read(length); for (let i = buffer.length - 4; i >= 0; i--) { if (content[0] === buffer[i] && content[1] === buffer[i + 1] && content[2] === buffer[i + 2] && content[3] === buffer[i + 3]) { stream.seek(position + i + 4); return true; } } if (!signature) { break; } } while (position > 0); return false; }; if (!seek([ 0x50, 0x4B, 0x05, 0x06 ])) { stream.seek(position); if (!signature) { return null; } throw new zip.Error('End of Zip central directory not found.'); } const reader = new zip.BinaryReader(stream.read(16)); reader.skip(12); let offset = reader.uint32(); // central directory offset if (offset > stream.length) { if (!seek([ 0x50, 0x4B, 0x06, 0x06 ])) { stream.seek(position); throw new zip.Error('End of Zip64 central directory not found.'); } const reader = new zip.BinaryReader(stream.read(52)); reader.skip(44); offset = reader.uint32(); if (reader.uint32() !== 0) { stream.seek(position); throw new zip.Error('Zip 64-bit central directory offset not supported.'); } } if (offset > stream.length) { stream.seek(position); throw new zip.Error('Invalid Zip central directory offset.'); } stream.seek(offset); const archive = new zip.Archive(stream); stream.seek(position); return archive; } return null; } constructor(stream) { this._entries = new Map(); const headers = []; const signature = [ 0x50, 0x4B, 0x01, 0x02 ]; while (stream.position + 4 < stream.length && stream.read(4).every((value, index) => value === signature[index])) { const header = {}; const reader = new zip.BinaryReader(stream.read(42)); reader.uint16(); // version made by reader.skip(2); // version needed to extract const flags = reader.uint16(); if ((flags & 1) == 1) { throw new zip.Error('Encrypted Zip entries not supported.'); } header.encoding = flags & 0x800 ? 'utf-8' : 'ascii'; header.compressionMethod = reader.uint16(); reader.uint32(); // date reader.uint32(); // crc32 header.compressedSize = reader.uint32(); header.size = reader.uint32(); header.nameLength = reader.uint16(); // file name length const extraDataLength = reader.uint16(); const commentLength = reader.uint16(); header.disk = reader.uint16(); // disk number start reader.uint16(); // internal file attributes reader.uint32(); // external file attributes header.localHeaderOffset = reader.uint32(); const nameBuffer = stream.read(header.nameLength); const decoder = new TextDecoder(header.encoding); header.name = decoder.decode(nameBuffer); const extraData = stream.read(extraDataLength); if (extraData.length > 0) { const reader = new zip.BinaryReader(extraData); while (reader.position < reader.length) { const type = reader.uint16(); const length = reader.uint16(); switch (type) { case 0x0001: if (header.size === 0xffffffff) { header.size = reader.uint32(); if (reader.uint32() !== 0) { throw new zip.Error('Zip 64-bit offset not supported.'); } } if (header.compressedSize === 0xffffffff) { header.compressedSize = reader.uint32(); if (reader.uint32() !== 0) { throw new zip.Error('Zip 64-bit offset not supported.'); } } if (header.localHeaderOffset === 0xffffffff) { header.localHeaderOffset = reader.uint32(); if (reader.uint32() !== 0) { throw new zip.Error('Zip 64-bit offset not supported.'); } } if (header.disk === 0xffff) { header.disk = reader.uint32(); } break; default: reader.skip(length); break; } } } stream.read(commentLength); // comment headers.push(header); } for (const header of headers) { if (header.size === 0 && header.name.endsWith('/')) { continue; } const entry = new zip.Entry(stream, header); this._entries.set(entry.name, entry.stream); } } get entries() { return this._entries; } }; zip.Entry = class { constructor(stream, header) { stream.seek(header.localHeaderOffset); const signature = [ 0x50, 0x4B, 0x03, 0x04 ]; if (stream.position + 4 > stream.length || !stream.read(4).every((value, index) => value === signature[index])) { throw new zip.Error('Invalid Zip local file header signature.'); } const reader = new zip.BinaryReader(stream.read(26)); reader.skip(22); header.nameLength = reader.uint16(); const extraDataLength = reader.uint16(); header.nameBuffer = stream.read(header.nameLength); stream.skip(extraDataLength); const decoder = new TextDecoder(header.encoding); this._name = decoder.decode(header.nameBuffer); this._stream = stream.stream(header.compressedSize); switch (header.compressionMethod) { case 0: { // stored if (header.size !== header.compressedSize) { throw new zip.Error('Invalid compression size.'); } break; } case 8: { // deflate this._stream = new zip.InflaterStream(this._stream, header.size); break; } default: throw new zip.Error('Invalid compression method.'); } } get name() { return this._name; } get stream() { return this._stream; } }; zip.Inflater = class { inflateRaw(data, length) { let buffer = null; if (typeof process === 'object' && typeof process.versions == 'object' && typeof process.versions.node !== 'undefined') { buffer = require('zlib').inflateRawSync(data); } else { const reader = new zip.BitReader(data); const writer = length === undefined ? new zip.BlockWriter() : new zip.BufferWriter(length); if (!zip.Inflater._staticLengthTree) { zip.Inflater._codeLengths = new Uint8Array(19); zip.Inflater._codeOrder = [ 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 ]; zip.Inflater._lengthBase = [ 24, 32, 40, 48, 56, 64, 72, 80, 89, 105, 121, 137, 154, 186, 218, 250, 283, 347, 411, 475, 540, 668, 796, 924, 1053, 1309, 1565, 1821, 2064, 7992, 7992, 7992 ]; zip.Inflater._distanceBase = [ 16, 32, 48, 64, 81, 113, 146, 210, 275, 403, 532, 788, 1045, 1557, 2070, 3094, 4119, 6167, 8216, 12312, 16409, 24601, 32794, 49178, 65563, 98331, 131100, 196636, 262173, 393245, 1048560, 1048560 ]; } let type; do { type = reader.bits(3); switch (type >>> 1) { case 0: { // uncompressed block this._copyUncompressedBlock(reader, writer); break; } case 1: { // block with fixed huffman trees if (!zip.Inflater._staticLengthTree) { zip.Inflater._staticLengthTree = zip.HuffmanTree.create(new Uint8Array([].concat.apply([], [[144, 8], [112, 9], [24, 7], [8, 8]].map((x) => [...Array(x[0])].map(() => x[1]))))); zip.Inflater._staticDistanceTree = zip.HuffmanTree.create(new Uint8Array([...Array(32)].map(() => 5))); } this._lengthTree = zip.Inflater._staticLengthTree; this._distanceTree = zip.Inflater._staticDistanceTree; this._inflateBlock(reader, writer); break; } case 2: { // block with dynamic huffman trees this._decodeTrees(reader); this._inflateBlock(reader, writer); break; } default: { throw new zip.Error('Unknown block type.'); } } } while ((type & 1) == 0); if (length !== undefined && length !== writer.length) { throw new zip.Error('Invalid uncompressed size.'); } buffer = writer.toBuffer(); } if (length !== undefined && length !== buffer.length) { throw new zip.Error('Invalid uncompressed size.'); } return buffer; } _copyUncompressedBlock(reader, writer) { const length = reader.uint16(); const inverseLength = reader.uint16(); if (length !== (~inverseLength & 0xffff)) { throw new zip.Error('Invalid uncompressed block length.'); } writer.write(reader.read(length)); } _decodeTrees(reader) { const hlit = reader.bits(5) + 257; const hdist = reader.bits(5) + 1; const hclen = reader.bits(4) + 4; const codeLengths = zip.Inflater._codeLengths; for (let i = 0; i < codeLengths.length; i++) { codeLengths[i] = 0; } const codeOrder = zip.Inflater._codeOrder; for (let i = 0; i < hclen; i++) { codeLengths[codeOrder[i]] = reader.bits(3); } const codeTree = zip.HuffmanTree.create(codeLengths); const codeMask = codeTree.length - 1; const lengths = new Uint8Array(hlit + hdist); let value = 0; let length = 0; for (let i = 0; i < hlit + hdist;) { const code = codeTree[reader.bits16() & codeMask]; reader.position += code & 0x0f; const literal = code >>> 4; switch (literal) { case 16: length = reader.bits(2) + 3; break; case 17: length = reader.bits(3) + 3; value = 0; break; case 18: length = reader.bits(7) + 11; value = 0; break; default: length = 1; value = literal; break; } for (; length > 0; length--) { lengths[i++] = value; } } this._lengthTree = zip.HuffmanTree.create(lengths.subarray(0, hlit)); this._distanceTree = zip.HuffmanTree.create(lengths.subarray(hlit, hlit + hdist)); } _inflateBlock(reader, writer) { const lengthTree = this._lengthTree; const distanceTree = this._distanceTree; const lengthMask = lengthTree.length - 1; const distanceMask = distanceTree.length - 1; const buffer = writer.buffer; const threshold = writer.threshold !== undefined ? writer.threshold : writer.length; let position = writer.position; for (;;) { if (position > threshold) { position = writer.push(position); } const code = lengthTree[reader.bits16() & lengthMask]; reader.position += code & 0x0f; const literal = code >>> 4; if (literal < 256) { buffer[position++] = literal; } else if (literal === 256) { writer.push(position); return; } else { let length = literal - 254; if (literal > 264) { const lengthBase = zip.Inflater._lengthBase[literal - 257]; length = (lengthBase >>> 3) + reader.bits(lengthBase & 0x07); } const code = distanceTree[reader.bits16() & distanceMask]; reader.position += code & 0x0f; const distanceBase = zip.Inflater._distanceBase[code >>> 4]; const bits = distanceBase & 0x0f; const distance = (distanceBase >>> 4) + (reader.bits16() & ((1 << bits) - 1)); reader.position += bits; let offset = position - distance; for (let i = 0; i < length; i++) { buffer[position++] = buffer[offset++]; } } } } }; zip.HuffmanTree = class { static create(tree) { let bits = tree[0]; for (let i = 1; i < tree.length; ++i) { if (tree[i] > bits) { bits = tree[i]; } } // Algorithm from https://github.com/photopea/UZIP.js let rev15 = zip.HuffmanTree._rev15; if (!rev15) { const length = 1 << 15; rev15 = new Uint16Array(length); for (let i = 0; i < length; i++) { let x = i; x = (((x & 0xaaaaaaaa) >>> 1) | ((x & 0x55555555) << 1)); x = (((x & 0xcccccccc) >>> 2) | ((x & 0x33333333) << 2)); x = (((x & 0xf0f0f0f0) >>> 4) | ((x & 0x0f0f0f0f) << 4)); x = (((x & 0xff00ff00) >>> 8) | ((x & 0x00ff00ff) << 8)); rev15[i] = (((x >>> 16) | (x << 16))) >>> 17; } zip.HuffmanTree._rev15 = rev15; zip.HuffmanTree._bitLengthCounts = new Uint16Array(16); zip.HuffmanTree._nextCodes = new Uint16Array(16); } const length = tree.length; const bitLengthCounts = zip.HuffmanTree._bitLengthCounts; for (let i = 0; i < 16; i++) { bitLengthCounts[i] = 0; } for (let i = 0; i < length; i++) { bitLengthCounts[tree[i]]++; } const nextCodes = zip.HuffmanTree._nextCodes; let code = 0; bitLengthCounts[0] = 0; for (let i = 0; i < bits; i++) { code = (code + bitLengthCounts[i]) << 1; nextCodes[i + 1] = code; } const codes = new Uint16Array(length); for (let i = 0; i < length; i++) { const index = tree[i]; if (index !== 0) { codes[i] = nextCodes[index]; nextCodes[index]++; } } const shift = 15 - bits; const table = new Uint16Array(1 << bits); for (let i = 0; i < length; i++) { const c = tree[i]; if (c !== 0) { const value = (i << 4) | c; const rest = bits - c; let index = codes[i] << rest; const max = index + (1 << rest); for (; index != max; index++) { table[rev15[index] >>> shift] = value; } } } return table; } }; zip.BitReader = class { constructor(buffer) { this.buffer = buffer; this.position = 0; } bits(count) { const offset = (this.position / 8) >> 0; const shift = (this.position & 7); this.position += count; return ((this.buffer[offset] | (this.buffer[offset + 1] << 8)) >>> shift) & ((1 << count) - 1); } bits16() { const offset = (this.position / 8) >> 0; return ((this.buffer[offset] | (this.buffer[offset + 1] << 8) | (this.buffer[offset + 2] << 16)) >>> (this.position & 7)); } read(length) { this.position = (this.position + 7) & ~7; // align const offset = (this.position / 8) >> 0; this.position += length * 8; return this.buffer.subarray(offset, offset + length); } uint16() { this.position = (this.position + 7) & ~7; // align const offset = (this.position / 8) >> 0; this.position += 16; return this.buffer[offset] | (this.buffer[offset + 1] << 8); } }; zip.BlockWriter = class { constructor() { this.blocks = []; this.buffer = new Uint8Array(65536); this.position = 0; this.length = 0; this.threshold = 0xf400; } push(position) { this.blocks.push(new Uint8Array(this.buffer.subarray(this.position, position))); this.length += position - this.position; this.position = position; return this._reset(); } write(buffer) { this.blocks.push(buffer); const length = buffer.length; this.length += length; if (length > 32768) { this.buffer.set(buffer.subarray(length - 32768, length), 0); this.position = 32768; } else { this._reset(); this.buffer.set(buffer, this.position); this.position += length; } } toBuffer() { const buffer = new Uint8Array(this.length); let offset = 0; for (const block of this.blocks) { buffer.set(block, offset); offset += block.length; } return buffer; } _reset() { if (this.position > 32768) { this.buffer.set(this.buffer.subarray(this.position - 32768, this.position), 0); this.position = 32768; } return this.position; } }; zip.BufferWriter = class { constructor(length) { this.buffer = new Uint8Array(length); this.length = length; this.position = 0; } push(position) { this.position = position; if (this.position > this.length) { throw new zip.Error('Invalid size.'); } return this.position; } write(buffer) { this.buffer.set(buffer, this.position); this.position += buffer.length; if (this.position > this.length) { throw new zip.Error('Invalid size.'); } return this.position; } toBuffer() { return this.buffer; } }; zip.InflaterStream = class { constructor(stream, length) { this._stream = stream; this._offset = this._stream.position; this._position = 0; this._length = length; } get position() { return this._position; } get length() { if (this._length === undefined) { this._inflate(); } return this._length; } seek(position) { if (this._buffer === undefined) { this._inflate(); } this._position = position >= 0 ? position : this._length + position; } skip(offset) { if (this._buffer === undefined) { this._inflate(); } this._position += offset; } peek(length) { const position = this._position; length = length !== undefined ? length : this._length - position; this.skip(length); const end = this._position; this.seek(position); if (position === 0 && length === this._length) { return this._buffer; } return this._buffer.subarray(position, end); } read(length) { const position = this._position; length = length !== undefined ? length : this._length - position; this.skip(length); if (position === 0 && length === this._length) { return this._buffer; } return this._buffer.subarray(position, this._position); } byte() { const position = this._position; this.skip(1); return this._buffer[position]; } _inflate() { if (this._buffer === undefined) { const position = this._stream.position; this._stream.seek(this._offset); const buffer = this._stream.peek(); this._buffer = new zip.Inflater().inflateRaw(buffer, this._length); this._length = this._buffer.length; this._stream.seek(position); delete this._stream; } } }; zip.BinaryReader = class { constructor(buffer) { this._buffer = buffer; this._length = buffer.length; this._position = 0; this._view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength); } get position() { return this._position; } get length() { return this._length; } create(buffer) { return new zip.BinaryReader(buffer); } stream(length) { return this.create(this.read(length)); } seek(position) { this._position = position >= 0 ? position : this._length + position; } skip(offset) { this._position += offset; } peek(length) { if (this._position === 0 && length === undefined) { return this._buffer; } const position = this._position; this.skip(length !== undefined ? length : this._length - this._position); const end = this._position; this.seek(position); return this._buffer.subarray(position, end); } read(length) { if (this._position === 0 && length === undefined) { this._position = this._length; return this._buffer; } const position = this._position; this.skip(length !== undefined ? length : this._length - this._position); return this._buffer.subarray(position, this._position); } byte() { const position = this._position; this.skip(1); return this._buffer[position]; } uint16() { const position = this._position; this.skip(2); return this._view.getUint16(position, true); } uint32() { const position = this._position; this.skip(4); return this._view.getUint32(position, true); } }; zlib.Archive = class { constructor(stream) { const position = stream.position; stream.read(2); const entry = new zlib.Entry(stream); this._entries = new Map([ [ entry.name, entry.stream ] ]); stream.seek(position); } get entries() { return this._entries; } }; zlib.Entry = class { constructor(stream) { this._stream = new zip.InflaterStream(stream); } get name() { return ''; } get stream() { return this._stream; } }; zip.Error = class extends Error { constructor(message) { super(message); this.name = 'Zip Error'; this.stack = undefined; } }; if (typeof module !== 'undefined' && typeof module.exports === 'object') { module.exports.Archive = zip.Archive; module.exports.Inflater = zip.Inflater; }