feat: add local OneNote and ONEPKG reader

This commit is contained in:
2026-07-22 17:06:03 +02:00
commit f581cbdced
93 changed files with 14949 additions and 0 deletions

93
tests/file-node.test.ts Normal file
View File

@@ -0,0 +1,93 @@
// 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 { DesktopFileNodeParser } from '../src/onenote/onestore/file-node.js';
import { OneNoteParserError } from '../src/onenote/parser/error.js';
import { resolveParserLimits } from '../src/onenote/parser/limits.js';
const MAGIC = 0xa4567ab1f5f7f4c4n;
const FOOTER = 0x8bc215c38233ba4bn;
describe('DesktopFileNodeParser', () => {
it('rejects a multi-fragment cycle immediately', () => {
const bytes = new Uint8Array(72);
writeFragment(bytes, 0, 0x10, 0, 36);
writeFragment(bytes, 36, 0x10, 1, 0);
const parser = new DesktopFileNodeParser(bytes, resolveParserLimits());
const parse = () =>
parser.parseList({ offset: 0, size: 36, nil: false, zero: false });
expect(parse).toThrowError(OneNoteParserError);
expect(parse).toThrow(/cycle/i);
});
it('stops decoding at ChunkTerminatorFND and permits an opaque tail', () => {
const bytes = new Uint8Array(52);
const view = new DataView(bytes.buffer);
view.setBigUint64(0, MAGIC, true);
view.setUint32(8, 0x10, true);
view.setUint32(12, 0, true);
view.setUint32(16, 0x10ff, true); // id 0xff, declared size 4
bytes.fill(0xa5, 20, 32); // deliberately not valid FileNode words
view.setBigUint64(32, 0xffffffffffffffffn, true);
view.setUint32(40, 0, true);
view.setBigUint64(44, FOOTER, true);
const parser = new DesktopFileNodeParser(bytes, resolveParserLimits());
const parsed = parser.parseList({
offset: 0,
size: bytes.length,
nil: false,
zero: false,
});
expect(parsed.nodes).toEqual([]);
});
it('uses the transaction-log count to delimit nonzero fragment padding', () => {
const bytes = new Uint8Array(52);
const view = new DataView(bytes.buffer);
view.setBigUint64(0, MAGIC, true);
view.setUint32(8, 0x10, true);
view.setUint32(12, 0, true);
view.setUint32(16, 0x1004, true); // id 4, declared size 4
bytes.fill(0xa5, 20, 32); // opaque tail without a terminator
view.setBigUint64(32, 0xffffffffffffffffn, true);
view.setUint32(40, 0, true);
view.setBigUint64(44, FOOTER, true);
const parser = new DesktopFileNodeParser(
bytes,
resolveParserLimits(),
new Map([[0x10, 1]])
);
const parsed = parser.parseList({
offset: 0,
size: bytes.length,
nil: false,
zero: false,
});
expect(parsed.nodes.map(({ id }) => id)).toEqual([0x004]);
});
});
function writeFragment(
bytes: Uint8Array,
offset: number,
listId: number,
sequence: number,
nextOffset: number
): void {
const view = new DataView(bytes.buffer, offset, 36);
view.setBigUint64(0, MAGIC, true);
view.setUint32(8, listId, true);
view.setUint32(12, sequence, true);
view.setBigUint64(16, BigInt(nextOffset), true);
view.setUint32(24, 36, true);
view.setBigUint64(28, FOOTER, true);
}