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)