From f14d5dd68d7dd74f71636d100df09d27ac80a974 Mon Sep 17 00:00:00 2001 From: Albrecht Degering Date: Wed, 22 Jul 2026 20:05:06 +0200 Subject: [PATCH] fix: accept per-cabinet reserve metadata --- src/onenote/onepkg/cabinet-set.test.ts | 74 ++++++++++++++++---------- src/onenote/onepkg/cabinet-set.ts | 29 ---------- 2 files changed, 45 insertions(+), 58 deletions(-) diff --git a/src/onenote/onepkg/cabinet-set.test.ts b/src/onenote/onepkg/cabinet-set.test.ts index 5d02445..794eac0 100644 --- a/src/onenote/onepkg/cabinet-set.test.ts +++ b/src/onenote/onepkg/cabinet-set.test.ts @@ -48,34 +48,39 @@ function mutateU16( return { ...part, bytes }; } -function withEmptyReserveLayout(part: CabSetPart): CabSetPart { +function withDistinctFolderReserve(part: CabSetPart): CabSetPart { const original = part.bytes; - const insertAt = 36; - const addedBytes = 4; + const originalView = new DataView( + 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); - bytes.set(original.subarray(0, insertAt)); - bytes.set(original.subarray(insertAt), insertAt + addedBytes); + bytes.set(original.subarray(0, reserveHeaderOffset)); + 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); 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( - 16, - new DataView(original.buffer, original.byteOffset).getUint32(16, true) + - 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, + originalFolderOffset + 4, + originalView.getUint32(originalFolderOffset, true) + addedBytes, true ); return { ...part, bytes }; @@ -167,12 +172,6 @@ describe('multi-cabinet sets', () => { 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(); // Part 5's last CFDATA cbUncomp is at 0xb5. 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 () => { const parts = fixtureSet(); const result = await openOneNotePackageSet(parts, { diff --git a/src/onenote/onepkg/cabinet-set.ts b/src/onenote/onepkg/cabinet-set.ts index c014b4f..a437dd8 100644 --- a/src/onenote/onepkg/cabinet-set.ts +++ b/src/onenote/onepkg/cabinet-set.ts @@ -15,7 +15,6 @@ import { FOLDER_CONTINUED_FROM_PREVIOUS, FOLDER_CONTINUED_PREVIOUS_AND_NEXT, FOLDER_CONTINUED_TO_NEXT, - FLAG_RESERVE_PRESENT, parseCabinetInternal, verifyDataBlockChecksumInternal, type CabDataBlockInternal, @@ -87,13 +86,6 @@ function checkedAdd( 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 { const normalized = value.normalize('NFC'); if ( @@ -186,13 +178,6 @@ function validateBoundary(left: NamedCabinet, right: NamedCabinet): boolean { { 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; } @@ -258,7 +243,6 @@ function validateNamedCabinets( left.parsed.result.cabinetSetIndex - right.parsed.result.cabinetSetIndex ); const setId = cabinets[0]!.parsed.result.cabinetSetId; - const reserveTemplate = cabinets[0]!.parsed; for (let index = 0; index < cabinets.length; index += 1) { const cabinet = cabinets[index]!; const parsed = cabinet.parsed; @@ -276,19 +260,6 @@ function validateNamedCabinets( { 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) {