Просмотр исходного кода

[visual] Escape special characters in pasted URLs (#14789)

GitOrigin-RevId: 58e0b4b9b902301dbcb34f918ebbfdad0c8a763f
Alf Eaton 3 лет назад
Родитель
Сommit
875ea723ca

+ 1 - 0
services/web/frontend/js/features/source-editor/components/command-tooltip/href-tooltip.tsx

@@ -102,6 +102,7 @@ export const HrefTooltipContent: FC = () => {
         bsStyle="link"
         className="ol-cm-command-tooltip-link"
         onClick={() => {
+          // TODO: unescape content
           window.open(url, '_blank')
         }}
       >

+ 24 - 2
services/web/frontend/js/features/source-editor/extensions/visual/paste-html.ts

@@ -217,6 +217,25 @@ const matchingParents = (element: HTMLElement, selector: string) => {
   return matches
 }
 
+const urlCharacterReplacements = new Map<string, string>([
+  ['\\', '\\\\'],
+  ['#', '\\#'],
+  ['%', '\\%'],
+  ['{', '%7B'],
+  ['}', '%7D'],
+])
+
+const protectUrlCharacters = (url: string) => {
+  // NOTE: add new characters to both this regex and urlCharacterReplacements
+  return url.replaceAll(/[\\#%{}]/g, match => {
+    const replacement = urlCharacterReplacements.get(match)
+    if (!replacement) {
+      throw new Error(`No replacement found for ${match}`)
+    }
+    return replacement
+  })
+}
+
 const processLists = (element: HTMLElement) => {
   for (const list of element.querySelectorAll('ol,ul')) {
     // if the list has only one item, replace the list with an element containing the contents of the item
@@ -519,8 +538,11 @@ const selectors = [
   createSelector({
     selector: 'a',
     match: element => !!element.href && hasContent(element),
-    start: (element: HTMLAnchorElement) => `\\href{${element.href}}{`,
-    end: element => `}`,
+    start: (element: HTMLAnchorElement) => {
+      const url = protectUrlCharacters(element.href)
+      return `\\href{${url}}{`
+    },
+    end: () => `}`,
   }),
   createSelector({
     selector: 'h1',

+ 9 - 1
services/web/test/frontend/features/source-editor/components/codemirror-editor-visual-paste-html.spec.tsx

@@ -277,7 +277,8 @@ describe('<CodeMirrorEditor/> paste HTML in Visual mode', function () {
   it('handles a pasted link', function () {
     mountEditor()
 
-    const data = '<a href="https://example.com/">foo</a>'
+    const data =
+      '<a href="https://example.com/?q=$foo_~bar&x=\\bar#fragment{y}%2">foo</a>'
 
     const clipboardData = new DataTransfer()
     clipboardData.setData('text/html', data)
@@ -285,6 +286,13 @@ describe('<CodeMirrorEditor/> paste HTML in Visual mode', function () {
 
     cy.get('@content').should('have.text', '{foo}')
     cy.get('.ol-cm-command-href').should('have.length', 1)
+
+    cy.get('.cm-line').eq(0).type('{leftArrow}')
+    cy.findByLabelText('URL').should(
+      'have.value',
+      'https://example.com/?q=$foo_~bar&x=\\\\bar\\#fragment%7By%7D\\%2'
+    )
+    // TODO: assert that the "Go to page" link has been unescaped
   })
 
   it('handles a pasted code block', function () {