@tokenizer/inflate
Advanced tools
+3
-1
@@ -15,3 +15,2 @@ import type { ITokenizer } from 'strtok3'; | ||
| private syncBuffer; | ||
| private entries; | ||
| constructor(tokenizer: ITokenizer); | ||
@@ -23,2 +22,5 @@ isZip(): Promise<boolean>; | ||
| unzip(fileCb: InflateFileFilter): Promise<void>; | ||
| private iterateOverCentralDirectory; | ||
| private inflate; | ||
| private readLocalFileHeader; | ||
| } |
+66
-52
@@ -54,9 +54,14 @@ import { StringType, UINT32_LE } from 'token-types'; | ||
| const eocdHeader = await this.tokenizer.readToken(EndOfCentralDirectoryRecordToken, offset); | ||
| const files = new Array(eocdHeader.nrOfEntriesOfSize); | ||
| const files = []; | ||
| this.tokenizer.setPosition(eocdHeader.offsetOfStartOfCd); | ||
| for (let n = 0; n < files.length; ++n) { | ||
| for (let n = 0; n < eocdHeader.nrOfEntriesOfSize; ++n) { | ||
| const entry = await this.tokenizer.readToken(FileHeader); | ||
| if (entry.signature !== Signature.CentralFileHeader) { | ||
| throw new Error('Expected Central-File-Header signature'); | ||
| } | ||
| entry.filename = await this.tokenizer.readToken(new StringType(entry.filenameLength, 'utf-8')); | ||
| files[n] = entry; | ||
| debug(`Add central-directory file-entry: n=${n}/${files.length}: filename=${files[n].filename}`); | ||
| await this.tokenizer.ignore(entry.extraFieldLength); | ||
| await this.tokenizer.ignore(entry.fileCommentLength); | ||
| files.push(entry); | ||
| debug(`Add central-directory file-entry: n=${n + 1}/${files.length}: filename=${files[n].filename}`); | ||
| } | ||
@@ -69,26 +74,13 @@ this.tokenizer.setPosition(pos); | ||
| async unzip(fileCb) { | ||
| let entry = 0; | ||
| const entries = await this.readCentralDirectory(); | ||
| if (entries) { | ||
| // Use Central Directory to iterate over files | ||
| return this.iterateOverCentralDirectory(entries, fileCb); | ||
| } | ||
| // Scan Zip files for local-file-header | ||
| let stop = false; | ||
| do { | ||
| let zipHeader = undefined; | ||
| if (this.entries) { | ||
| // Use Central Director entry | ||
| zipHeader = this.entries[entry]; | ||
| await this.tokenizer.ignore(LocalFileHeaderToken.len + zipHeader.filenameLength); | ||
| } | ||
| else { | ||
| const signature = await this.tokenizer.peekToken(UINT32_LE); | ||
| switch (signature) { | ||
| case Signature.LocalFileHeader: | ||
| zipHeader = await this.tokenizer.readToken(LocalFileHeaderToken); | ||
| zipHeader.filename = await this.tokenizer.readToken(new StringType(zipHeader.filenameLength, 'utf-8')); | ||
| break; | ||
| case Signature.CentralFileHeader: | ||
| break; | ||
| default: | ||
| throw new Error('Unexpected signature'); | ||
| } | ||
| if (!zipHeader) | ||
| return; | ||
| } | ||
| const zipHeader = await this.readLocalFileHeader(); | ||
| if (!zipHeader) | ||
| break; | ||
| const next = fileCb(zipHeader); | ||
@@ -98,8 +90,2 @@ stop = !!next.stop; | ||
| await this.tokenizer.ignore(zipHeader.extraFieldLength); | ||
| if (entry === 0 && zipHeader.dataDescriptor) { | ||
| this.entries = await this.readCentralDirectory(); | ||
| if (this.entries) { | ||
| zipHeader = this.entries[entry]; | ||
| } | ||
| } | ||
| if (zipHeader.dataDescriptor && zipHeader.compressedSize === 0) { | ||
@@ -125,25 +111,14 @@ const chunks = []; | ||
| debug(`Found data-descriptor-signature at pos=${this.tokenizer.position}`); | ||
| fileData = mergeArrays(chunks); | ||
| // Set position to next ZIP header | ||
| if (next.handler) { | ||
| await this.inflate(zipHeader, mergeArrays(chunks), next.handler); | ||
| } | ||
| } | ||
| if (next.handler) { | ||
| if (!fileData) { | ||
| else { | ||
| if (next.handler) { | ||
| debug(`Reading compressed-file-data: ${zipHeader.compressedSize} bytes`); | ||
| fileData = new Uint8Array(zipHeader.compressedSize); | ||
| await this.tokenizer.readBuffer(fileData); | ||
| await this.inflate(zipHeader, fileData, next.handler); | ||
| } | ||
| // Extract file data | ||
| if (!fileData) | ||
| throw new Error('fileData should be assigned'); | ||
| if (zipHeader.compressedMethod === 0) { | ||
| await next.handler(fileData); | ||
| } | ||
| else { | ||
| debug(`Decompress filename=${zipHeader.filename}, compressed-size=${fileData.length}`); | ||
| const uncompressedData = decompressSync(fileData); | ||
| await next.handler(uncompressedData); | ||
| } | ||
| } | ||
| else { | ||
| if (!fileData) { | ||
| debug(`Ignoring compressed-file-data: ${zipHeader.compressedSize} bytes`); | ||
@@ -155,2 +130,3 @@ await this.tokenizer.ignore(zipHeader.compressedSize); | ||
| if (zipHeader.dataDescriptor) { | ||
| // await this.tokenizer.ignore(DataDescriptor.len); | ||
| const dataDescriptor = await this.tokenizer.readToken(DataDescriptor); | ||
@@ -161,6 +137,44 @@ if (dataDescriptor.signature !== 0x08074b50) { | ||
| } | ||
| ++entry; | ||
| debug(`Completed file iteration at=${this.tokenizer.position}`); | ||
| } while (!stop && (!this.entries || entry < this.entries.length)); | ||
| } while (!stop); | ||
| } | ||
| async iterateOverCentralDirectory(entries, fileCb) { | ||
| for (const fileHeader of entries) { | ||
| const next = fileCb(fileHeader); | ||
| if (next.handler) { | ||
| this.tokenizer.setPosition(fileHeader.relativeOffsetOfLocalHeader); | ||
| const zipHeader = await this.readLocalFileHeader(); | ||
| if (zipHeader) { | ||
| await this.tokenizer.ignore(zipHeader.extraFieldLength); | ||
| const fileData = new Uint8Array(fileHeader.compressedSize); | ||
| await this.tokenizer.readBuffer(fileData); | ||
| await this.inflate(zipHeader, fileData, next.handler); | ||
| } | ||
| } | ||
| if (next.stop) | ||
| break; | ||
| } | ||
| } | ||
| inflate(zipHeader, fileData, cb) { | ||
| if (zipHeader.compressedMethod === 0) { | ||
| return cb(fileData); | ||
| } | ||
| debug(`Decompress filename=${zipHeader.filename}, compressed-size=${fileData.length}`); | ||
| const uncompressedData = decompressSync(fileData); | ||
| return cb(uncompressedData); | ||
| } | ||
| async readLocalFileHeader() { | ||
| const signature = await this.tokenizer.peekToken(UINT32_LE); | ||
| if (signature === Signature.LocalFileHeader) { | ||
| const header = await this.tokenizer.readToken(LocalFileHeaderToken); | ||
| header.filename = await this.tokenizer.readToken(new StringType(header.filenameLength, 'utf-8')); | ||
| return header; | ||
| } | ||
| if (signature === Signature.CentralFileHeader) { | ||
| return false; | ||
| } | ||
| if (signature === 0xE011CFD0) { | ||
| throw new Error('Encrypted ZIP'); | ||
| } | ||
| throw new Error('Unexpected signature'); | ||
| } | ||
| } | ||
@@ -167,0 +181,0 @@ function indexOf(buffer, portion) { |
+3
-1
| { | ||
| "name": "@tokenizer/inflate", | ||
| "version": "0.2.4", | ||
| "version": "0.2.5", | ||
| "description": "Tokenized zip support", | ||
@@ -34,3 +34,5 @@ "type": "module", | ||
| "devDependencies": { | ||
| "@aws-sdk/client-s3": "^3.705.0", | ||
| "@biomejs/biome": "=1.9.4", | ||
| "@tokenizer/s3": "^0.5.1", | ||
| "@types/chai": "^5.0.1", | ||
@@ -37,0 +39,0 @@ "@types/debug": "^4", |
26497
2.82%442
3.76%14
16.67%