From e7827fbd572ea28db61ea2afe7bb35af4f309b87 Mon Sep 17 00:00:00 2001 From: Alf Eaton Date: Mon, 20 May 2024 11:29:31 +0100 Subject: [PATCH] Validate URL protocol before opening from Visual Editor tooltip (#18393) GitOrigin-RevId: 1da255d3e8ccd91e8c8774d140ec663906be948f --- .../components/command-tooltip/href-tooltip.tsx | 3 ++- .../components/command-tooltip/url-tooltip.tsx | 3 ++- .../frontend/js/features/source-editor/utils/url.ts | 11 +++++++++++ .../codemirror-editor-visual-command-tooltip.spec.tsx | 8 ++++---- .../codemirror-editor-visual-tooltips.spec.tsx | 8 ++++---- 5 files changed, 23 insertions(+), 10 deletions(-) create mode 100644 services/web/frontend/js/features/source-editor/utils/url.ts diff --git a/services/web/frontend/js/features/source-editor/components/command-tooltip/href-tooltip.tsx b/services/web/frontend/js/features/source-editor/components/command-tooltip/href-tooltip.tsx index a0d681d9cb..2f9a4333cd 100644 --- a/services/web/frontend/js/features/source-editor/components/command-tooltip/href-tooltip.tsx +++ b/services/web/frontend/js/features/source-editor/components/command-tooltip/href-tooltip.tsx @@ -17,6 +17,7 @@ import { import { Button, ControlLabel, FormControl, FormGroup } from 'react-bootstrap' import Icon from '../../../../shared/components/icon' import { EditorState } from '@codemirror/state' +import { openURL } from '@/features/source-editor/utils/url' export const HrefTooltipContent: FC = () => { const state = useCodeMirrorStateContext() @@ -108,7 +109,7 @@ export const HrefTooltipContent: FC = () => { className="ol-cm-command-tooltip-link" onClick={() => { // TODO: unescape content - window.open(url, '_blank') + openURL(url) }} > diff --git a/services/web/frontend/js/features/source-editor/components/command-tooltip/url-tooltip.tsx b/services/web/frontend/js/features/source-editor/components/command-tooltip/url-tooltip.tsx index c51b497de0..632d71dd03 100644 --- a/services/web/frontend/js/features/source-editor/components/command-tooltip/url-tooltip.tsx +++ b/services/web/frontend/js/features/source-editor/components/command-tooltip/url-tooltip.tsx @@ -9,6 +9,7 @@ import { } from '../../lezer-latex/latex.terms.mjs' import Icon from '../../../../shared/components/icon' import { EditorState } from '@codemirror/state' +import { openURL } from '@/features/source-editor/utils/url' export const UrlTooltipContent: FC = () => { const { t } = useTranslation() @@ -23,7 +24,7 @@ export const UrlTooltipContent: FC = () => { onClick={() => { const url = readUrl(state) if (url) { - window.open(url, '_blank') + openURL(url) } }} > diff --git a/services/web/frontend/js/features/source-editor/utils/url.ts b/services/web/frontend/js/features/source-editor/utils/url.ts new file mode 100644 index 0000000000..8bfc9bdeab --- /dev/null +++ b/services/web/frontend/js/features/source-editor/utils/url.ts @@ -0,0 +1,11 @@ +const ALLOWED_PROTOCOLS = ['https:', 'http:'] + +export const openURL = (content: string) => { + const url = new URL(content, document.location.href) + + if (!ALLOWED_PROTOCOLS.includes(url.protocol)) { + throw new Error(`Not opening URL with protocol ${url.protocol}`) + } + + window.open(url, '_blank') +} diff --git a/services/web/test/frontend/features/source-editor/components/codemirror-editor-visual-command-tooltip.spec.tsx b/services/web/test/frontend/features/source-editor/components/codemirror-editor-visual-command-tooltip.spec.tsx index 837f90a64a..d46b522a11 100644 --- a/services/web/test/frontend/features/source-editor/components/codemirror-editor-visual-command-tooltip.spec.tsx +++ b/services/web/test/frontend/features/source-editor/components/codemirror-editor-visual-command-tooltip.spec.tsx @@ -54,8 +54,8 @@ describe(' command tooltip in Visual mode', function () { // open the link cy.findByRole('button', { name: 'Go to page' }).click() cy.get('@window-open').should( - 'have.been.calledOnceWithExactly', - 'https://example.com', + 'have.been.calledWithMatch', + Cypress.sinon.match.has('href', 'https://example.com/'), '_blank' ) @@ -112,8 +112,8 @@ describe(' command tooltip in Visual mode', function () { // open the link cy.findByRole('button', { name: 'Go to page' }).click() cy.get('@window-open').should( - 'have.been.calledOnceWithExactly', - 'https://example.com', + 'have.been.calledWithMatch', + Cypress.sinon.match.has('href', 'https://example.com/'), '_blank' ) }) diff --git a/services/web/test/frontend/features/source-editor/components/codemirror-editor-visual-tooltips.spec.tsx b/services/web/test/frontend/features/source-editor/components/codemirror-editor-visual-tooltips.spec.tsx index c6e28f9eee..106a80ba18 100644 --- a/services/web/test/frontend/features/source-editor/components/codemirror-editor-visual-tooltips.spec.tsx +++ b/services/web/test/frontend/features/source-editor/components/codemirror-editor-visual-tooltips.spec.tsx @@ -42,8 +42,8 @@ describe(' tooltips in Visual mode', function () { }) cy.findByRole('button', { name: 'Go to page' }).click() cy.get('@open-window').should( - 'have.been.calledOnceWithExactly', - 'https://example.com/foo', + 'have.been.calledWithMatch', + Cypress.sinon.match.has('href', 'https://example.com/foo'), '_blank' ) cy.findByRole('button', { name: 'Remove link' }).click() @@ -62,8 +62,8 @@ describe(' tooltips in Visual mode', function () { }) cy.findByRole('button', { name: 'Go to page' }).click() cy.get('@open-window').should( - 'have.been.calledOnceWithExactly', - 'https://example.com', + 'have.been.calledWithMatch', + Cypress.sinon.match.has('href', 'https://example.com/'), '_blank' ) })