mirror of
https://github.com/ueberdosis/tiptap.git
synced 2025-06-07 09:25:29 +08:00
When [we fixed a XSS vuln](https://github.com/ueberdosis/tiptap/pull/5160), we inadvertently broke the ability to use custom protocols, this resolves that by allowing additional custom protocols to be considered valid and not stripped out
This commit is contained in:
parent
6a0f4f30f8
commit
593f1070a8
5
.changeset/fluffy-bears-remember.md
Normal file
5
.changeset/fluffy-bears-remember.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"@tiptap/extension-link": patch
|
||||
---
|
||||
|
||||
Respect custom protocols for links again, custom protocols are supported in additional to the default set #5468
|
@ -106,11 +106,24 @@ declare module '@tiptap/core' {
|
||||
|
||||
// From DOMPurify
|
||||
// https://github.com/cure53/DOMPurify/blob/main/src/regexp.js
|
||||
const ATTR_WHITESPACE = /[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g // eslint-disable-line no-control-regex
|
||||
const IS_ALLOWED_URI = /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i // eslint-disable-line no-useless-escape
|
||||
// eslint-disable-next-line no-control-regex
|
||||
const ATTR_WHITESPACE = /[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g
|
||||
|
||||
function isAllowedUri(uri: string | undefined) {
|
||||
return !uri || uri.replace(ATTR_WHITESPACE, '').match(IS_ALLOWED_URI)
|
||||
function isAllowedUri(uri: string | undefined, protocols?: LinkOptions['protocols']) {
|
||||
const allowedProtocols: string[] = ['http', 'https', 'ftp', 'ftps', 'mailto', 'tel', 'callto', 'sms', 'cid', 'xmpp']
|
||||
|
||||
if (protocols) {
|
||||
protocols.forEach(protocol => {
|
||||
const nextProtocol = (typeof protocol === 'string' ? protocol : protocol.scheme)
|
||||
|
||||
if (nextProtocol) {
|
||||
allowedProtocols.push(nextProtocol)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-useless-escape
|
||||
return !uri || uri.replace(ATTR_WHITESPACE, '').match(new RegExp(`^(?:(?:${allowedProtocols.join('|')}):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))`, 'i'))
|
||||
}
|
||||
|
||||
/**
|
||||
@ -187,7 +200,7 @@ export const Link = Mark.create<LinkOptions>({
|
||||
const href = (dom as HTMLElement).getAttribute('href')
|
||||
|
||||
// prevent XSS attacks
|
||||
if (!href || !isAllowedUri(href)) {
|
||||
if (!href || !isAllowedUri(href, this.options.protocols)) {
|
||||
return false
|
||||
}
|
||||
return null
|
||||
@ -197,7 +210,7 @@ export const Link = Mark.create<LinkOptions>({
|
||||
|
||||
renderHTML({ HTMLAttributes }) {
|
||||
// prevent XSS attacks
|
||||
if (!isAllowedUri(HTMLAttributes.href)) {
|
||||
if (!isAllowedUri(HTMLAttributes.href, this.options.protocols)) {
|
||||
// strip out the href
|
||||
return ['a', mergeAttributes(this.options.HTMLAttributes, { ...HTMLAttributes, href: '' }), 0]
|
||||
}
|
||||
|
@ -250,4 +250,29 @@ describe('extension-link', () => {
|
||||
getEditorEl()?.remove()
|
||||
})
|
||||
})
|
||||
|
||||
describe('custom protocols', () => {
|
||||
it('allows using additional custom protocols', () => {
|
||||
['custom://test.css', 'another-custom://protocol.html', ...validUrls].forEach(url => {
|
||||
editor = new Editor({
|
||||
element: createEditorEl(),
|
||||
extensions: [
|
||||
Document,
|
||||
Text,
|
||||
Paragraph,
|
||||
Link.configure({
|
||||
protocols: ['custom', { scheme: 'another-custom' }],
|
||||
}),
|
||||
],
|
||||
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()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user