diff --git a/components/email/__tests__/composer-island-sanitize.test.ts b/components/email/__tests__/composer-island-sanitize.test.ts new file mode 100644 index 000000000..e297be0af --- /dev/null +++ b/components/email/__tests__/composer-island-sanitize.test.ts @@ -0,0 +1,64 @@ +import { afterEach, describe, expect, it } from 'vitest'; +import { Editor } from '@tiptap/core'; +import Document from '@tiptap/extension-document'; +import Paragraph from '@tiptap/extension-paragraph'; +import Text from '@tiptap/extension-text'; +import type { Editor as ReactEditor } from '@tiptap/react'; + +import { QuotedHtml, serializeEditorContent } from '../quoted-html'; +import { SignatureBlock, SIGNATURE_BLOCK_MARKER } from '../signature-block'; + +// A re-opened draft hands its stored HTML to the editor as is, and the quote +// and signature islands copy their inner HTML verbatim into the app document. +// A draft written by another client, or by another member of a shared +// mailbox, could carry event handlers that then ran in the webmail origin. + +const PAYLOAD = 'l

Quoted original

'; + +function mount(html: string): Editor { + return new Editor({ extensions: [Document, Paragraph, Text, QuotedHtml, SignatureBlock], content: html }); +} + +function islandHtml(editor: Editor, selector: string): string { + const host = editor.view.dom.querySelector(selector) as HTMLElement; + return host.shadowRoot!.innerHTML; +} + +function expectInert(html: string) { + expect(html).not.toMatch(/onerror/i); + expect(html).not.toMatch(/javascript:/i); + expect(html).not.toMatch(/