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.
76 lines (75 loc) • 7.93 kB
JavaScript
import zlib from "node:zlib";
import { CompressionMethod } from "../../zip/index.js";
import CP437Encoder from "./cp437Encoder.js";
const ValidationResult = {
VALID_TORRENTZIP: 1,
VALID_RVZSTD: 2,
INVALID: 3
};
const ValidationResultInverted = Object.fromEntries(
Object.entries(ValidationResult).map(([key, value]) => [value, key])
);
var tzValidator_default = {
/**
* Validate if the file is a valid TorrentZip file.
*/
async validate(zipReader) {
const eocd = await zipReader.endOfCentralDirectoryRecord();
if (eocd.diskNumber !== 0 || eocd.centralDirectoryDiskStart !== 0 || eocd.centralDirectoryDiskRecordsCount !== eocd.centralDirectoryTotalRecordsCount || !(eocd.comment.startsWith("TORRENTZIPPED-") && eocd.comment.length === 22 || eocd.comment.startsWith("RVZSTD-") && eocd.comment.length === 15) || eocd.zip64Record && (eocd.zip64Record.diskNumber !== 0 || eocd.zip64Record.centralDirectoryDiskStart !== 0 || eocd.zip64Record.centralDirectoryDiskRecordsCount !== eocd.zip64Record.centralDirectoryTotalRecordsCount || eocd.zip64Record.comment !== "" || eocd.zip64Record.versionMadeBy !== 45 || eocd.zip64Record.versionNeeded !== 45)) {
return ValidationResult.INVALID;
}
const centralDirectoryFileHeaders = await zipReader.centralDirectoryFileHeaders();
const isRvZstd = centralDirectoryFileHeaders.some(
(cdfh) => cdfh.compressionMethod !== CompressionMethod.DEFLATE
);
for (const cdFileHeader of centralDirectoryFileHeaders) {
if (isRvZstd && cdFileHeader.compressionMethod !== CompressionMethod.ZSTD && cdFileHeader.compressionMethod !== CompressionMethod.STORE || !isRvZstd && cdFileHeader.compressionMethod !== CompressionMethod.DEFLATE) {
return ValidationResult.INVALID;
}
if (!(cdFileHeader.compressionMethod === CompressionMethod.DEFLATE && cdFileHeader.versionNeeded === (cdFileHeader.zip64ExtendedInformation === void 0 ? 20 : 45) || cdFileHeader.compressionMethod === CompressionMethod.ZSTD && cdFileHeader.versionNeeded === 63 || cdFileHeader.compressionMethod === CompressionMethod.STORE && cdFileHeader.versionNeeded === (cdFileHeader.zip64ExtendedInformation === void 0 ? 20 : 45)) || !(cdFileHeader.generalPurposeBitFlag === 2 || cdFileHeader.generalPurposeBitFlag === (2 | 2048) && !CP437Encoder.canEncode(cdFileHeader.fileNameResolved())) || !(cdFileHeader.compressionMethod === CompressionMethod.DEFLATE && cdFileHeader.fileModificationTime === 48128 && cdFileHeader.fileModificationDate === 8600 || cdFileHeader.compressionMethod === CompressionMethod.ZSTD && cdFileHeader.fileModificationTime === 0 && cdFileHeader.fileModificationDate === 0 || cdFileHeader.compressionMethod === CompressionMethod.STORE && cdFileHeader.fileModificationTime === 0 && cdFileHeader.fileModificationDate === 0) || cdFileHeader.compressedSize >= 4294967295 && (cdFileHeader.zip64ExtendedInformation?.compressedSize ?? 0) < cdFileHeader.compressedSize || cdFileHeader.uncompressedSize >= 4294967295 && (cdFileHeader.zip64ExtendedInformation?.uncompressedSize ?? 0) < cdFileHeader.uncompressedSize || cdFileHeader.compressionMethod === CompressionMethod.STORE && cdFileHeader.uncompressedSize !== 0 || cdFileHeader.fileNameResolved().includes("\\") || !(cdFileHeader.extraFields.size === 0 || cdFileHeader.extraFields.size === 1 && cdFileHeader.extraFields.has(1)) || cdFileHeader.fileComment.length > 0 || cdFileHeader.fileDiskStart !== 0 || cdFileHeader.internalFileAttributes !== 0 || cdFileHeader.externalFileAttributes !== 0) {
return ValidationResult.INVALID;
}
}
const fileNamesLowerCase = centralDirectoryFileHeaders.map(
(fileHeader) => fileHeader.fileNameResolved().toLowerCase()
);
const fileNamesLowerCaseSorted = fileNamesLowerCase.toSorted((a, b) => a.localeCompare(b));
if (fileNamesLowerCase.some((name, i) => name !== fileNamesLowerCaseSorted[i])) {
return ValidationResult.INVALID;
}
let crc32 = 0;
for (const fileHeader of centralDirectoryFileHeaders) {
crc32 = zlib.crc32(fileHeader.raw, crc32);
}
const cdfhCrc32 = (crc32 >>> 0).toString(16).padStart(8, "0").toUpperCase();
if (!isRvZstd && eocd.comment !== `TORRENTZIPPED-${cdfhCrc32}` || isRvZstd && eocd.comment !== `RVZSTD-${cdfhCrc32}`) {
return ValidationResult.INVALID;
}
let expectedOffset = 0;
for (const centralDirectoryFileHeader of centralDirectoryFileHeaders) {
const localFileHeader = await centralDirectoryFileHeader.localFileHeader();
if (localFileHeader.getLocalFileDataRelativeOffset() !== centralDirectoryFileHeader.localFileHeaderRelativeOffsetResolved() + localFileHeader.raw.length) {
return ValidationResult.INVALID;
}
if (centralDirectoryFileHeader.localFileHeaderRelativeOffsetResolved() !== expectedOffset) {
return ValidationResult.INVALID;
}
expectedOffset += localFileHeader.raw.length + localFileHeader.compressedSizeResolved();
if (!(localFileHeader.versionNeeded === centralDirectoryFileHeader.versionNeeded || localFileHeader.zip64ExtendedInformation === void 0 && centralDirectoryFileHeader.zip64ExtendedInformation !== void 0 && centralDirectoryFileHeader.versionNeeded === 45) || localFileHeader.generalPurposeBitFlag !== centralDirectoryFileHeader.generalPurposeBitFlag || localFileHeader.compressionMethod !== centralDirectoryFileHeader.compressionMethod || localFileHeader.fileModificationTime !== centralDirectoryFileHeader.fileModificationTime || localFileHeader.fileModificationDate !== centralDirectoryFileHeader.fileModificationDate || !localFileHeader.uncompressedCrc32.equals(centralDirectoryFileHeader.uncompressedCrc32) || // TorrentZip LFH sizes can differ from the CDFH when zip64
!(localFileHeader.compressedSize === 4294967295 && localFileHeader.zip64ExtendedInformation?.compressedSize !== void 0 && (localFileHeader.zip64ExtendedInformation.compressedSize >= 4294967295 && localFileHeader.zip64ExtendedInformation.compressedSize === centralDirectoryFileHeader.zip64ExtendedInformation?.compressedSize || localFileHeader.zip64ExtendedInformation.compressedSize < 4294967295 && localFileHeader.zip64ExtendedInformation.compressedSize === centralDirectoryFileHeader.compressedSize) || localFileHeader.compressedSize < 4294967295 && localFileHeader.compressedSize === centralDirectoryFileHeader.compressedSize) || !(localFileHeader.uncompressedSize === 4294967295 && localFileHeader.zip64ExtendedInformation?.uncompressedSize !== void 0 && (localFileHeader.zip64ExtendedInformation.uncompressedSize >= 4294967295 && localFileHeader.zip64ExtendedInformation.uncompressedSize === centralDirectoryFileHeader.zip64ExtendedInformation?.uncompressedSize || localFileHeader.zip64ExtendedInformation.uncompressedSize < 4294967295 && localFileHeader.zip64ExtendedInformation.uncompressedSize === centralDirectoryFileHeader.uncompressedSize) || localFileHeader.uncompressedSize < 4294967295 && localFileHeader.uncompressedSize === centralDirectoryFileHeader.uncompressedSize) || // If one of the sizes exceeds 0xFFFFFFFF, then they must both be clamped
localFileHeader.uncompressedSize === 4294967295 && localFileHeader.compressedSize !== 4294967295 || localFileHeader.compressedSize === 4294967295 && localFileHeader.uncompressedSize !== 4294967295 || localFileHeader.fileNameResolved() !== centralDirectoryFileHeader.fileNameResolved() || !(localFileHeader.extraFields.size === 0 || localFileHeader.extraFields.size === 1 && localFileHeader.extraFields.has(1))) {
return ValidationResult.INVALID;
}
}
if (expectedOffset !== eocd.centralDirectoryOffsetResolved()) {
return ValidationResult.INVALID;
}
return isRvZstd ? ValidationResult.VALID_RVZSTD : ValidationResult.VALID_TORRENTZIP;
}
};
export {
ValidationResult,
ValidationResultInverted,
tzValidator_default as default
};
//# sourceMappingURL=tzValidator.js.map