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
JavaScript
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