Skip to content

Commit

Permalink
fix(link): respect custom protocols #5468
Browse files Browse the repository at this point in the history
  • Loading branch information
nperez0111 committed Aug 12, 2024
1 parent 222f2ac commit d766bd5
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 6 deletions.
5 changes: 5 additions & 0 deletions .changeset/fluffy-bears-remember.md
Original file line number Diff line number Diff line change
@@ -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
25 changes: 19 additions & 6 deletions packages/extension-link/src/link.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'))
}

/**
Expand Down Expand Up @@ -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
Expand All @@ -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]
}
Expand Down
25 changes: 25 additions & 0 deletions tests/cypress/integration/extensions/link.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
})
})
})
})

0 comments on commit d766bd5

Please sign in to comment.