diff --git a/cypress/integration/linkSchemes.ts b/cypress/integration/linkSchemes.ts new file mode 100644 index 000000000..ba1deebde --- /dev/null +++ b/cypress/integration/linkSchemes.ts @@ -0,0 +1,93 @@ +/* + * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +describe('markdown formatted links to', () => { + beforeEach(() => { + cy.loadConfig() + cy.visitTestEditor() + }) + + it('external domains render as external link', () => { + cy.codemirrorFill('[external](https://hedgedoc.org/)') + cy.getMarkdownBody() + .find('a') + .should('have.attr', 'href', 'https://hedgedoc.org/') + .should('have.attr', 'rel', 'noreferer noopener') + .should('have.attr', 'target', '_blank') + }) + + it('note anchor references render as anchor link', () => { + cy.codemirrorFill('[anchor](#anchor)') + cy.getMarkdownBody() + .find('a') + .should('have.attr', 'href', 'http://127.0.0.1:3001/n/test#anchor') + }) + + it('internal pages render as internal link', () => { + cy.codemirrorFill('[internal](other-note)') + cy.getMarkdownBody() + .find('a') + .should('have.attr', 'href', 'http://127.0.0.1:3001/n/other-note') + }) + + it('data URIs do not render', () => { + cy.codemirrorFill('[data](data:text/plain,evil)') + cy.getMarkdownBody() + .find('a') + .should('not.exist') + }) + + it('javascript URIs do not render', () => { + cy.codemirrorFill('[js](javascript:alert("evil"))') + cy.getMarkdownBody() + .find('a') + .should('not.exist') + }) +}) + +describe('HTML anchor element links to', () => { + beforeEach(() => { + cy.loadConfig() + cy.visitTestEditor() + }) + + it('external domains render as external link', () => { + cy.codemirrorFill('external') + cy.getMarkdownBody() + .find('a') + .should('have.attr', 'href', 'https://hedgedoc.org/') + .should('have.attr', 'rel', 'noreferer noopener') + .should('have.attr', 'target', '_blank') + }) + + it('note anchor references render as anchor link', () => { + cy.codemirrorFill('anchor') + cy.getMarkdownBody() + .find('a') + .should('have.attr', 'href', 'http://127.0.0.1:3001/n/test#anchor') + }) + + it('internal pages render as internal link', () => { + cy.codemirrorFill('internal') + cy.getMarkdownBody() + .find('a') + .should('have.attr', 'href', 'http://127.0.0.1:3001/n/other-note') + }) + + it('data URIs do not render', () => { + cy.codemirrorFill('data') + cy.getMarkdownBody() + .find('a') + .should('not.exist') + }) + + it('javascript URIs do not render', () => { + cy.codemirrorFill('js') + cy.getMarkdownBody() + .find('a') + .should('not.exist') + }) +}) diff --git a/src/components/markdown-renderer/replace-components/link-replacer/link-replacer.tsx b/src/components/markdown-renderer/replace-components/link-replacer/link-replacer.tsx index c3b5e4b80..4cd71c052 100644 --- a/src/components/markdown-renderer/replace-components/link-replacer/link-replacer.tsx +++ b/src/components/markdown-renderer/replace-components/link-replacer/link-replacer.tsx @@ -25,7 +25,13 @@ export class LinkReplacer extends ComponentReplacer { return undefined } - const url = node.attribs.href + const url = node.attribs.href.trim() + + // eslint-disable-next-line no-script-url + if (url.startsWith('data:') || url.startsWith('javascript:')) { + return { node.attribs.href } + } + const isJumpMark = url.substr(0, 1) === '#' const id = url.substr(1)