704 lines
21 KiB
TypeScript
704 lines
21 KiB
TypeScript
import { strFromU8, unzipSync } from 'fflate';
|
|
import { describe, expect, it } from 'vitest';
|
|
|
|
import type { ParserDiagnostic } from '../model/diagnostics.js';
|
|
import type {
|
|
OneNoteContentBlock,
|
|
OneNoteTextBlock,
|
|
OneNoteTextStyle,
|
|
} from '../one/content-model.js';
|
|
import {
|
|
createOneNoteExportPlan,
|
|
createOneNoteExportSnapshot,
|
|
createOneNoteStaticNotebookZip,
|
|
OneNoteExportError,
|
|
SafeExportNameAllocator,
|
|
sanitizeExportFilename,
|
|
serializeOneNoteMarkdown,
|
|
serializeOneNotePlainText,
|
|
serializeOneNoteSemanticHtml,
|
|
serializeOneNoteStructuredJson,
|
|
type OneNoteExportSnapshot,
|
|
} from './index.js';
|
|
import { utf8ByteLength } from './plan.js';
|
|
|
|
const PNG = Uint8Array.from([
|
|
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49,
|
|
0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x06,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
]);
|
|
const JPEG = Uint8Array.from([
|
|
0xff, 0xd8, 0xff, 0xc0, 0x00, 0x0b, 0x08, 0x00, 0x01, 0x00, 0x01, 0x01, 0x01,
|
|
0x11, 0x00, 0xff, 0xd9,
|
|
]);
|
|
const SOURCE_MARKUP = '<img src=x onerror=alert(1)><script>bad()</script>';
|
|
|
|
describe('OneNote export serializers', () => {
|
|
it('retains provenance and rich content without emitting active source markup', () => {
|
|
const snapshot = fixtureSnapshot();
|
|
const structured = JSON.parse(serializeOneNoteStructuredJson(snapshot)) as {
|
|
app: { version: string };
|
|
parser: { version: string };
|
|
supportLevel: string;
|
|
source: { name: string };
|
|
diagnostics: ParserDiagnostic[];
|
|
warnings: Array<{ code: string }>;
|
|
unsupportedContent: Array<{ sourceJcid: number }>;
|
|
resources: Array<Record<string, unknown>>;
|
|
};
|
|
|
|
expect(structured).toMatchObject({
|
|
app: { version: '1.2.3' },
|
|
parser: { version: '9.8.7' },
|
|
supportLevel: 'substantial',
|
|
source: { name: '../../<script>source.onepkg' },
|
|
});
|
|
expect(structured.diagnostics.map(({ code }) => code)).toEqual([
|
|
'package-warning',
|
|
'section-warning',
|
|
'page-warning',
|
|
]);
|
|
expect(
|
|
structured.resources.every((resource) => !('bytes' in resource))
|
|
).toBe(true);
|
|
expect(structured.unsupportedContent).toEqual([
|
|
expect.objectContaining({ sourceJcid: 0x1234 }),
|
|
]);
|
|
expect(structured.warnings.map(({ code }) => code)).toEqual(
|
|
expect.arrayContaining([
|
|
'UNSUPPORTED_IMAGE_RESOURCE',
|
|
'ACTIVE_ATTACHMENT_EXTENSION_NEUTRALIZED',
|
|
'BLOCK_RESOURCE_UNAVAILABLE',
|
|
])
|
|
);
|
|
|
|
const plain = serializeOneNotePlainText(snapshot);
|
|
expect(plain).toContain('Application: OneNote Tools 1.2.3');
|
|
expect(plain).toContain('Parser: TypeScript parser 9.8.7');
|
|
expect(plain).toContain(SOURCE_MARKUP);
|
|
expect(plain).toContain('Fallback page text');
|
|
expect(plain).toContain('Ink drawing: 1 stroke, 2 points');
|
|
|
|
const markdown = serializeOneNoteMarkdown(snapshot);
|
|
expect(markdown).not.toContain('<script>');
|
|
expect(markdown).not.toContain('<img src=x');
|
|
expect(markdown).not.toContain('javascript:');
|
|
expect(markdown).toContain(
|
|
String.raw`<script>bad\(\)</script>`
|
|
);
|
|
expect(markdown).toContain('Fallback page text');
|
|
expect(markdown).toContain('| Column 1 | Column 2 |');
|
|
|
|
const html = serializeOneNoteSemanticHtml(snapshot);
|
|
const document = new DOMParser().parseFromString(html, 'text/html');
|
|
expect(
|
|
document.querySelectorAll('script,object,iframe,embed')
|
|
).toHaveLength(0);
|
|
expect(
|
|
document.querySelectorAll(
|
|
'[onload],[onerror],[onclick],foreignObject,use'
|
|
)
|
|
).toHaveLength(0);
|
|
expect(document.querySelectorAll('a[href^="javascript:"]')).toHaveLength(0);
|
|
expect(document.body.textContent).toContain(SOURCE_MARKUP);
|
|
expect(document.body.textContent).toContain('Fallback page text');
|
|
expect(document.querySelectorAll('table td')).toHaveLength(2);
|
|
expect(document.querySelector('.text strong')?.textContent).toContain(
|
|
SOURCE_MARKUP
|
|
);
|
|
expect(document.querySelectorAll('img')).toHaveLength(0);
|
|
expect(document.querySelectorAll('svg.ink')).toHaveLength(1);
|
|
expect(
|
|
document.querySelector('polyline')?.getAttribute('stroke-opacity')
|
|
).toBe('0.498');
|
|
expect(
|
|
document
|
|
.querySelector('meta[http-equiv="Content-Security-Policy"]')
|
|
?.getAttribute('content')
|
|
).toContain("default-src 'none'");
|
|
});
|
|
|
|
it('maps the full ink transparency byte range to SVG opacity', () => {
|
|
const snapshot = fixtureSnapshot();
|
|
const ink = snapshot.sections[0]!.pages[0]!.blocks.find(
|
|
(block) => block.kind === 'ink'
|
|
);
|
|
if (!ink || ink.kind !== 'ink') throw new Error('Ink fixture is missing');
|
|
ink.strokes[0]!.transparency = 0;
|
|
ink.strokes.push({
|
|
...ink.strokes[0]!,
|
|
points: ink.strokes[0]!.points.map((point) => ({ ...point })),
|
|
transparency: 255,
|
|
});
|
|
|
|
const document = new DOMParser().parseFromString(
|
|
serializeOneNoteSemanticHtml(snapshot),
|
|
'text/html'
|
|
);
|
|
expect(
|
|
[...document.querySelectorAll('polyline')].map((stroke) =>
|
|
stroke.getAttribute('stroke-opacity')
|
|
)
|
|
).toEqual(['1', '0']);
|
|
});
|
|
|
|
it('creates a deterministic, path-safe static notebook ZIP', () => {
|
|
const snapshot = fixtureSnapshot();
|
|
const first = createOneNoteStaticNotebookZip(snapshot);
|
|
const second = createOneNoteStaticNotebookZip(snapshot);
|
|
|
|
expect(first.filename).toBe('CON_script_.zip');
|
|
expect(first.mediaType).toBe('application/zip');
|
|
expect(first.bytes).toEqual(second.bytes);
|
|
expect(first.manifest.counts).toMatchObject({
|
|
sections: 1,
|
|
pages: 2,
|
|
resources: 3,
|
|
unsupportedContent: 1,
|
|
});
|
|
|
|
const files = unzipSync(first.bytes);
|
|
expect(Object.keys(files).sort()).toEqual([
|
|
'assets/_CON/CON_script_.html.bin',
|
|
'assets/_CON/photo.jpg',
|
|
'assets/_CON/photo.png',
|
|
'index.html',
|
|
'manifest.json',
|
|
'notebook.json',
|
|
'notebook.md',
|
|
'notebook.txt',
|
|
'warnings.json',
|
|
]);
|
|
for (const path of Object.keys(files)) {
|
|
expect(path.startsWith('/')).toBe(false);
|
|
expect(path).not.toContain('\\');
|
|
expect(path.split('/')).not.toContain('..');
|
|
}
|
|
expect(files['assets/_CON/photo.png']).toEqual(PNG);
|
|
expect(files['assets/_CON/photo.jpg']).toEqual(JPEG);
|
|
expect(strFromU8(files['assets/_CON/CON_script_.html.bin']!)).toBe(
|
|
'<script>alert(2)</script>'
|
|
);
|
|
|
|
const html = strFromU8(files['index.html']!);
|
|
const document = new DOMParser().parseFromString(html, 'text/html');
|
|
expect(document.querySelector('img')?.getAttribute('src')).toBe(
|
|
'assets/_CON/photo.png'
|
|
);
|
|
expect(document.querySelector('a[download]')?.getAttribute('href')).toBe(
|
|
'assets/_CON/CON_script_.html.bin'
|
|
);
|
|
expect(
|
|
document.querySelectorAll('script,object,iframe,embed')
|
|
).toHaveLength(0);
|
|
|
|
const manifest = JSON.parse(strFromU8(files['manifest.json']!)) as {
|
|
app: { version: string };
|
|
parser: { version: string };
|
|
supportLevel: string;
|
|
source: { name: string };
|
|
resources: Array<{ path: string; mediaType: string }>;
|
|
};
|
|
expect(manifest).toMatchObject({
|
|
app: { version: '1.2.3' },
|
|
parser: { version: '9.8.7' },
|
|
supportLevel: 'substantial',
|
|
source: { name: '../../<script>source.onepkg' },
|
|
});
|
|
expect(manifest.resources).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
path: 'assets/_CON/photo.png',
|
|
mediaType: 'image/png',
|
|
}),
|
|
expect.objectContaining({
|
|
path: 'assets/_CON/photo.jpg',
|
|
mediaType: 'image/jpeg',
|
|
}),
|
|
expect.objectContaining({
|
|
path: 'assets/_CON/CON_script_.html.bin',
|
|
mediaType: 'application/octet-stream',
|
|
}),
|
|
])
|
|
);
|
|
|
|
const warnings = JSON.parse(strFromU8(files['warnings.json']!)) as {
|
|
diagnostics: ParserDiagnostic[];
|
|
exportWarnings: Array<{ code: string }>;
|
|
unsupportedContent: Array<{ sourceJcid: number }>;
|
|
};
|
|
expect(warnings.diagnostics).toHaveLength(3);
|
|
expect(warnings.exportWarnings.map(({ code }) => code)).toEqual(
|
|
expect.arrayContaining([
|
|
'UNSUPPORTED_IMAGE_RESOURCE',
|
|
'ACTIVE_ATTACHMENT_EXTENSION_NEUTRALIZED',
|
|
'BLOCK_RESOURCE_UNAVAILABLE',
|
|
])
|
|
);
|
|
expect(warnings.unsupportedContent).toEqual([
|
|
expect.objectContaining({ sourceJcid: 0x1234 }),
|
|
]);
|
|
});
|
|
|
|
it('bounds traversal and rejects cyclic content', () => {
|
|
const snapshot = fixtureSnapshot();
|
|
expect(() =>
|
|
serializeOneNotePlainText(snapshot, { limits: { maxPages: 1 } })
|
|
).toThrowError(
|
|
expect.objectContaining<Partial<OneNoteExportError>>({
|
|
code: 'limit-exceeded',
|
|
})
|
|
);
|
|
|
|
const cyclic: Extract<OneNoteContentBlock, { kind: 'outline' }> = {
|
|
kind: 'outline',
|
|
id: 'cycle',
|
|
indents: [],
|
|
layout: {},
|
|
children: [],
|
|
};
|
|
cyclic.children.push(cyclic);
|
|
snapshot.sections[0]!.pages[0]!.blocks = [cyclic];
|
|
expect(() => serializeOneNoteStructuredJson(snapshot)).toThrowError(
|
|
expect.objectContaining<Partial<OneNoteExportError>>({
|
|
code: 'invalid-input',
|
|
})
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('OneNote export aggregate budgets', () => {
|
|
it('counts repeated rich-text representations against one text budget', () => {
|
|
const baseline = minimalSnapshot([textBlock('text', '')]);
|
|
const baselineCharacters =
|
|
createOneNoteExportPlan(baseline).textCharacterCount;
|
|
const snapshot = minimalSnapshot([textBlock('text', '0123456789')]);
|
|
|
|
expect(() =>
|
|
serializeOneNoteStructuredJson(snapshot, {
|
|
limits: { maxTextCharacters: baselineCharacters + 29 },
|
|
})
|
|
).toThrowError(
|
|
expect.objectContaining<Partial<OneNoteExportError>>({
|
|
code: 'limit-exceeded',
|
|
message: expect.stringContaining('text character'),
|
|
})
|
|
);
|
|
});
|
|
|
|
it('counts ink points across blocks and strokes', () => {
|
|
const ink = (id: string): OneNoteContentBlock => ({
|
|
kind: 'ink',
|
|
id,
|
|
strokes: [
|
|
{
|
|
points: [
|
|
{ x: 0, y: 0 },
|
|
{ x: 1, y: 1 },
|
|
],
|
|
},
|
|
],
|
|
children: [],
|
|
layout: {},
|
|
});
|
|
const snapshot = minimalSnapshot([ink('ink-1'), ink('ink-2')]);
|
|
|
|
expect(() =>
|
|
serializeOneNoteSemanticHtml(snapshot, {
|
|
limits: { maxInkPoints: 3 },
|
|
})
|
|
).toThrowError(
|
|
expect.objectContaining<Partial<OneNoteExportError>>({
|
|
code: 'limit-exceeded',
|
|
message: expect.stringContaining('ink point'),
|
|
})
|
|
);
|
|
});
|
|
|
|
it('preflights standalone output and measures exact UTF-8 bytes', () => {
|
|
const encodedSample = 'ASCII · 😀 · \ud800';
|
|
expect(utf8ByteLength(encodedSample)).toBe(
|
|
new TextEncoder().encode(encodedSample).byteLength
|
|
);
|
|
|
|
expect(() =>
|
|
serializeOneNoteMarkdown(minimalSnapshot(), {
|
|
limits: { maxOutputBytes: 8 * 1024 },
|
|
})
|
|
).toThrowError(
|
|
expect.objectContaining<Partial<OneNoteExportError>>({
|
|
code: 'limit-exceeded',
|
|
message: expect.stringContaining('Projected standalone'),
|
|
})
|
|
);
|
|
});
|
|
|
|
it('includes resource members in the static ZIP output budget', () => {
|
|
const resourceBytes = new Uint8Array(64);
|
|
const snapshot = minimalSnapshot(
|
|
[],
|
|
[
|
|
{
|
|
sectionId: 's',
|
|
id: 'attachment',
|
|
kind: 'attachment',
|
|
filename: 'attachment.bin',
|
|
extension: 'bin',
|
|
mediaType: 'application/octet-stream',
|
|
browserRenderable: false,
|
|
bytes: resourceBytes,
|
|
},
|
|
]
|
|
);
|
|
const resourceLimits = {
|
|
maxResourceBytes: 128,
|
|
maxTotalResourceBytes: 128,
|
|
};
|
|
const plan = createOneNoteExportPlan(snapshot, {
|
|
limits: { ...resourceLimits, maxOutputBytes: 1_000_000 },
|
|
});
|
|
const justTooSmall =
|
|
plan.projectedOutputBytes * 6 + resourceBytes.byteLength - 1;
|
|
|
|
expect(() =>
|
|
createOneNoteStaticNotebookZip(snapshot, {
|
|
limits: { ...resourceLimits, maxOutputBytes: justTooSmall },
|
|
})
|
|
).toThrowError(
|
|
expect.objectContaining<Partial<OneNoteExportError>>({
|
|
code: 'limit-exceeded',
|
|
message: expect.stringContaining('Projected static notebook'),
|
|
})
|
|
);
|
|
});
|
|
|
|
it('never auto-embeds parser-disabled or oversized-pixel images', () => {
|
|
const imageResource = (
|
|
id: string,
|
|
bytes: Uint8Array,
|
|
browserRenderable = true
|
|
): OneNoteExportSnapshot['resources'][number] => ({
|
|
sectionId: 's',
|
|
id,
|
|
kind: 'image',
|
|
filename: `${id}.png`,
|
|
extension: 'png',
|
|
mediaType: 'image/png',
|
|
browserRenderable,
|
|
bytes,
|
|
});
|
|
const snapshot = minimalSnapshot(
|
|
[],
|
|
[
|
|
imageResource('parser-disabled', PNG, false),
|
|
imageResource('dimension-bomb', pngWithDimensions(16_385, 1)),
|
|
imageResource('pixel-bomb', pngWithDimensions(8_193, 8_193)),
|
|
]
|
|
);
|
|
|
|
const plan = createOneNoteExportPlan(snapshot);
|
|
expect(plan.resources).toHaveLength(0);
|
|
expect(plan.warnings).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
code: 'IMAGE_RESOURCE_NOT_RENDERABLE',
|
|
resourceId: 'parser-disabled',
|
|
}),
|
|
expect.objectContaining({
|
|
code: 'UNSAFE_IMAGE_RESOURCE',
|
|
resourceId: 'dimension-bomb',
|
|
message: expect.stringContaining('dimension-limit'),
|
|
}),
|
|
expect.objectContaining({
|
|
code: 'UNSAFE_IMAGE_RESOURCE',
|
|
resourceId: 'pixel-bomb',
|
|
message: expect.stringContaining('pixel-limit'),
|
|
}),
|
|
])
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('OneNote export filenames', () => {
|
|
it('removes traversal, reserved names, controls and directional overrides', () => {
|
|
expect(sanitizeExportFilename('../../CON')).toBe('_CON');
|
|
expect(sanitizeExportFilename('..\\AUX.txt')).toBe('_AUX.txt');
|
|
expect(sanitizeExportFilename('safe\u202ename.txt')).toBe('safename.txt');
|
|
expect(sanitizeExportFilename('', '../../NUL')).toBe('_NUL');
|
|
expect(sanitizeExportFilename('x'.repeat(200))).toHaveLength(120);
|
|
expect(
|
|
sanitizeExportFilename(`name.${'x'.repeat(200)}`).length
|
|
).toBeLessThanOrEqual(120);
|
|
});
|
|
|
|
it('allocates case-insensitive unique names', () => {
|
|
const allocator = new SafeExportNameAllocator();
|
|
expect(allocator.allocate('Report.txt')).toBe('Report.txt');
|
|
expect(allocator.allocate('report.TXT')).toBe('report-2.txt');
|
|
});
|
|
|
|
it('keeps collision suffixes within the filename cap', () => {
|
|
const allocator = new SafeExportNameAllocator();
|
|
const filename = `${'x'.repeat(116)}.txt`;
|
|
expect(filename).toHaveLength(120);
|
|
expect(allocator.allocate(filename)).toBe(filename);
|
|
|
|
const duplicate = allocator.allocate(filename);
|
|
expect(duplicate).toBe(`${'x'.repeat(114)}-2.txt`);
|
|
expect([...duplicate]).toHaveLength(120);
|
|
});
|
|
|
|
it('allocates many duplicate names without rescanning prior suffixes', () => {
|
|
const allocator = new SafeExportNameAllocator();
|
|
const names = Array.from({ length: 10_000 }, () =>
|
|
allocator.allocate('Report.txt')
|
|
);
|
|
|
|
expect(names[0]).toBe('Report.txt');
|
|
expect(names.at(-1)).toBe('Report-10000.txt');
|
|
expect(new Set(names)).toHaveLength(names.length);
|
|
});
|
|
});
|
|
|
|
function fixtureSnapshot(): OneNoteExportSnapshot {
|
|
const richText = textBlock('text-1', SOURCE_MARKUP, {
|
|
bold: true,
|
|
italic: true,
|
|
});
|
|
richText.runs[0]!.href = 'javascript:alert(1)';
|
|
const blocks: OneNoteContentBlock[] = [
|
|
richText,
|
|
{
|
|
kind: 'table',
|
|
id: 'table-1',
|
|
declaredColumnCount: 2,
|
|
rows: [
|
|
{
|
|
id: 'row-1',
|
|
cells: [
|
|
{ id: 'cell-1', blocks: [textBlock('cell-text', 'A | B')] },
|
|
{ id: 'cell-2', blocks: [textBlock('cell-text-2', 'C')] },
|
|
],
|
|
},
|
|
],
|
|
columnWidths: [100, 100],
|
|
lockedColumns: [false, false],
|
|
bordersVisible: true,
|
|
layout: {},
|
|
},
|
|
{
|
|
kind: 'image',
|
|
id: 'image-block',
|
|
resourceId: 'image-1',
|
|
filename: 'source.svg',
|
|
altText: '<svg onload=alert(3)>Preview</svg>',
|
|
href: 'javascript:alert(4)',
|
|
isBackground: false,
|
|
layout: {},
|
|
},
|
|
{
|
|
kind: 'image',
|
|
id: 'bad-image-block',
|
|
resourceId: 'bad-image',
|
|
filename: 'bad.svg',
|
|
isBackground: false,
|
|
layout: {},
|
|
},
|
|
{
|
|
kind: 'image',
|
|
id: 'jpeg-image-block',
|
|
resourceId: 'image-2',
|
|
filename: 'photo.jpeg',
|
|
isBackground: false,
|
|
layout: {},
|
|
},
|
|
{
|
|
kind: 'attachment',
|
|
id: 'attachment-block',
|
|
resourceId: 'attachment-1',
|
|
filename: '../../CON<script>.html',
|
|
layout: {},
|
|
},
|
|
{
|
|
kind: 'ink',
|
|
id: 'ink-1',
|
|
strokes: [
|
|
{
|
|
points: [
|
|
{ x: 0, y: 0 },
|
|
{ x: 100, y: 50 },
|
|
],
|
|
width: 8,
|
|
transparency: 128,
|
|
color: 0x332211,
|
|
},
|
|
],
|
|
children: [],
|
|
layout: {},
|
|
},
|
|
{
|
|
kind: 'unsupported',
|
|
id: 'unsupported-1',
|
|
sourceJcid: 0x1234,
|
|
description: '<script>Unknown object</script>',
|
|
},
|
|
];
|
|
|
|
return createOneNoteExportSnapshot({
|
|
sourceName: '../../<script>source.onepkg',
|
|
sourceFormat: 'onepkg',
|
|
notebookName: '../CON<script>.onepkg',
|
|
appName: 'OneNote Tools',
|
|
appVersion: '1.2.3',
|
|
parser: {
|
|
name: 'TypeScript parser',
|
|
version: '9.8.7',
|
|
implementation: 'typescript',
|
|
supportedVariants: ['desktop-store', 'fsshttpb-package-store'],
|
|
referenceRevision: 'MS-ONE 16.0',
|
|
},
|
|
supportLevel: 'substantial',
|
|
supportNotes: ['Unsupported objects remain explicit.'],
|
|
sections: [
|
|
{
|
|
id: 'section-1',
|
|
name: '../CON',
|
|
sourcePath: '../unsafe/Section.one',
|
|
pages: [
|
|
{
|
|
id: 'page-1',
|
|
title: '<script>Page</script>',
|
|
text: SOURCE_MARKUP,
|
|
textPreview: SOURCE_MARKUP,
|
|
blocks,
|
|
diagnostics: [diagnostic('page-warning')],
|
|
},
|
|
{
|
|
id: 'page-2',
|
|
title: 'Fallback',
|
|
text: 'Fallback page text <script>must stay text</script>',
|
|
textPreview: 'Fallback page text',
|
|
blocks: [],
|
|
diagnostics: [],
|
|
},
|
|
],
|
|
diagnostics: [diagnostic('section-warning')],
|
|
},
|
|
],
|
|
resources: [
|
|
{
|
|
sectionId: 'section-1',
|
|
id: 'image-1',
|
|
kind: 'image',
|
|
filename: 'photo.svg',
|
|
extension: 'svg',
|
|
mediaType: 'image/svg+xml',
|
|
browserRenderable: true,
|
|
size: PNG.byteLength,
|
|
bytes: PNG,
|
|
},
|
|
{
|
|
sectionId: 'section-1',
|
|
id: 'bad-image',
|
|
kind: 'image',
|
|
filename: 'bad.svg',
|
|
extension: 'svg',
|
|
mediaType: 'image/svg+xml',
|
|
browserRenderable: true,
|
|
bytes: new TextEncoder().encode('<svg><script>bad()</script></svg>'),
|
|
},
|
|
{
|
|
sectionId: 'section-1',
|
|
id: 'image-2',
|
|
kind: 'image',
|
|
filename: 'photo.png',
|
|
extension: 'png',
|
|
mediaType: 'image/png',
|
|
browserRenderable: true,
|
|
bytes: JPEG,
|
|
},
|
|
{
|
|
sectionId: 'section-1',
|
|
id: 'attachment-1',
|
|
kind: 'attachment',
|
|
filename: '../../CON<script>.html',
|
|
extension: 'html',
|
|
mediaType: 'text/html',
|
|
browserRenderable: false,
|
|
bytes: new TextEncoder().encode('<script>alert(2)</script>'),
|
|
},
|
|
],
|
|
diagnostics: [diagnostic('package-warning')],
|
|
});
|
|
}
|
|
|
|
function minimalSnapshot(
|
|
blocks: OneNoteContentBlock[] = [],
|
|
resources: OneNoteExportSnapshot['resources'] = []
|
|
): OneNoteExportSnapshot {
|
|
return createOneNoteExportSnapshot({
|
|
sourceName: 's.one',
|
|
sourceFormat: 'one',
|
|
notebookName: 'n',
|
|
appName: 'a',
|
|
appVersion: '1',
|
|
parser: {
|
|
name: 'p',
|
|
version: '1',
|
|
implementation: 't',
|
|
supportedVariants: [],
|
|
},
|
|
sections: [
|
|
{
|
|
id: 's',
|
|
name: 's',
|
|
pages: [
|
|
{
|
|
id: 'p',
|
|
title: 'p',
|
|
text: '',
|
|
textPreview: '',
|
|
blocks,
|
|
diagnostics: [],
|
|
},
|
|
],
|
|
diagnostics: [],
|
|
},
|
|
],
|
|
resources,
|
|
});
|
|
}
|
|
|
|
function pngWithDimensions(width: number, height: number): Uint8Array {
|
|
const bytes = PNG.slice();
|
|
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
view.setUint32(16, width);
|
|
view.setUint32(20, height);
|
|
return bytes;
|
|
}
|
|
|
|
function textBlock(
|
|
id: string,
|
|
text: string,
|
|
style: OneNoteTextStyle = {}
|
|
): OneNoteTextBlock {
|
|
return {
|
|
kind: 'text',
|
|
id,
|
|
sourceText: text,
|
|
text,
|
|
runs: [{ start: 0, end: text.length, text, style }],
|
|
paragraphStyle: {},
|
|
layout: {},
|
|
};
|
|
}
|
|
|
|
function diagnostic(code: string): ParserDiagnostic {
|
|
return {
|
|
severity: 'warning',
|
|
code,
|
|
message: `${code} <script>message</script>`,
|
|
recoverable: true,
|
|
};
|
|
}
|