170 lines
4.5 KiB
TypeScript
170 lines
4.5 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
|
|
import type { ParserDiagnostic } from '../model/diagnostics.js';
|
|
import { DEFAULT_ONENOTE_PARSER_LIMITS } from '../parser/limits.js';
|
|
import type {
|
|
ExGuid,
|
|
ObjectPropSet,
|
|
ObjectSpace,
|
|
PropertyEntry,
|
|
StoreObject,
|
|
} from '../onestore/types.js';
|
|
import { exGuidKey } from '../onestore/types.js';
|
|
import { parseContentObjects } from './content.js';
|
|
import { JCID, PROPERTY } from './constants.js';
|
|
|
|
const GUID = '{55555555-5555-5555-5555-555555555555}';
|
|
|
|
describe('content table allocation safety', () => {
|
|
it('rejects a max-u32 declared column count before bitmap allocation', () => {
|
|
const tableId: ExGuid = { guid: GUID, value: 1 };
|
|
const table = object(
|
|
tableId,
|
|
JCID.TableNode,
|
|
props(
|
|
u32Entry(PROPERTY.RowCount, 1),
|
|
u32Entry(PROPERTY.ColumnCount, 0xffff_ffff),
|
|
bytesEntry(PROPERTY.TableColumnsLocked, Uint8Array.of(1, 1))
|
|
)
|
|
);
|
|
const diagnostics: ParserDiagnostic[] = [];
|
|
const space: ObjectSpace = {
|
|
id: { guid: GUID, value: 0 },
|
|
roots: new Map(),
|
|
objects: new Map([[exGuidKey(table.id), table]]),
|
|
};
|
|
|
|
expect(
|
|
parseContentObjects([tableId], {
|
|
space,
|
|
limits: { ...DEFAULT_ONENOTE_PARSER_LIMITS },
|
|
diagnostics,
|
|
resources: new Map(),
|
|
})
|
|
).toEqual([]);
|
|
expect(diagnostics).toContainEqual(
|
|
expect.objectContaining({
|
|
code: 'CONTENT_PARSE_FAILED',
|
|
message: expect.stringContaining(
|
|
`Declared table column count exceeds ${DEFAULT_ONENOTE_PARSER_LIMITS.maxTableCells}`
|
|
),
|
|
})
|
|
);
|
|
});
|
|
|
|
it('rejects declared table dimensions whose product exceeds the cell limit', () => {
|
|
const tableId: ExGuid = { guid: GUID, value: 2 };
|
|
const table = object(
|
|
tableId,
|
|
JCID.TableNode,
|
|
props(
|
|
u32Entry(PROPERTY.RowCount, 1_001),
|
|
u32Entry(PROPERTY.ColumnCount, 1_000)
|
|
)
|
|
);
|
|
const diagnostics: ParserDiagnostic[] = [];
|
|
|
|
expect(
|
|
parseContentObjects([tableId], {
|
|
space: {
|
|
id: { guid: GUID, value: 0 },
|
|
roots: new Map(),
|
|
objects: new Map([[exGuidKey(table.id), table]]),
|
|
},
|
|
limits: { ...DEFAULT_ONENOTE_PARSER_LIMITS },
|
|
diagnostics,
|
|
resources: new Map(),
|
|
})
|
|
).toEqual([]);
|
|
expect(diagnostics[0]?.message).toContain(
|
|
`Declared table cell count exceeds ${DEFAULT_ONENOTE_PARSER_LIMITS.maxTableCells}`
|
|
);
|
|
});
|
|
|
|
it('rejects actual row references before mapping an oversized array', () => {
|
|
const tableId: ExGuid = { guid: GUID, value: 3 };
|
|
const firstRowId: ExGuid = { guid: GUID, value: 4 };
|
|
const secondRowId: ExGuid = { guid: GUID, value: 5 };
|
|
const table = object(
|
|
tableId,
|
|
JCID.TableNode,
|
|
props(referenceEntry(PROPERTY.ElementChildNodes, 2))
|
|
);
|
|
table.resolvedObjectIds = [firstRowId, secondRowId];
|
|
const firstRow = object(firstRowId, JCID.TableRowNode, props());
|
|
const secondRow = object(secondRowId, JCID.TableRowNode, props());
|
|
const diagnostics: ParserDiagnostic[] = [];
|
|
|
|
expect(
|
|
parseContentObjects([tableId], {
|
|
space: {
|
|
id: { guid: GUID, value: 0 },
|
|
roots: new Map(),
|
|
objects: new Map(
|
|
[table, firstRow, secondRow].map((value) => [
|
|
exGuidKey(value.id),
|
|
value,
|
|
])
|
|
),
|
|
},
|
|
limits: { ...DEFAULT_ONENOTE_PARSER_LIMITS, maxTableCells: 1 },
|
|
diagnostics,
|
|
resources: new Map(),
|
|
})
|
|
).toEqual([]);
|
|
expect(diagnostics[0]?.message).toContain(
|
|
'Table row reference count exceeds 1'
|
|
);
|
|
});
|
|
});
|
|
|
|
function object(
|
|
id: ExGuid,
|
|
jcid: number,
|
|
objectProps: ObjectPropSet
|
|
): StoreObject {
|
|
return {
|
|
id,
|
|
jcid,
|
|
props: objectProps,
|
|
mapping: new Map(),
|
|
contextId: { guid: GUID, value: 0 },
|
|
};
|
|
}
|
|
|
|
function props(...entries: PropertyEntry[]): ObjectPropSet {
|
|
return {
|
|
objectIds: [],
|
|
objectSpaceIds: [],
|
|
contextIds: [],
|
|
properties: { entries },
|
|
};
|
|
}
|
|
|
|
function u32Entry(rawId: number, value: number): PropertyEntry {
|
|
return {
|
|
rawId,
|
|
id: rawId & 0x03ff_ffff,
|
|
type: 0x4,
|
|
value: { kind: 'u32', value },
|
|
};
|
|
}
|
|
|
|
function bytesEntry(rawId: number, value: Uint8Array): PropertyEntry {
|
|
return {
|
|
rawId,
|
|
id: rawId & 0x03ff_ffff,
|
|
type: 0x7,
|
|
value: { kind: 'bytes', value },
|
|
};
|
|
}
|
|
|
|
function referenceEntry(rawId: number, count: number): PropertyEntry {
|
|
return {
|
|
rawId,
|
|
id: rawId & 0x03ff_ffff,
|
|
type: 0x9,
|
|
value: { kind: 'objectIds', count },
|
|
};
|
|
}
|