// 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/. // SPDX-License-Identifier: MPL-2.0 import { describe, expect, it } from 'vitest'; import { parseFileDataDeclaration, parseFileDataStore, } from '../src/onenote/onestore/file-data.js'; import type { FileNode, FileNodeList } from '../src/onenote/onestore/types.js'; import { fileDataBytes } from '../src/onenote/onestore/types.js'; import { resolveParserLimits } from '../src/onenote/parser/limits.js'; const HEADER = '{BDE316E7-2665-4511-A4C4-8D4D0B7A9EAC}'; const FOOTER = '{71FBA722-0F79-4A0B-BB13-899256426B24}'; const STORAGE_GUID = '{01234567-89AB-CDEF-8123-456789ABCDEF}'; describe('OneStore file-data objects', () => { it('indexes bounded payload ranges and resolves 0x072 declarations', () => { const payload = Uint8Array.of(0x89, 0x50, 0x4e, 0x47, 13, 10, 26, 10); const fixture = fileDataFixture(payload); const store = parseFileDataStore( fixture.bytes, fixture.root, resolveParserLimits() ); const blob = store.get(STORAGE_GUID)!; expect(blob).toMatchObject({ offset: 100, size: payload.length }); expect(blob.source.buffer).toBe(fixture.bytes.buffer); expect([...fileDataBytes(blob)]).toEqual([...payload]); const declarationBytes = declarationPayload( 0x072, `${STORAGE_GUID}`, '.png' ); const declaration = parseFileDataDeclaration( declarationBytes, node(0x072, 0, declarationBytes.length), store, resolveParserLimits() ); expect(declaration).toMatchObject({ compactId: { guidIndex: 3, value: 7 }, jcid: 0x00080039, fileData: { referenceKind: 'embedded', storageGuid: STORAGE_GUID, extension: '.png', blob, }, }); }); it('keeps external references explicit and enforces payload limits', () => { const externalBytes = declarationPayload( 0x073, 'media/image.onebin', '.jpg' ); expect( parseFileDataDeclaration( externalBytes, node(0x073, 0, externalBytes.length), new Map(), resolveParserLimits() ).fileData ).toEqual({ referenceKind: 'external', dataReference: 'media/image.onebin', extension: '.jpg', }); const fixture = fileDataFixture(Uint8Array.of(1, 2, 3, 4, 5)); expect(() => parseFileDataStore( fixture.bytes, fixture.root, resolveParserLimits({ maxFileDataBytes: 4 }) ) ).toThrow(/payload is 5 bytes/); }); }); function fileDataFixture(payload: Uint8Array): { bytes: Uint8Array; root: FileNodeList; } { const objectOffset = 64; const padding = (8 - ((36 + payload.length) % 8)) % 8; const objectSize = 36 + payload.length + padding + 16; const bytes = Buffer.alloc(objectOffset + objectSize); writeGuid(bytes, 0, STORAGE_GUID); writeGuid(bytes, objectOffset, HEADER); bytes.writeBigUInt64LE(BigInt(payload.length), objectOffset + 16); bytes.writeUInt32LE(0xfeedbeef, objectOffset + 24); bytes.writeBigUInt64LE(0x1234n, objectOffset + 28); bytes.set(payload, objectOffset + 36); writeGuid(bytes, objectOffset + 36 + payload.length + padding, FOOTER); const dataNode: FileNode = { ...node(0x094, 0, 16), dataReference: { offset: objectOffset, size: objectSize, nil: false, zero: false, }, }; return { bytes, root: { id: 0, nodes: [ { ...node(0x090, 0, 0), childList: { id: 1, nodes: [dataNode] }, }, ], }, }; } function declarationPayload( id: 0x072 | 0x073, dataReference: string, extension: string ): Uint8Array { const data = Buffer.from(dataReference, 'utf16le'); const ext = Buffer.from(extension, 'utf16le'); const countSize = id === 0x072 ? 1 : 4; const result = Buffer.alloc( 4 + 4 + countSize + 4 + data.length + 4 + ext.length ); let offset = 0; result.writeUInt32LE((3 << 8) | 7, offset); offset += 4; result.writeUInt32LE(0x00080039, offset); offset += 4; if (id === 0x072) result.writeUInt8(1, offset); else result.writeUInt32LE(1, offset); offset += countSize; result.writeUInt32LE(dataReference.length, offset); offset += 4; result.set(data, offset); offset += data.length; result.writeUInt32LE(extension.length, offset); offset += 4; result.set(ext, offset); return result; } function node( id: number, payloadOffset: number, payloadLength: number ): FileNode { return { id, declaredSize: payloadLength, offset: payloadOffset, payloadOffset, payloadLength, }; } function writeGuid(target: Buffer, offset: number, guid: string): void { const hex = guid.replace(/[{}-]/g, ''); const bytes = Buffer.from(hex, 'hex'); const order = [3, 2, 1, 0, 5, 4, 7, 6, 8, 9, 10, 11, 12, 13, 14, 15]; order.forEach((sourceIndex, index) => { target[offset + index] = bytes[sourceIndex]!; }); }