@shockpkg/ria-packager
Version:
Package for creating Adobe AIR packages
717 lines (655 loc) • 15.3 kB
JavaScript
/* eslint-disable max-classes-per-file */
import { deflateRaw } from 'node:zlib';
import { crc32 } from '@shockpkg/icon-encoder';
/**
* 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.
*/
export 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.
*/
export 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 crc32(data) >>> 0;
}
/**
* Zlib deflate raw data.
*
* @param data Data to be compressed.
* @returns Compressed data.
*/
async _zlibDeflateRaw(data) {
return new Promise((resolve, reject) => {
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.
*/
export 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;
}
}
//# sourceMappingURL=zipper.mjs.map