134 lines
3.4 KiB
TypeScript
134 lines
3.4 KiB
TypeScript
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
|
|
import { describe, expect, it } from 'vitest';
|
|
|
|
import { BinaryReader } from '../src/onenote/binary/BinaryReader.js';
|
|
import { parseObjectPropSet } from '../src/onenote/onestore/property-set.js';
|
|
import { OneNoteParserError } from '../src/onenote/parser/error.js';
|
|
import { resolveParserLimits } from '../src/onenote/parser/limits.js';
|
|
|
|
describe('bounded OneStore primitives', () => {
|
|
it('reports an absolute offset instead of reading beyond its view', () => {
|
|
const reader = new BinaryReader(
|
|
Uint8Array.from([0, 1, 2, 3, 4, 5]),
|
|
2,
|
|
3,
|
|
'test view'
|
|
);
|
|
expect(reader.u16()).toBe(0x0302);
|
|
|
|
try {
|
|
reader.u16();
|
|
expect.unreachable('the read should fail');
|
|
} catch (error) {
|
|
expect(error).toBeInstanceOf(OneNoteParserError);
|
|
expect(error).toMatchObject({
|
|
code: 'BOUNDS_EXCEEDED',
|
|
offset: 4,
|
|
structure: 'test view',
|
|
});
|
|
}
|
|
});
|
|
|
|
it('decodes a bounded object property set without reading Boolean bytes', () => {
|
|
const bytes = Uint8Array.from([
|
|
0x00,
|
|
0x00,
|
|
0x00,
|
|
0x80, // OID stream: zero IDs, OSID stream absent
|
|
0x02,
|
|
0x00, // two properties
|
|
0x01,
|
|
0x00,
|
|
0x00,
|
|
0x88, // Boolean true
|
|
0x02,
|
|
0x00,
|
|
0x00,
|
|
0x1c, // byte vector
|
|
0x03,
|
|
0x00,
|
|
0x00,
|
|
0x00,
|
|
0xaa,
|
|
0xbb,
|
|
0xcc,
|
|
]);
|
|
const parsed = parseObjectPropSet(
|
|
bytes,
|
|
{ offset: 0, size: bytes.length, nil: false, zero: false },
|
|
resolveParserLimits()
|
|
);
|
|
|
|
expect(parsed.properties.entries[0]?.value).toEqual({
|
|
kind: 'bool',
|
|
value: true,
|
|
});
|
|
expect(parsed.properties.entries[1]?.value).toMatchObject({
|
|
kind: 'bytes',
|
|
value: Uint8Array.from([0xaa, 0xbb, 0xcc]),
|
|
});
|
|
});
|
|
|
|
it('enforces one aggregate budget across nested PropertySet arrays', () => {
|
|
const bytes = objectPropertySetWithNestedArrays(16);
|
|
const reference = {
|
|
offset: 0,
|
|
size: bytes.length,
|
|
nil: false,
|
|
zero: false,
|
|
};
|
|
|
|
expect(() =>
|
|
parseObjectPropSet(
|
|
bytes,
|
|
reference,
|
|
// Every array contains one set, but the property set contains 16.
|
|
resolveParserLimits({ maxObjectReferences: 15 })
|
|
)
|
|
).toThrowError(
|
|
expect.objectContaining({
|
|
code: 'LIMIT_EXCEEDED',
|
|
structure: 'Nested PropertySets',
|
|
})
|
|
);
|
|
|
|
const parsed = parseObjectPropSet(
|
|
bytes,
|
|
reference,
|
|
resolveParserLimits({ maxObjectReferences: 16 })
|
|
);
|
|
expect(parsed.properties.entries).toHaveLength(16);
|
|
});
|
|
});
|
|
|
|
function objectPropertySetWithNestedArrays(count: number): Uint8Array {
|
|
const propertyIds = Array.from({ length: count }, (_, index) =>
|
|
u32((0x10 << 26) | index)
|
|
).flat();
|
|
const values = Array.from({ length: count }, (_, index) => [
|
|
...u32(1), // One nested PropertySet in this array.
|
|
...u32(index),
|
|
0x00,
|
|
0x00, // Empty nested PropertySet.
|
|
]).flat();
|
|
return Uint8Array.from([
|
|
...u32(0x8000_0000), // Zero OIDs; OSID stream absent.
|
|
count & 0xff,
|
|
count >>> 8,
|
|
...propertyIds,
|
|
...values,
|
|
]);
|
|
}
|
|
|
|
function u32(value: number): number[] {
|
|
return [
|
|
value & 0xff,
|
|
(value >>> 8) & 0xff,
|
|
(value >>> 16) & 0xff,
|
|
value >>> 24,
|
|
];
|
|
}
|