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.

76 lines (75 loc) • 7.93 kB
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