fix: accept per-cabinet reserve metadata
This commit is contained in:
@@ -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, {
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user