fix: accept per-cabinet reserve metadata

This commit is contained in:
2026-07-22 20:05:06 +02:00
parent 572a67148a
commit f14d5dd68d
2 changed files with 45 additions and 58 deletions

View File

@@ -48,34 +48,39 @@ function mutateU16(
return { ...part, bytes }; return { ...part, bytes };
} }
function withEmptyReserveLayout(part: CabSetPart): CabSetPart { function withDistinctFolderReserve(part: CabSetPart): CabSetPart {
const original = part.bytes; const original = part.bytes;
const insertAt = 36; const originalView = new DataView(
const addedBytes = 4; original.buffer,
original.byteOffset,
original.byteLength
);
const reserveHeaderOffset = 36;
const originalFileOffset = originalView.getUint32(16, true);
// The pinned fixture part has one zero-reserve CFFOLDER immediately before
// its CFFILE table. Add the optional reserve-size header plus one private
// folder-reserve byte without changing any compressed data.
const originalFolderOffset = originalFileOffset - 8;
const addedBytes = 5;
const bytes = new Uint8Array(original.length + addedBytes); const bytes = new Uint8Array(original.length + addedBytes);
bytes.set(original.subarray(0, insertAt)); bytes.set(original.subarray(0, reserveHeaderOffset));
bytes.set(original.subarray(insertAt), insertAt + addedBytes); bytes.set(
original.subarray(reserveHeaderOffset, originalFileOffset),
reserveHeaderOffset + 4
);
bytes[originalFileOffset + 4] = 0xa5;
bytes.set(original.subarray(originalFileOffset), originalFileOffset + 5);
const view = new DataView(bytes.buffer); const view = new DataView(bytes.buffer);
view.setUint32(8, original.length + addedBytes, true); view.setUint32(8, original.length + addedBytes, true);
view.setUint32(16, originalFileOffset + addedBytes, true);
view.setUint16(30, originalView.getUint16(30, true) | 0x0004, true);
view.setUint16(reserveHeaderOffset, 0, true);
view.setUint8(reserveHeaderOffset + 2, 1);
view.setUint8(reserveHeaderOffset + 3, 0);
view.setUint32( view.setUint32(
16, originalFolderOffset + 4,
new DataView(original.buffer, original.byteOffset).getUint32(16, true) + originalView.getUint32(originalFolderOffset, true) + addedBytes,
addedBytes,
true
);
view.setUint16(
30,
new DataView(original.buffer, original.byteOffset).getUint16(30, true) |
0x0004,
true
);
// cbCFHeader, cbCFFolder, and cbCFData are all zero.
bytes.fill(0, insertAt, insertAt + addedBytes);
// This fixture's only CFFOLDER begins at 0x8e before insertion.
view.setUint32(
0x8e + addedBytes,
new DataView(original.buffer, original.byteOffset).getUint32(0x8e, true) +
addedBytes,
true true
); );
return { ...part, bytes }; return { ...part, bytes };
@@ -167,12 +172,6 @@ describe('multi-cabinet sets', () => {
diagnostic: { code: 'cab-set-file-continuation-mismatch' }, diagnostic: { code: 'cab-set-file-continuation-mismatch' },
}); });
const reserve = fixtureSet();
reserve[2] = withEmptyReserveLayout(reserve[2]!);
await expect(extractCabinetSet(reserve)).rejects.toMatchObject({
diagnostic: { code: 'cab-set-reserve-layout-mismatch' },
});
const incompleteBlock = fixtureSet(); const incompleteBlock = fixtureSet();
// Part 5's last CFDATA cbUncomp is at 0xb5. // Part 5's last CFDATA cbUncomp is at 0xb5.
incompleteBlock[4] = mutateU16(incompleteBlock[4]!, 0xb5, 0); incompleteBlock[4] = mutateU16(incompleteBlock[4]!, 0xb5, 0);
@@ -181,6 +180,23 @@ describe('multi-cabinet sets', () => {
}); });
}); });
it('accepts independent reserve layouts and folder metadata per part', async () => {
const parts = fixtureSet();
parts[2] = withDistinctFolderReserve(parts[2]!);
const result = await extractCabinetSet(parts);
expect(
result.extractedEntries.map((entry) =>
new TextDecoder().decode(entry.bytes)
)
).toEqual([
expectedPart(1) + expectedPart(2),
expectedPart(3),
expectedPart(4) + expectedPart(5),
]);
});
it('opens the complete set through the package API', async () => { it('opens the complete set through the package API', async () => {
const parts = fixtureSet(); const parts = fixtureSet();
const result = await openOneNotePackageSet(parts, { const result = await openOneNotePackageSet(parts, {

View File

@@ -15,7 +15,6 @@ import {
FOLDER_CONTINUED_FROM_PREVIOUS, FOLDER_CONTINUED_FROM_PREVIOUS,
FOLDER_CONTINUED_PREVIOUS_AND_NEXT, FOLDER_CONTINUED_PREVIOUS_AND_NEXT,
FOLDER_CONTINUED_TO_NEXT, FOLDER_CONTINUED_TO_NEXT,
FLAG_RESERVE_PRESENT,
parseCabinetInternal, parseCabinetInternal,
verifyDataBlockChecksumInternal, verifyDataBlockChecksumInternal,
type CabDataBlockInternal, type CabDataBlockInternal,
@@ -87,13 +86,6 @@ function checkedAdd(
return sum; return sum;
} }
function bytesEqual(left: Uint8Array, right: Uint8Array): boolean {
return (
left.length === right.length &&
left.every((value, index) => value === right[index])
);
}
function cabinetNameKey(value: string, structure: string): string { function cabinetNameKey(value: string, structure: string): string {
const normalized = value.normalize('NFC'); const normalized = value.normalize('NFC');
if ( if (
@@ -186,13 +178,6 @@ function validateBoundary(left: NamedCabinet, right: NamedCabinet): boolean {
{ structure: 'CFFOLDER.typeCompress' } { structure: 'CFFOLDER.typeCompress' }
); );
} }
if (!bytesEqual(leftFolder.reserve, rightFolder.reserve)) {
fail(
'cab-set-folder-reserve-mismatch',
'Continued folder segments have different reserved metadata.',
{ structure: 'CFFOLDER.abReserve' }
);
}
return true; return true;
} }
@@ -258,7 +243,6 @@ function validateNamedCabinets(
left.parsed.result.cabinetSetIndex - right.parsed.result.cabinetSetIndex left.parsed.result.cabinetSetIndex - right.parsed.result.cabinetSetIndex
); );
const setId = cabinets[0]!.parsed.result.cabinetSetId; const setId = cabinets[0]!.parsed.result.cabinetSetId;
const reserveTemplate = cabinets[0]!.parsed;
for (let index = 0; index < cabinets.length; index += 1) { for (let index = 0; index < cabinets.length; index += 1) {
const cabinet = cabinets[index]!; const cabinet = cabinets[index]!;
const parsed = cabinet.parsed; const parsed = cabinet.parsed;
@@ -276,19 +260,6 @@ function validateNamedCabinets(
{ structure: 'CFHEADER.iCabinet' } { structure: 'CFHEADER.iCabinet' }
); );
} }
if (
(parsed.flags & FLAG_RESERVE_PRESENT) !==
(reserveTemplate.flags & FLAG_RESERVE_PRESENT) ||
parsed.headerReserveSize !== reserveTemplate.headerReserveSize ||
parsed.folderReserveSize !== reserveTemplate.folderReserveSize ||
parsed.dataReserveSize !== reserveTemplate.dataReserveSize
) {
fail(
'cab-set-reserve-layout-mismatch',
'Cabinet parts use inconsistent reserve-area layouts.',
{ structure: 'CFHEADER reserve sizes' }
);
}
} }
for (let index = 0; index < cabinets.length; index += 1) { for (let index = 0; index < cabinets.length; index += 1) {