mirror of
https://github.com/ueberdosis/tiptap.git
synced 2025-01-10 05:58:13 +08:00
254 lines
7.9 KiB
TypeScript
254 lines
7.9 KiB
TypeScript
import { Editor } from '@tiptap/core'
|
|
import Document from '@tiptap/extension-document'
|
|
import Link from '@tiptap/extension-link'
|
|
import Paragraph from '@tiptap/extension-paragraph'
|
|
import Text from '@tiptap/extension-text'
|
|
|
|
/**
|
|
* Most link tests should actually exist in the demo/ app folder
|
|
*/
|
|
describe('extension-link', () => {
|
|
const editorElClass = 'tiptap'
|
|
let editor: Editor | null = null
|
|
|
|
const createEditorEl = () => {
|
|
const editorEl = document.createElement('div')
|
|
|
|
editorEl.classList.add(editorElClass)
|
|
document.body.appendChild(editorEl)
|
|
return editorEl
|
|
}
|
|
const getEditorEl = () => document.querySelector(`.${editorElClass}`)
|
|
|
|
const validUrls = [
|
|
'https://example.com',
|
|
'http://example.com',
|
|
'/same-site/index.html',
|
|
'../relative.html',
|
|
'mailto:info@example.com',
|
|
'ftp://info@example.com',
|
|
]
|
|
|
|
it('does output href tag for valid JSON schemas', () => {
|
|
validUrls.forEach(url => {
|
|
editor = new Editor({
|
|
element: createEditorEl(),
|
|
extensions: [
|
|
Document,
|
|
Text,
|
|
Paragraph,
|
|
Link,
|
|
],
|
|
content: {
|
|
type: 'doc',
|
|
content: [
|
|
{
|
|
type: 'paragraph',
|
|
content: [
|
|
{
|
|
type: 'text',
|
|
text: 'hello world!',
|
|
marks: [
|
|
{
|
|
type: 'link',
|
|
attrs: {
|
|
href: url,
|
|
},
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
})
|
|
|
|
expect(editor.getHTML()).to.include(url)
|
|
expect(JSON.stringify(editor.getJSON())).to.include(url)
|
|
|
|
editor?.destroy()
|
|
getEditorEl()?.remove()
|
|
})
|
|
})
|
|
|
|
it('does output href tag for valid HTML schemas', () => {
|
|
validUrls.forEach(url => {
|
|
editor = new Editor({
|
|
element: createEditorEl(),
|
|
extensions: [
|
|
Document,
|
|
Text,
|
|
Paragraph,
|
|
Link,
|
|
],
|
|
content: `<p><a href="${url}">hello world!</a></p>`,
|
|
})
|
|
|
|
expect(editor.getHTML()).to.include(url)
|
|
expect(JSON.stringify(editor.getJSON())).to.include(url)
|
|
|
|
editor?.destroy()
|
|
getEditorEl()?.remove()
|
|
})
|
|
})
|
|
|
|
// We have to disable the eslint rule here because we're trying to purposely test eval urls
|
|
// Examples inspired by: https://portswigger.net/web-security/cross-site-scripting/cheat-sheet#protocols
|
|
const invalidUrls = [
|
|
// A standard JavaScript protocol
|
|
// eslint-disable-next-line no-script-url
|
|
'javascript:alert(window.origin)',
|
|
|
|
// The protocol is not case sensitive
|
|
// eslint-disable-next-line no-script-url
|
|
'jAvAsCrIpT:alert(window.origin)',
|
|
|
|
// Characters \x01-\x20 are allowed before the protocol
|
|
// eslint-disable-next-line no-script-url
|
|
'\x00javascript:alert(window.origin)',
|
|
// eslint-disable-next-line no-script-url
|
|
'\x01javascript:alert(window.origin)',
|
|
// eslint-disable-next-line no-script-url
|
|
'\x02javascript:alert(window.origin)',
|
|
// eslint-disable-next-line no-script-url
|
|
'\x03javascript:alert(window.origin)',
|
|
// eslint-disable-next-line no-script-url
|
|
'\x04javascript:alert(window.origin)',
|
|
// eslint-disable-next-line no-script-url
|
|
'\x05javascript:alert(window.origin)',
|
|
// eslint-disable-next-line no-script-url
|
|
'\x06javascript:alert(window.origin)',
|
|
// eslint-disable-next-line no-script-url
|
|
'\x07javascript:alert(window.origin)',
|
|
// eslint-disable-next-line no-script-url
|
|
'\x08javascript:alert(window.origin)',
|
|
// eslint-disable-next-line no-script-url
|
|
'\x09javascript:alert(window.origin)',
|
|
// eslint-disable-next-line no-script-url
|
|
'\x0ajavascript:alert(window.origin)',
|
|
// eslint-disable-next-line no-script-url
|
|
'\x0bjavascript:alert(window.origin)',
|
|
// eslint-disable-next-line no-script-url
|
|
'\x0cjavascript:alert(window.origin)',
|
|
// eslint-disable-next-line no-script-url
|
|
'\x0djavascript:alert(window.origin)',
|
|
// eslint-disable-next-line no-script-url
|
|
'\x0ejavascript:alert(window.origin)',
|
|
// eslint-disable-next-line no-script-url
|
|
'\x0fjavascript:alert(window.origin)',
|
|
// eslint-disable-next-line no-script-url
|
|
'\x10javascript:alert(window.origin)',
|
|
// eslint-disable-next-line no-script-url
|
|
'\x11javascript:alert(window.origin)',
|
|
// eslint-disable-next-line no-script-url
|
|
'\x12javascript:alert(window.origin)',
|
|
// eslint-disable-next-line no-script-url
|
|
'\x13javascript:alert(window.origin)',
|
|
// eslint-disable-next-line no-script-url
|
|
'\x14javascript:alert(window.origin)',
|
|
// eslint-disable-next-line no-script-url
|
|
'\x15javascript:alert(window.origin)',
|
|
// eslint-disable-next-line no-script-url
|
|
'\x16javascript:alert(window.origin)',
|
|
// eslint-disable-next-line no-script-url
|
|
'\x17javascript:alert(window.origin)',
|
|
// eslint-disable-next-line no-script-url
|
|
'\x18javascript:alert(window.origin)',
|
|
// eslint-disable-next-line no-script-url
|
|
'\x19javascript:alert(window.origin)',
|
|
// eslint-disable-next-line no-script-url
|
|
'\x1ajavascript:alert(window.origin)',
|
|
// eslint-disable-next-line no-script-url
|
|
'\x1bjavascript:alert(window.origin)',
|
|
// eslint-disable-next-line no-script-url
|
|
'\x1cjavascript:alert(window.origin)',
|
|
// eslint-disable-next-line no-script-url
|
|
'\x1djavascript:alert(window.origin)',
|
|
// eslint-disable-next-line no-script-url
|
|
'\x1ejavascript:alert(window.origin)',
|
|
// eslint-disable-next-line no-script-url
|
|
'\x1fjavascript:alert(window.origin)',
|
|
|
|
// Characters \x09,\x0a,\x0d are allowed inside the protocol
|
|
// eslint-disable-next-line no-script-url
|
|
'java\x09script:alert(window.origin)',
|
|
// eslint-disable-next-line no-script-url
|
|
'java\x0ascript:alert(window.origin)',
|
|
// eslint-disable-next-line no-script-url
|
|
'java\x0dscript:alert(window.origin)',
|
|
|
|
// Characters \x09,\x0a,\x0d are allowed after protocol name before the colon
|
|
// eslint-disable-next-line no-script-url
|
|
'javascript\x09:alert(window.origin)',
|
|
// eslint-disable-next-line no-script-url
|
|
'javascript\x0a:alert(window.origin)',
|
|
// eslint-disable-next-line no-script-url
|
|
'javascript\x0d:alert(window.origin)',
|
|
]
|
|
|
|
it('does not output href for :javascript links in JSON schema', () => {
|
|
invalidUrls.forEach(url => {
|
|
editor = new Editor({
|
|
element: createEditorEl(),
|
|
extensions: [
|
|
Document,
|
|
Text,
|
|
Paragraph,
|
|
Link,
|
|
],
|
|
content: {
|
|
type: 'doc',
|
|
content: [
|
|
{
|
|
type: 'paragraph',
|
|
content: [
|
|
{
|
|
type: 'text',
|
|
text: 'hello world!',
|
|
marks: [
|
|
{
|
|
type: 'link',
|
|
attrs: {
|
|
href: url,
|
|
},
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
})
|
|
|
|
expect(editor.getHTML()).to.not.include(url)
|
|
// Unfortunately, if the content is provided as JSON, it stays in the editor instance until it's destroyed
|
|
// At least, it cannot be outputted as HTML into a page
|
|
// expect(JSON.stringify(editor.getJSON())).to.not.include(url)
|
|
|
|
editor?.destroy()
|
|
getEditorEl()?.remove()
|
|
})
|
|
})
|
|
|
|
it('does not output href for :javascript links in HTML schema', () => {
|
|
invalidUrls.forEach(url => {
|
|
editor = new Editor({
|
|
element: createEditorEl(),
|
|
extensions: [
|
|
Document,
|
|
Text,
|
|
Paragraph,
|
|
Link,
|
|
],
|
|
content: `<p><a href="${url}">hello world!</a></p>`,
|
|
})
|
|
|
|
expect(editor.getHTML()).to.not.include(url)
|
|
expect(JSON.stringify(editor.getJSON())).to.not.include(url)
|
|
|
|
editor?.destroy()
|
|
getEditorEl()?.remove()
|
|
})
|
|
})
|
|
})
|