dduf-check / src /lib /check-dduf.ts
coyotte508
✨ Check zip format
b5430db
raw
history blame
3.21 kB
import { checkFilename } from './check-filename';
import { WebBlob } from './WebBlob';
export async function* checkDduf(url: string): AsyncGenerator<string> {
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';
}