UNPKG

igir

Version:

🕹 A zero-setup ROM collection manager that sorts, filters, extracts or archives, patches, and reports on collections of any size on any OS.

117 lines (116 loc) • 4.34 kB
import fs from "node:fs"; import ArchiveExtraDataRecord from "./archiveExtraDataRecord.js"; import CentralDirectoryFileHeader from "./centralDirectoryFileHeader.js"; import DataDescriptor from "./dataDescriptor.js"; import EndOfCentralDirectory from "./endOfCentralDirectory.js"; import LocalFileHeader from "./localFileHeader.js"; class ZipReader { zipFilePath; _centralDirectoryFileHeaders; _endOfCentralDirectoryRecord; constructor(zipFilePath) { this.zipFilePath = zipFilePath; } /** * Return all central directory file headers. */ async centralDirectoryFileHeaders() { if (this._centralDirectoryFileHeaders !== void 0) { return this._centralDirectoryFileHeaders; } const fileHandle = await fs.promises.open(this.zipFilePath, "r"); try { const eocd = await this.endOfCentralDirectoryRecordFromFileHandle(fileHandle); this._centralDirectoryFileHeaders = await CentralDirectoryFileHeader.centralDirectoryFileFromFileHandle( this.zipFilePath, fileHandle, eocd ); return this._centralDirectoryFileHeaders; } finally { await fileHandle.close(); } } /** * Return the archive extra data record that may appear between the last local file's data and the * start of the central directory, or undefined when there isn't one. */ async archiveExtraDataRecord() { const fileHandle = await fs.promises.open(this.zipFilePath, "r"); try { const endOfLocalFiles = await this.endOfLocalFiles(fileHandle); return await ArchiveExtraDataRecord.fromFileHandle(fileHandle, endOfLocalFiles); } finally { await fileHandle.close(); } } /** * Return the byte position immediately after the last local file's data and optional data * descriptor. */ async endOfLocalFiles(fileHandle) { const centralDirectoryFileHeaders = await this.centralDirectoryFileHeaders(); if (centralDirectoryFileHeaders.length === 0) { return 0; } const lastCentralDirectoryFileHeader = centralDirectoryFileHeaders.reduce( (last, current) => current.localFileHeaderRelativeOffsetResolved() > last.localFileHeaderRelativeOffsetResolved() ? current : last ); const localFileHeader = await LocalFileHeader.localFileHeaderFromFileHandle( lastCentralDirectoryFileHeader, fileHandle ); const dataEnd = localFileHeader.getLocalFileDataRelativeOffset() + localFileHeader.compressedSizeResolved(); if (!localFileHeader.hasDataDescriptor()) { return dataEnd; } const dataDescriptor = await DataDescriptor.fromFileHandle( fileHandle, dataEnd, localFileHeader.versionNeeded >= 45 ); return dataEnd + dataDescriptor.raw.length; } async assertValidMagicNumber(fileHandle) { const magicNumber = await this.readMagicNumber(fileHandle); if ( // At least one file in the zip !magicNumber.equals(LocalFileHeader.LOCAL_FILE_HEADER_SIGNATURE) && // No files in the zip !magicNumber.equals(EndOfCentralDirectory.END_OF_CENTRAL_DIRECTORY_RECORD_SIGNATURE) && // The zip is spanned, and this ISN'T the first file !magicNumber.equals(DataDescriptor.DATA_DESCRIPTOR_SIGNATURE) ) { throw new Error(`unknown zip file magic number: ${magicNumber.toString("hex")}`); } } async readMagicNumber(fileHandle) { const buffer = Buffer.allocUnsafe(4); await fileHandle.read({ buffer, position: 0 }); return buffer; } /** * Return the end of central directory record. */ async endOfCentralDirectoryRecord() { if (this._endOfCentralDirectoryRecord !== void 0) { return this._endOfCentralDirectoryRecord; } const fileHandle = await fs.promises.open(this.zipFilePath, "r"); try { return await this.endOfCentralDirectoryRecordFromFileHandle(fileHandle); } finally { await fileHandle.close(); } } async endOfCentralDirectoryRecordFromFileHandle(fileHandle) { if (this._endOfCentralDirectoryRecord !== void 0) { return this._endOfCentralDirectoryRecord; } await this.assertValidMagicNumber(fileHandle); this._endOfCentralDirectoryRecord = await EndOfCentralDirectory.fromFileHandle(fileHandle); return this._endOfCentralDirectoryRecord; } } export { ZipReader as default }; //# sourceMappingURL=zipReader.js.map