UNPKG

@shockpkg/ria-packager

Version:

Package for creating Adobe AIR packages

725 lines (663 loc) 15.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ZipperEntryExtraField = exports.ZipperEntry = exports.Zipper = void 0; var _nodeZlib = require("node:zlib"); var _iconEncoder = require("@shockpkg/icon-encoder"); /* eslint-disable max-classes-per-file */ /** * Add Extended Timestamp to bit flags and list. * * @param flags Bit flags. * @param times Array of times. * @param time Time to add, or not if null. * @param index Bit index. * @param local Local header or central. * @returns New bit flags. */ function addET(flags, times, time = null, index, local) { if (time !== null) { // eslint-disable-next-line no-bitwise flags |= 1 << index; if (index || local) { times.push(typeof time === 'number' ? time : Math.round(time.getTime() / 1000)); } } return flags; } /** * Zipper write stream interface. * A subset of Writable. */ /** * Zipper Entry Extra Field object. */ class ZipperEntryExtraField { /** * Type ID. */ type = 0; /** * Data for the type. */ data = new Uint8Array(0); /** * Zipper Entry Extra Field constructor. */ constructor() {} /** * Get the encode size. * * @returns Encode size. */ sizeof() { return 4 + this.data.length; } /** * Encode type and data as data. * * @returns Encoded data. */ encode() { const { data, type } = this; const d = new Uint8Array(this.sizeof()); const v = new DataView(d.buffer, d.byteOffset, d.byteLength); v.setUint16(0, type, true); v.setUint16(2, data.length, true); d.set(data, 4); return d; } /** * Init Info-ZIP UNIX type 2 data, local header. * * @param uid User ID. * @param gid Group ID. */ initInfoZipUnix2Local(uid = 0, gid = 0) { this._initInfoZipUnix2(true, uid, gid); } /** * Init Info-ZIP UNIX type 2 data, central header. * * @param uid User ID. * @param gid Group ID. */ initInfoZipUnix2Central(uid = 0, gid = 0) { this._initInfoZipUnix2(false, uid, gid); } /** * Init Extended Timestamp data, local header. * * @param mtime Modification time. * @param atime Access time. * @param ctime Creation time. */ initExtendedTimestampLocal(mtime = null, atime = null, ctime = null) { this._initExtendedTimestamp(true, mtime, atime, ctime); } /** * Init Extended Timestamp data, central header. * * @param mtime Modification time. * @param atime Access time. * @param ctime Creation time. */ initExtendedTimestampCentral(mtime = null, atime = null, ctime = null) { this._initExtendedTimestamp(false, mtime, atime, ctime); } /** * Init Info-ZIP UNIX type 2 data. * * @param local Local header or central. * @param uid User ID. * @param gid Group ID. */ _initInfoZipUnix2(local, uid, gid) { const d = local ? new Uint8Array(4) : new Uint8Array(0); if (local) { const v = new DataView(d.buffer, d.byteOffset, d.byteLength); v.setUint16(0, uid, true); v.setUint16(2, gid, true); } // Type: 'Ux' this.type = 0x7855; this.data = d; } /** * Init Extended Timestamp data. * * @param local Local header or central. * @param mtime Modification time. * @param atime Access time. * @param ctime Creation time. */ _initExtendedTimestamp(local, mtime, atime, ctime) { let flags = 0; const times = []; flags = addET(flags, times, mtime, 0, local); flags = addET(flags, times, atime, 1, local); flags = addET(flags, times, ctime, 2, local); const d = new Uint8Array(1 + times.length * 4); let i = 0; d[i++] = flags; const v = new DataView(d.buffer, d.byteOffset, d.byteLength); for (const time of times) { v.setUint32(i, time, true); i += 4; } // Type: 'UT' this.type = 0x5455; this.data = d; } } /** * Zipper Entry object. */ exports.ZipperEntryExtraField = ZipperEntryExtraField; class ZipperEntry { /** * Tag signature, local header. */ signatureLocal = 0x4034b50; /** * Tag signature, central header. */ signatureCentral = 0x2014b50; /** * Extract version. */ extractVersion = 0x14; /** * Extract host OS. */ extractHostOS = 0; /** * Create version. */ createVersion = 0x14; /** * Create host OS. */ createHostOS = 0; /** * Extract flags. */ flags = 0; /** * Compression type. */ compression = 0; /** * DOS time. */ time = 0; /** * DOS date. */ date = 0; /** * Data CRC32. */ crc32 = 0; /** * Size compressed. */ sizeCompressed = 0; /** * Size uncompressed. */ sizeUncompressed = 0; /** * Disk number start. */ diskNumberStart = 0; /** * Internal attributes. */ internalAttributes = 0; /** * External attributes. */ externalAttributes = 0; /** * Header offset, local header. */ headerOffsetLocal = 0; /** * Entry path. */ path = new Uint8Array(0); /** * Entry comment. */ comment = new Uint8Array(0); /** * Extra fields, local header. */ extraFieldsLocal = []; /** * Extra fields, central header. */ extraFieldsCentral = []; /** * Zipper Entry constructor. */ constructor() {} /** * Get the file record extra fields size. * * @returns Extra fields size. */ sizeofExtraFieldsLocal() { let r = 0; for (const ef of this.extraFieldsLocal) { r += ef.sizeof(); } return r; } /** * Get the file record extra fields size. * * @returns Extra fields size. */ sizeofLocal() { return 30 + this.path.length + this.sizeofExtraFieldsLocal(); } /** * Get the file record extra fields size. * * @returns Extra fields size. */ sizeofExtraFieldsCentral() { let r = 0; for (const ef of this.extraFieldsCentral) { r += ef.sizeof(); } return r; } /** * Get the central record extra fields size. * * @returns Extra fields size. */ sizeofCentral() { return 46 + this.path.length + this.comment.length + this.sizeofExtraFieldsCentral(); } /** * Create new ZipperEntryExtraField object. * * @returns ZipperEntryExtraField object. */ createExtraField() { return new ZipperEntryExtraField(); } /** * Set date from a date object or timestamp. * * @param date Date object or timestamp. */ setDate(date) { const dosTime = this._dateToDosTime(date); this.date = dosTime.date; this.time = dosTime.time; } /** * Get local record data. * * @returns Local record data. */ encodeLocal() { const { path, extraFieldsLocal } = this; const d = new Uint8Array(this.sizeofLocal()); const v = new DataView(d.buffer, d.byteOffset, d.byteLength); let i = 0; v.setUint32(i, this.signatureLocal, true); i += 4; v.setUint8(i++, this.extractVersion); v.setUint8(i++, this.extractHostOS); v.setUint16(i, this.flags, true); i += 2; v.setUint16(i, this.compression, true); i += 2; v.setUint16(i, this.time, true); i += 2; v.setUint16(i, this.date, true); i += 2; v.setUint32(i, this.crc32, true); i += 4; v.setUint32(i, this.sizeCompressed, true); i += 4; v.setUint32(i, this.sizeUncompressed, true); i += 4; v.setUint16(i, path.length, true); i += 2; v.setUint16(i, this.sizeofExtraFieldsLocal(), true); i += 2; d.set(path, i); i += path.length; for (const ef of extraFieldsLocal) { const e = ef.encode(); d.set(e, i); i += e.length; } return d; } /** * Get central record data. * * @returns Central entry data. */ encodeCentral() { const { path, comment, extraFieldsCentral } = this; const d = new Uint8Array(this.sizeofCentral()); const v = new DataView(d.buffer, d.byteOffset, d.byteLength); let i = 0; v.setUint32(i, this.signatureCentral, true); i += 4; v.setUint8(i++, this.createVersion); v.setUint8(i++, this.createHostOS); v.setUint8(i++, this.extractVersion); v.setUint8(i++, this.extractHostOS); v.setUint16(i, this.flags, true); i += 2; v.setUint16(i, this.compression, true); i += 2; v.setUint16(i, this.time, true); i += 2; v.setUint16(i, this.date, true); i += 2; v.setUint32(i, this.crc32, true); i += 4; v.setUint32(i, this.sizeCompressed, true); i += 4; v.setUint32(i, this.sizeUncompressed, true); i += 4; v.setUint16(i, path.length, true); i += 2; v.setUint16(i, this.sizeofExtraFieldsCentral(), true); i += 2; v.setUint16(i, comment.length, true); i += 2; v.setUint16(i, this.diskNumberStart, true); i += 2; v.setUint16(i, this.internalAttributes, true); i += 2; v.setUint32(i, this.externalAttributes, true); i += 4; v.setUint32(i, this.headerOffsetLocal, true); i += 4; d.set(path, i); i += path.length; for (const ef of extraFieldsCentral) { const e = ef.encode(); d.set(e, i); i += e.length; } d.set(comment, i); return d; } /** * Setup data for entry. * * @param data Data for the entry. * @param compress Compress option, true to force, false to disable. * @returns Resulting data, or null if no data passed. */ async initData(data, compress = null) { this.compression = 0; this.crc32 = 0; this.sizeCompressed = 0; this.sizeUncompressed = 0; if (!data) { return null; } const crc32 = this._crc32(data); if (compress === false) { this.crc32 = crc32; this.sizeCompressed = this.sizeUncompressed = data.length; this.compression = 0; return data; } if (compress === true) { const comp = await this._zlibDeflateRaw(data); this.crc32 = crc32; this.sizeUncompressed = data.length; this.sizeCompressed = comp.length; this.compression = 8; return comp; } const comp = await this._zlibDeflateRaw(data); const r = comp.length < data.length ? comp : data; this.crc32 = crc32; this.sizeUncompressed = data.length; this.sizeCompressed = r.length; this.compression = r === data ? 0 : 8; return r; } /** * Add extra fields for Extended Timestamp. * * @param mtime Modification time. * @param atime Access time. * @param ctime Creation time. */ addExtraFieldsExtendedTimestamp(mtime = null, atime = null, ctime = null) { const efl = this.createExtraField(); efl.initExtendedTimestampLocal(mtime, atime, ctime); this.extraFieldsLocal.push(efl); const efc = this.createExtraField(); efc.initExtendedTimestampCentral(mtime, atime, ctime); this.extraFieldsCentral.push(efc); } /** * Add extra fields for Info-ZIP UNIX type 2. * * @param uid User ID. * @param gid Group ID. */ addExtraFieldsInfoZipUnix2(uid = 0, gid = 0) { const efl = this.createExtraField(); efl.initInfoZipUnix2Local(uid, gid); this.extraFieldsLocal.push(efl); const efc = this.createExtraField(); efc.initInfoZipUnix2Central(uid, gid); this.extraFieldsCentral.push(efc); } /** * Convert date from a date object or timestamp. * * @param date Date object or timestamp. * @returns DOS time. */ _dateToDosTime(date) { const d = typeof date === 'number' ? new Date(date * 1000) : date; return { date: // eslint-disable-next-line no-bitwise d.getDate() & 0x1f | // eslint-disable-next-line no-bitwise (d.getMonth() + 1 & 0xf) << 5 | // eslint-disable-next-line no-bitwise (d.getFullYear() - 1980 & 0x7f) << 9, time: // eslint-disable-next-line no-bitwise Math.floor(d.getSeconds() / 2) | // eslint-disable-next-line no-bitwise (d.getMinutes() & 0x3f) << 5 | // eslint-disable-next-line no-bitwise (d.getHours() & 0x1f) << 11 }; } /** * Calculate the CRC32 hash for data. * * @param data Data to be hashed. * @returns CRC32 hash. */ _crc32(data) { // eslint-disable-next-line no-bitwise return (0, _iconEncoder.crc32)(data) >>> 0; } /** * Zlib deflate raw data. * * @param data Data to be compressed. * @returns Compressed data. */ async _zlibDeflateRaw(data) { return new Promise((resolve, reject) => { (0, _nodeZlib.deflateRaw)(data, (err, d) => { if (err) { reject(err); return; } resolve(new Uint8Array(d.buffer, d.byteOffset, d.byteLength)); }); }); } } /** * Zipper, a low-level ZIP file writter. */ exports.ZipperEntry = ZipperEntry; class Zipper { /** * Tag signature. */ signature = 0x6054b50; /** * Archive comment. */ comment = new Uint8Array(0); /** * Added entries. */ entries = []; /** * Current offset. */ _offset = 0; /** * Output stream. */ /** * Zipper constructor. * * @param output Writable stream. */ constructor(output) { this._output = output; } /** * Create new ZipperEntry object. * * @returns ZipperEntry object. */ createEntry() { return new ZipperEntry(); } /** * Add Entry and any associated data. * * @param entry Entry object. * @param data Data from the entry initData method. */ async addEntry(entry, data = null) { const { _offset } = this; const { sizeCompressed } = entry; if (data) { if (data.length !== sizeCompressed) { throw new Error('Data length and compressed size must match'); } } else if (sizeCompressed) { throw new Error('Data required when compressed size not zero'); } entry.headerOffsetLocal = _offset; this.entries.push(entry); await this._writeOutput(entry.encodeLocal()); if (data) { await this._writeOutput(data); } } /** * Close stream. */ async close() { const { _offset, entries, comment } = this; let size = 0; for (const e of entries) { const d = e.encodeCentral(); // eslint-disable-next-line no-await-in-loop await this._writeOutput(d); size += d.length; } const d = new Uint8Array(22 + comment.length); const v = new DataView(d.buffer, d.byteOffset, d.byteLength); let i = 0; v.setUint32(i, this.signature, true); i += 4; v.setUint16(i, 0, true); i += 2; v.setUint16(i, 0, true); i += 2; v.setUint16(i, entries.length, true); i += 2; v.setUint16(i, entries.length, true); i += 2; v.setUint32(i, size, true); i += 4; v.setUint32(i, _offset, true); i += 4; v.setUint16(i, comment.length, true); i += 2; d.set(comment, i); await this._writeOutput(d); await new Promise((resolve, reject) => { this._output.end(err => { if (err) { reject(err); return; } resolve(); }); }); } /** * Write data buffer to output stream. * * @param data Output data. */ async _writeOutput(data) { await new Promise((resolve, reject) => { this._output.write(data, err => { if (err) { reject(err); return; } resolve(); }); }); this._offset += data.length; } } exports.Zipper = Zipper; //# sourceMappingURL=zipper.js.map