mirror of
https://github.com/philomena-dev/philomena.git
synced 2025-02-02 04:16:43 +01:00
146 lines
5.5 KiB
TypeScript
146 lines
5.5 KiB
TypeScript
import { crc32, asciiEncode, serialize } from './binary';
|
|
|
|
// eslint-disable-next-line no-unused-vars
|
|
type U8Consumer = (_: Uint8Array) => void;
|
|
|
|
interface FileInfo {
|
|
headerOffset: number;
|
|
byteLength: number;
|
|
crc32: number;
|
|
name: ArrayBuffer;
|
|
}
|
|
|
|
// See https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT
|
|
// for full details of the ZIP format.
|
|
export class Zip {
|
|
fileInfo: { [key: string]: FileInfo };
|
|
offset: number;
|
|
|
|
constructor() {
|
|
this.fileInfo = {};
|
|
this.offset = 0;
|
|
}
|
|
|
|
storeFile(name: string, file: ArrayBuffer, consumer: U8Consumer): void {
|
|
const crc = crc32(file);
|
|
const ns = asciiEncode(name);
|
|
|
|
this.fileInfo[name] = {
|
|
headerOffset: this.offset,
|
|
byteLength: file.byteLength,
|
|
crc32: crc,
|
|
name: ns
|
|
};
|
|
|
|
const localField = serialize([
|
|
[2, 0x0001], /* zip64 local field */
|
|
[2, 0x0010], /* local field length (excl. header) */
|
|
[8, file.byteLength], /* compressed size */
|
|
[8, file.byteLength] /* uncompressed size */
|
|
]);
|
|
|
|
const header = serialize([
|
|
[4, 0x04034b50], /* local header signature */
|
|
[2, 0x002d], /* version = zip64 */
|
|
[2, 0x0000], /* flags = none */
|
|
[2, 0x0000], /* compression = store */
|
|
[2, 0x0000], /* time = 00:00 */
|
|
[2, 0x0000], /* date = 1980-01-01 */
|
|
[4, crc], /* file crc32 */
|
|
[4, 0xffffffff], /* zip64 compressed size */
|
|
[4, 0xffffffff], /* zip64 uncompressed size */
|
|
[2, ns.byteLength], /* length of name */
|
|
[2, localField.byteLength] /* length of local field */
|
|
]);
|
|
|
|
this.offset += header.byteLength + ns.byteLength + localField.byteLength + file.byteLength;
|
|
|
|
consumer(new Uint8Array(header));
|
|
consumer(new Uint8Array(ns));
|
|
consumer(new Uint8Array(localField));
|
|
consumer(new Uint8Array(file));
|
|
}
|
|
|
|
finalize(consumer: U8Consumer): void {
|
|
const cdOff = this.offset;
|
|
let numFiles = 0;
|
|
|
|
for (const name in this.fileInfo) {
|
|
const info = this.fileInfo[name];
|
|
|
|
const cdField = serialize([
|
|
[2, 0x0001], /* zip64 central field */
|
|
[2, 0x0018], /* central field length (excl. header) */
|
|
[8, info.byteLength], /* compressed size */
|
|
[8, info.byteLength], /* uncompressed size */
|
|
[8, info.headerOffset] /* local header offset */
|
|
]);
|
|
|
|
const cdEntry = serialize([
|
|
[4, 0x02014b50], /* CD entry signature */
|
|
[2, 0x002d], /* created with zip64 */
|
|
[2, 0x002d], /* extract with zip64 */
|
|
[2, 0x0000], /* flags = none */
|
|
[2, 0x0000], /* compression = store */
|
|
[2, 0x0000], /* time = 00:00 */
|
|
[2, 0x0000], /* date = 1980-01-01 */
|
|
[4, info.crc32], /* file crc32 */
|
|
[4, 0xffffffff], /* zip64 compressed size */
|
|
[4, 0xffffffff], /* zip64 uncompressed size */
|
|
[2, info.name.byteLength], /* length of name */
|
|
[2, cdField.byteLength], /* length of central field */
|
|
[2, 0x0000], /* comment length */
|
|
[2, 0x0000], /* disk number */
|
|
[2, 0x0000], /* internal attributes */
|
|
[4, 0x00000000], /* external attributes */
|
|
[4, 0xffffffff], /* zip64 local header offset */
|
|
]);
|
|
|
|
this.offset += cdEntry.byteLength + info.name.byteLength + cdField.byteLength;
|
|
|
|
consumer(new Uint8Array(cdEntry));
|
|
consumer(new Uint8Array(info.name));
|
|
consumer(new Uint8Array(cdField));
|
|
|
|
numFiles++;
|
|
}
|
|
|
|
const endCdOff = this.offset;
|
|
const endCd64 = serialize([
|
|
[4, 0x06064b50], /* zip64 end of CD signature */
|
|
[8, 44], /* size of end of CD */
|
|
[2, 0x002d], /* created with zip64 */
|
|
[2, 0x002d], /* extract with zip64 */
|
|
[4, 0x00000000], /* this disk number */
|
|
[4, 0x00000000], /* starting disk number */
|
|
[8, numFiles], /* number of files on this disk */
|
|
[8, numFiles], /* total number of files */
|
|
[8, endCdOff - cdOff], /* size of CD */
|
|
[8, cdOff] /* location of CD */
|
|
]);
|
|
|
|
const endLoc64 = serialize([
|
|
[4, 0x07064b50], /* zip64 end of CD locator */
|
|
[4, 0x00000000], /* disk number of CD */
|
|
[8, endCdOff], /* location of end of CD */
|
|
[4, 1] /* number of disks */
|
|
]);
|
|
|
|
const endCd = serialize([
|
|
[4, 0x06054b50], /* end of CD */
|
|
[2, 0x0000], /* this disk number */
|
|
[2, 0x0000], /* starting disk number */
|
|
[2, numFiles], /* number of files on this disk */
|
|
[2, numFiles], /* total number of files */
|
|
[4, endCdOff - cdOff], /* size of CD */
|
|
[4, 0xffffffff], /* zip64 location of CD */
|
|
[2, 0x0000] /* comment length */
|
|
]);
|
|
|
|
this.offset += endCd64.byteLength + endLoc64.byteLength + endCd.byteLength;
|
|
|
|
consumer(new Uint8Array(endCd64));
|
|
consumer(new Uint8Array(endLoc64));
|
|
consumer(new Uint8Array(endCd));
|
|
}
|
|
}
|