Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 22 additions & 21 deletions modules/zip/src/parse-zip/cd-file-header.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ const CD_EXTRA_FIELD_LENGTH_OFFSET = 30;
const CD_START_DISK_OFFSET = 32;
const CD_LOCAL_HEADER_OFFSET_OFFSET = 42;
const CD_FILE_NAME_OFFSET = 46n;
const ZIP64_EXTRA_FIELD_ID = 0x0001;

export const signature: ZipSignature = new Uint8Array([0x50, 0x4b, 0x01, 0x02]);

Expand Down Expand Up @@ -144,14 +145,6 @@ export async function* makeZipCDHeaderIterator(
);
}
}
/**
* returns the number written in the provided bytes
* @param bytes two bytes containing the number
* @returns the number written in the provided bytes
*/
const getUint16 = (...bytes: [number, number]) => {
return bytes[0] + bytes[1] * 16;
};

/**
* reads all nesessary data from zip64 record in the extra data
Expand All @@ -167,19 +160,27 @@ const findZip64DataInExtra = (zip64data: Zip64Data, extraField: DataView): Parti
if (zip64dataList.length > 0) {
// total length of data in zip64 notation in bytes
const zip64chunkSize = zip64dataList.reduce((sum, curr) => sum + curr.length, 0);
// we're looking for the zip64 nontation header (0x0001)
// and a size field with a correct value next to it
const offsetInExtraData = new Uint8Array(extraField.buffer).findIndex(
(_val, i, arr) =>
getUint16(arr[i], arr[i + 1]) === 0x0001 &&
getUint16(arr[i + 2], arr[i + 3]) === zip64chunkSize
);
// then we read all the nesessary fields from the zip64 data
let bytesRead = 0;
for (const note of zip64dataList) {
const offset = bytesRead;
zip64DataRes[note.name] = extraField.getBigUint64(offsetInExtraData + 4 + offset, true);
bytesRead = offset + note.length;
let offset = 0;
while (offset + 4 <= extraField.byteLength) {
const headerId = extraField.getUint16(offset, true);
const dataSize = extraField.getUint16(offset + 2, true);
const payloadStart = offset + 4;
if (payloadStart + dataSize > extraField.byteLength) {
break;
}
if (headerId === ZIP64_EXTRA_FIELD_ID && dataSize === zip64chunkSize) {
let bytesRead = 0;
for (const note of zip64dataList) {
const fieldOffset = payloadStart + bytesRead;
if (fieldOffset + 8 > payloadStart + dataSize) {
break;
}
zip64DataRes[note.name] = extraField.getBigUint64(fieldOffset, true);
bytesRead += note.length;
}
break;
}
offset = payloadStart + dataSize;
}
}

Expand Down
79 changes: 79 additions & 0 deletions modules/zip/test/zip-utils/cd-file-header.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,82 @@ test('SLPKLoader#zip64 info generation', async t => {
t.equal(header.byteLength, 20);
t.end();
});

test('SLPKLoader#central directory file header parse with zip64 sentinel and empty extra field', async t => {
const header = generateCDHeader({crc32: 0, fileName: 'test.json', length: 0, offset: 0n});
const view = new DataView(header);
view.setUint32(20, 0xffffffff, true);
view.setUint32(24, 0xffffffff, true);
const cdFileHeader = await parseZipCDFileHeader(0n, new DataViewReadableFile(view));
t.equal(cdFileHeader?.fileName, 'test.json');
t.equal(cdFileHeader?.uncompressedSize, BigInt(0xffffffff));
t.equal(cdFileHeader?.compressedSize, BigInt(0xffffffff));
t.equal(cdFileHeader?.extraFieldLength, 0);
t.end();
});

test('SLPKLoader#central directory file header parse with truncated zip64 extra field', async t => {
const header = generateCDHeader({crc32: 0, fileName: 'test.json', length: 0, offset: 0n});
const view = new DataView(header);
view.setUint32(20, 0xffffffff, true);
view.setUint32(24, 0xffffffff, true);
view.setUint16(30, 8, true);
const extra = new Uint8Array([0x01, 0x00, 0x10, 0x00, 0, 0, 0, 0]);
const buffer = new Uint8Array(header.byteLength + extra.byteLength);
buffer.set(new Uint8Array(header), 0);
buffer.set(extra, header.byteLength);
const cdFileHeader = await parseZipCDFileHeader(
0n,
new DataViewReadableFile(new DataView(buffer.buffer))
);
t.equal(cdFileHeader?.uncompressedSize, BigInt(0xffffffff));
t.equal(cdFileHeader?.fileName, 'test.json');
t.equal(cdFileHeader?.extraFieldLength, 8);
t.end();
});

test('SLPKLoader#central directory file header parse with valid zip64 extra field', async t => {
const header = generateCDHeader({
crc32: 0,
fileName: 'test.json',
length: 0xffffffffff,
offset: 0n
});
const cdFileHeader = await parseZipCDFileHeader(
0n,
new DataViewReadableFile(new DataView(header))
);
t.equal(cdFileHeader?.uncompressedSize, BigInt(0xffffffffff));
t.equal(cdFileHeader?.compressedSize, BigInt(0xffffffffff));
t.equal(cdFileHeader?.extraFieldLength, 20);
t.equal(cdFileHeader?.localHeaderOffset, 0n);
t.end();
});

test('SLPKLoader#central directory file header parse with zip64 extra field after unrelated record', async t => {
const header = generateCDHeader({crc32: 0, fileName: 'test.json', length: 0, offset: 0n});
const view = new DataView(header);
view.setUint32(20, 0xffffffff, true);
view.setUint32(24, 0xffffffff, true);
view.setUint16(30, 28, true);
const extra = new DataView(new ArrayBuffer(28));
extra.setUint16(0, 0x9999, true);
extra.setUint16(2, 4, true);
extra.setUint16(4, 0x0001, true);
extra.setUint16(6, 16, true);
extra.setUint16(8, 0x0001, true);
extra.setUint16(10, 16, true);
extra.setBigUint64(12, BigInt(0x1122334455), true);
extra.setBigUint64(20, BigInt(0x66778899aa), true);
const buffer = new Uint8Array(header.byteLength + extra.byteLength);
buffer.set(new Uint8Array(header), 0);
buffer.set(new Uint8Array(extra.buffer), header.byteLength);
const cdFileHeader = await parseZipCDFileHeader(
0n,
new DataViewReadableFile(new DataView(buffer.buffer))
);
t.equal(cdFileHeader?.uncompressedSize, BigInt(0x1122334455));
t.equal(cdFileHeader?.compressedSize, BigInt(0x66778899aa));
t.equal(cdFileHeader?.extraFieldLength, 28);
t.end();
});
Loading