123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186 |
- import crc32 from 'crc/crc32';
- const encoder = new TextEncoder();
- function dosDateTime(dateTime = new Date()) {
- const year = (dateTime.getFullYear() - 1980) << 9;
- const month = (dateTime.getMonth() + 1) << 5;
- const day = dateTime.getDate();
- const date = year | month | day;
- const hour = dateTime.getHours() << 11;
- const minute = dateTime.getMinutes() << 5;
- const second = Math.floor(dateTime.getSeconds() / 2);
- const time = hour | minute | second;
- return { date, time };
- }
- class File {
- constructor(info) {
- this.name = encoder.encode(info.name);
- this.size = info.size;
- this.bytesRead = 0;
- this.crc = null;
- this.dateTime = dosDateTime();
- }
- get header() {
- const h = new ArrayBuffer(30 + this.name.byteLength);
- const v = new DataView(h);
- v.setUint32(0, 0x04034b50, true); // sig
- v.setUint16(4, 20, true); // version
- v.setUint16(6, 0x808, true); // bit flags (use data descriptor(8) + utf8-encoded(8 << 8))
- v.setUint16(8, 0, true); // compression
- v.setUint16(10, this.dateTime.time, true); // modified time
- v.setUint16(12, this.dateTime.date, true); // modified date
- v.setUint32(14, 0, true); // crc32 (in descriptor)
- v.setUint32(18, 0, true); // compressed size (in descriptor)
- v.setUint32(22, 0, true); // uncompressed size (in descriptor)
- v.setUint16(26, this.name.byteLength, true); // name length
- v.setUint16(28, 0, true); // extra field length
- for (let i = 0; i < this.name.byteLength; i++) {
- v.setUint8(30 + i, this.name[i]);
- }
- return new Uint8Array(h);
- }
- get dataDescriptor() {
- const dd = new ArrayBuffer(16);
- const v = new DataView(dd);
- v.setUint32(0, 0x08074b50, true); // sig
- v.setUint32(4, this.crc, true); // crc32
- v.setUint32(8, this.size, true); // compressed size
- v.setUint32(12, this.size, true); // uncompressed size
- return new Uint8Array(dd);
- }
- directoryRecord(offset) {
- const dr = new ArrayBuffer(46 + this.name.byteLength);
- const v = new DataView(dr);
- v.setUint32(0, 0x02014b50, true); // sig
- v.setUint16(4, 20, true); // version made
- v.setUint16(6, 20, true); // version required
- v.setUint16(8, 0x808, true); // bit flags (use data descriptor(8) + utf8-encoded(8 << 8))
- v.setUint16(10, 0, true); // compression
- v.setUint16(12, this.dateTime.time, true); // modified time
- v.setUint16(14, this.dateTime.date, true); // modified date
- v.setUint32(16, this.crc, true); // crc
- v.setUint32(20, this.size, true); // compressed size
- v.setUint32(24, this.size, true); // uncompressed size
- v.setUint16(28, this.name.byteLength, true); // name length
- v.setUint16(30, 0, true); // extra length
- v.setUint16(32, 0, true); // comment length
- v.setUint16(34, 0, true); // disk number
- v.setUint16(36, 0, true); // internal file attrs
- v.setUint32(38, 0, true); // external file attrs
- v.setUint32(42, offset, true); // file offset
- for (let i = 0; i < this.name.byteLength; i++) {
- v.setUint8(46 + i, this.name[i]);
- }
- return new Uint8Array(dr);
- }
- get byteLength() {
- return this.size + this.name.byteLength + 30 + 16;
- }
- append(data, controller) {
- this.bytesRead += data.byteLength;
- const endIndex = data.byteLength - Math.max(this.bytesRead - this.size, 0);
- const buf = data.slice(0, endIndex);
- this.crc = crc32(buf, this.crc);
- controller.enqueue(buf);
- if (endIndex < data.byteLength) {
- return data.slice(endIndex, data.byteLength);
- }
- }
- }
- function centralDirectory(files, controller) {
- let directoryOffset = 0;
- let directorySize = 0;
- for (let i = 0; i < files.length; i++) {
- const file = files[i];
- const record = file.directoryRecord(directoryOffset);
- directoryOffset += file.byteLength;
- controller.enqueue(record);
- directorySize += record.byteLength;
- }
- controller.enqueue(eod(files.length, directorySize, directoryOffset));
- }
- function eod(fileCount, directorySize, directoryOffset) {
- const e = new ArrayBuffer(22);
- const v = new DataView(e);
- v.setUint32(0, 0x06054b50, true); // sig
- v.setUint16(4, 0, true); // disk number
- v.setUint16(6, 0, true); // directory disk
- v.setUint16(8, fileCount, true); // number of records
- v.setUint16(10, fileCount, true); // total records
- v.setUint32(12, directorySize, true); // size of directory
- v.setUint32(16, directoryOffset, true); // offset of directory
- v.setUint16(20, 0, true); // comment length
- return new Uint8Array(e);
- }
- class ZipStreamController {
- constructor(files, source) {
- this.files = files;
- this.fileIndex = 0;
- this.file = null;
- this.reader = source.getReader();
- this.nextFile();
- this.extra = null;
- }
- nextFile() {
- this.file = this.files[this.fileIndex++];
- }
- async pull(controller) {
- if (!this.file) {
- // end of archive
- centralDirectory(this.files, controller);
- return controller.close();
- }
- if (this.file.bytesRead === 0) {
- // beginning of file
- controller.enqueue(this.file.header);
- if (this.extra) {
- this.extra = this.file.append(this.extra, controller);
- }
- }
- if (this.file.bytesRead >= this.file.size) {
- // end of file
- controller.enqueue(this.file.dataDescriptor);
- this.nextFile();
- return this.pull(controller);
- }
- const data = await this.reader.read();
- if (data.done) {
- this.nextFile();
- return this.pull(controller);
- }
- this.extra = this.file.append(data.value, controller);
- }
- }
- export default class Zip {
- constructor(manifest, source) {
- this.files = manifest.files.map(info => new File(info));
- this.source = source;
- }
- get stream() {
- return new ReadableStream(new ZipStreamController(this.files, this.source));
- }
- get size() {
- const entries = this.files.reduce(
- (total, file) => total + file.byteLength * 2 - file.size,
- 0
- );
- const eod = 22;
- return entries + eod;
- }
- }
|