import { checkFilename } from './check-filename'; import { WebBlob } from './WebBlob'; export async function* checkDduf(url: string): AsyncGenerator { const blob = await WebBlob.create(new URL(url)); yield 'File size: ' + blob.size; // DDUF is a zip file, uncompressed. const last100kB = await blob.slice(blob.size - 100000, blob.size).arrayBuffer(); const view = new DataView(last100kB); let index = view.byteLength - 22; let found = false; while (index >= 0) { if (view.getUint32(index, true) === 0x06054b50) { found = true; break; } index--; } if (!found) { throw new Error('DDUF footer not found in last 100kB of file'); } yield 'DDUF footer found at offset ' + (blob.size - last100kB.byteLength + index); const diskNumber = view.getUint16(index + 4, true); if (diskNumber === 0xffff) { throw new Error('Spanned archives (ZIP64) not yet supported'); } if (diskNumber !== 0) { throw new Error('Multi-disk archives not supported'); } const fileCount = view.getUint16(index + 10, true); const centralDirSize = view.getUint32(index + 12, true); const centralDirOffset = view.getUint32(index + 16, true); yield 'File count: ' + fileCount; yield 'Central directory size: ' + centralDirSize; yield 'Central directory offset: ' + centralDirOffset; const centralDir = centralDirOffset > blob.size - last100kB.byteLength ? last100kB.slice( centralDirOffset - (blob.size - last100kB.byteLength), centralDirOffset - (blob.size - last100kB.byteLength) + centralDirSize ) : await blob.slice(centralDirOffset, centralDirOffset + centralDirSize).arrayBuffer(); const centralDirView = new DataView(centralDir); let offset = 0; for (let i = 0; i < fileCount; i++) { if (centralDirView.getUint32(offset + 0, true) !== 0x02014b50) { throw new Error('Invalid central directory file header'); } if (offset + 46 > centralDir.byteLength) { throw new Error('Unexpected end of central directory'); } const compressionMethod = centralDirView.getUint16(offset + 10, true); if (compressionMethod !== 0) { throw new Error('Unsupported compression method: ' + compressionMethod); } const size = centralDirView.getUint32(offset + 24, true); const compressedSize = centralDirView.getUint32(offset + 20, true); if (size !== compressedSize) { throw new Error('Compressed size and size differ'); } const filenameLength = centralDirView.getUint16(offset + 28, true); const fileName = new TextDecoder().decode( new Uint8Array(centralDir, offset + 46, filenameLength) ); yield 'File ' + i; yield 'File name: ' + fileName; yield 'File size: ' + size; checkFilename(fileName); const fileDiskNumber = centralDirView.getUint16(34, true); if (fileDiskNumber !== 0) { throw new Error('Multi-disk archives not supported'); } const extraFieldLength = centralDirView.getUint16(offset + 30, true); const commentLength = centralDirView.getUint16(offset + 32, true); const filePosition = centralDirView.getUint32(offset + 42, true); yield 'File position in archive: ' + filePosition; offset += 46 + filenameLength + extraFieldLength + commentLength; } yield 'All files checked'; }