fix: bound retained rich text content
This commit is contained in:
@@ -54,6 +54,7 @@ import {
|
|||||||
export interface OneNoteContentBudget {
|
export interface OneNoteContentBudget {
|
||||||
blocks: number;
|
blocks: number;
|
||||||
textRuns: number;
|
textRuns: number;
|
||||||
|
textCharacters: number;
|
||||||
tableCells: number;
|
tableCells: number;
|
||||||
inkPoints: number;
|
inkPoints: number;
|
||||||
}
|
}
|
||||||
@@ -72,7 +73,13 @@ interface ContentContext extends OneNoteContentParseOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function createContentBudget(): OneNoteContentBudget {
|
export function createContentBudget(): OneNoteContentBudget {
|
||||||
return { blocks: 0, textRuns: 0, tableCells: 0, inkPoints: 0 };
|
return {
|
||||||
|
blocks: 0,
|
||||||
|
textRuns: 0,
|
||||||
|
textCharacters: 0,
|
||||||
|
tableCells: 0,
|
||||||
|
inkPoints: 0,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Parse page graph roots into an inert, recursively structured content tree. */
|
/** Parse page graph roots into an inert, recursively structured content tree. */
|
||||||
@@ -235,6 +242,7 @@ function parseRichText(
|
|||||||
let sourceText = optionalString(object, PROPERTY.RichEditTextUnicode);
|
let sourceText = optionalString(object, PROPERTY.RichEditTextUnicode);
|
||||||
sourceText ??= optionalLatin1(object, PROPERTY.TextExtendedAscii);
|
sourceText ??= optionalLatin1(object, PROPERTY.TextExtendedAscii);
|
||||||
sourceText = (sourceText ?? '').split('\0').join('');
|
sourceText = (sourceText ?? '').split('\0').join('');
|
||||||
|
incrementTextCharacterBudget(context, sourceText.length);
|
||||||
|
|
||||||
const paragraphStyleId = objectReferences(object, PROPERTY.ParagraphStyle)[0];
|
const paragraphStyleId = objectReferences(object, PROPERTY.ParagraphStyle)[0];
|
||||||
const paragraphStyle = paragraphStyleId
|
const paragraphStyle = paragraphStyleId
|
||||||
@@ -1223,6 +1231,22 @@ function incrementTextRunBudget(context: ContentContext, amount: number): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function incrementTextCharacterBudget(
|
||||||
|
context: ContentContext,
|
||||||
|
amount: number
|
||||||
|
): void {
|
||||||
|
const next = context.budget.textCharacters + amount;
|
||||||
|
if (
|
||||||
|
!Number.isSafeInteger(next) ||
|
||||||
|
next > context.limits.maxExtractedTextChars
|
||||||
|
) {
|
||||||
|
throw new Error(
|
||||||
|
`Rich-text character count exceeds ${context.limits.maxExtractedTextChars}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
context.budget.textCharacters = next;
|
||||||
|
}
|
||||||
|
|
||||||
function incrementTableCellBudget(context: ContentContext): void {
|
function incrementTableCellBudget(context: ContentContext): void {
|
||||||
context.budget.tableCells += 1;
|
context.budget.tableCells += 1;
|
||||||
if (context.budget.tableCells > context.limits.maxTableCells) {
|
if (context.budget.tableCells > context.limits.maxTableCells) {
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
type OneNoteContentBlock,
|
type OneNoteContentBlock,
|
||||||
type OneNoteTextBlock,
|
type OneNoteTextBlock,
|
||||||
} from '../src/onenote/index.js';
|
} from '../src/onenote/index.js';
|
||||||
|
import type { ParserDiagnostic } from '../src/onenote/model/diagnostics.js';
|
||||||
import { parseContentObjects } from '../src/onenote/one/content.js';
|
import { parseContentObjects } from '../src/onenote/one/content.js';
|
||||||
import { JCID, PROPERTY } from '../src/onenote/one/constants.js';
|
import { JCID, PROPERTY } from '../src/onenote/one/constants.js';
|
||||||
import type {
|
import type {
|
||||||
@@ -207,6 +208,47 @@ describe('OneNote rich-content model', () => {
|
|||||||
.reduce((total, stroke) => total + stroke.points.length, 0);
|
.reduce((total, stroke) => total + stroke.points.length, 0);
|
||||||
expect(parsedPoints).toBeLessThanOrEqual(100);
|
expect(parsedPoints).toBeLessThanOrEqual(100);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('bounds retained rich text across content blocks', () => {
|
||||||
|
const guid = '{33333333-3333-3333-3333-333333333333}';
|
||||||
|
const firstId = id(guid, 1);
|
||||||
|
const secondId = id(guid, 2);
|
||||||
|
const richTextObject = (objectId: ExGuid, text: string) =>
|
||||||
|
object(
|
||||||
|
objectId,
|
||||||
|
JCID.RichTextNode,
|
||||||
|
props(
|
||||||
|
bytesEntry(PROPERTY.RichEditTextUnicode, Buffer.from(text, 'utf16le'))
|
||||||
|
),
|
||||||
|
guid
|
||||||
|
);
|
||||||
|
const objects = [
|
||||||
|
richTextObject(firstId, 'first'),
|
||||||
|
richTextObject(secondId, 'second'),
|
||||||
|
];
|
||||||
|
const diagnostics: ParserDiagnostic[] = [];
|
||||||
|
const blocks = parseContentObjects([firstId, secondId], {
|
||||||
|
space: {
|
||||||
|
id: id(guid, 0),
|
||||||
|
roots: new Map(),
|
||||||
|
objects: new Map(
|
||||||
|
objects.map((value) => [exGuidKey(value.id), value] as const)
|
||||||
|
),
|
||||||
|
},
|
||||||
|
limits: { ...DEFAULT_ONENOTE_PARSER_LIMITS, maxExtractedTextChars: 8 },
|
||||||
|
diagnostics,
|
||||||
|
resources: new Map(),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(blocks).toHaveLength(1);
|
||||||
|
expect(blocks[0]).toMatchObject({ kind: 'text', text: 'first' });
|
||||||
|
expect(diagnostics).toContainEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
code: 'CONTENT_PARSE_FAILED',
|
||||||
|
message: expect.stringContaining('Rich-text character count exceeds 8'),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function parseSyntheticLink(href: string): OneNoteTextBlock {
|
function parseSyntheticLink(href: string): OneNoteTextBlock {
|
||||||
|
|||||||
Reference in New Issue
Block a user