diff --git a/frontend/src/components/common/html-to-react/__snapshots__/html-to-react.spec.tsx.snap b/frontend/src/components/common/html-to-react/__snapshots__/html-to-react.spec.tsx.snap index 83f048ac9..3cc18fda8 100644 --- a/frontend/src/components/common/html-to-react/__snapshots__/html-to-react.spec.tsx.snap +++ b/frontend/src/components/common/html-to-react/__snapshots__/html-to-react.spec.tsx.snap @@ -27,4 +27,40 @@ exports[`HTML to React will forward the parser options 1`] = ` </div> `; +exports[`HTML to React will render links with non-http protocols 1`] = ` +<div> + <a + href="tel:+1234567890" + > + tel + </a> + <a + href="mailto:test@example.com" + > + mailto + </a> + <a + href="geo:25.197,55.274" + > + geo + </a> + <a + href="xmpp:test" + > + xmpp + </a> +</div> +`; + +exports[`HTML to React won't render script links 1`] = ` +<div> + <a> + js + </a> + <a> + vbs + </a> +</div> +`; + exports[`HTML to React won't render script tags 1`] = `<div />`; diff --git a/frontend/src/components/common/html-to-react/html-to-react.spec.tsx b/frontend/src/components/common/html-to-react/html-to-react.spec.tsx index 2c74c1f04..9a1f956b5 100644 --- a/frontend/src/components/common/html-to-react/html-to-react.spec.tsx +++ b/frontend/src/components/common/html-to-react/html-to-react.spec.tsx @@ -17,6 +17,24 @@ describe('HTML to React', () => { expect(view.container).toMatchSnapshot() }) + it("won't render script links", () => { + const view = render( + <HtmlToReact htmlCode={'<a href="javascript:alert(true)">js</a><a href="vbscript:WScript.Evil">vbs</a>'} /> + ) + expect(view.container).toMatchSnapshot() + }) + + it('will render links with non-http protocols', () => { + const view = render( + <HtmlToReact + htmlCode={ + '<a href="tel:+1234567890">tel</a><a href="mailto:test@example.com">mailto</a><a href="geo:25.197,55.274">geo</a><a href="xmpp:test">xmpp</a>' + } + /> + ) + expect(view.container).toMatchSnapshot() + }) + it('will forward the DomPurify settings', () => { const view = render( <HtmlToReact domPurifyConfig={{ ADD_TAGS: ['test-tag'] }} htmlCode={'<test-tag>Test!</test-tag>'} /> diff --git a/frontend/src/components/common/html-to-react/html-to-react.tsx b/frontend/src/components/common/html-to-react/html-to-react.tsx index 44e85edf3..e73486793 100644 --- a/frontend/src/components/common/html-to-react/html-to-react.tsx +++ b/frontend/src/components/common/html-to-react/html-to-react.tsx @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file) + * SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file) * * SPDX-License-Identifier: AGPL-3.0-only */ @@ -16,6 +16,8 @@ export interface HtmlToReactProps { parserOptions?: ParserOptions } +const REGEX_URI_SCHEME_NO_SCRIPTS = /^(?!.*script:).+:?/i + /** * Renders * @param htmlCode @@ -26,7 +28,12 @@ export interface HtmlToReactProps { export const HtmlToReact: React.FC<HtmlToReactProps> = ({ htmlCode, domPurifyConfig, parserOptions }) => { const elements = useMemo(() => { const sanitizedHtmlCode = measurePerformance('html-to-react: sanitize', () => { - return sanitize(htmlCode, { ...domPurifyConfig, RETURN_DOM_FRAGMENT: false, RETURN_DOM: false }) + return sanitize(htmlCode, { + ...domPurifyConfig, + RETURN_DOM_FRAGMENT: false, + RETURN_DOM: false, + ALLOWED_URI_REGEXP: REGEX_URI_SCHEME_NO_SCRIPTS + }) }) return measurePerformance('html-to-react: convertHtmlToReact', () => { return convertHtmlToReact(sanitizedHtmlCode, parserOptions)