From 8af636c58de535f0671512e5c31249b80e2cc107 Mon Sep 17 00:00:00 2001 From: Arpit Jain <3242828+arpitjain099@users.noreply.github.com> Date: Mon, 5 May 2025 12:15:41 +0000 Subject: [PATCH 01/10] Fix Incomplete string escaping or encoding errors --- packages/mermaid/src/dagre-wrapper/edges.js | 1 + packages/mermaid/src/diagrams/class/svgDraw.js | 1 + packages/mermaid/src/diagrams/common/common.ts | 1 + packages/mermaid/src/diagrams/er/erRenderer.js | 1 + packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts | 1 + packages/mermaid/src/diagrams/state/shapes.js | 1 + packages/mermaid/src/rendering-util/rendering-elements/edges.js | 2 +- 7 files changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/mermaid/src/dagre-wrapper/edges.js b/packages/mermaid/src/dagre-wrapper/edges.js index 5a97e5b63..5f37009c9 100644 --- a/packages/mermaid/src/dagre-wrapper/edges.js +++ b/packages/mermaid/src/dagre-wrapper/edges.js @@ -446,6 +446,7 @@ export const insertEdge = function (elem, e, edge, clusterDb, diagramType, graph window.location.host + window.location.pathname + window.location.search; + url = url.replace(/\\/g, '\\\\'); url = url.replace(/\(/g, '\\('); url = url.replace(/\)/g, '\\)'); } diff --git a/packages/mermaid/src/diagrams/class/svgDraw.js b/packages/mermaid/src/diagrams/class/svgDraw.js index 73cf97aeb..28e7955a1 100644 --- a/packages/mermaid/src/diagrams/class/svgDraw.js +++ b/packages/mermaid/src/diagrams/class/svgDraw.js @@ -48,6 +48,7 @@ export const drawEdge = function (elem, path, relation, conf, diagObj) { window.location.host + window.location.pathname + window.location.search; + url = url.replace(/\\/g, '\\\\'); url = url.replace(/\(/g, '\\('); url = url.replace(/\)/g, '\\)'); } diff --git a/packages/mermaid/src/diagrams/common/common.ts b/packages/mermaid/src/diagrams/common/common.ts index fd76d0a45..8955830c3 100644 --- a/packages/mermaid/src/diagrams/common/common.ts +++ b/packages/mermaid/src/diagrams/common/common.ts @@ -158,6 +158,7 @@ const getUrl = (useAbsolute: boolean): string => { window.location.host + window.location.pathname + window.location.search; + url = url.replace(/\\/g, '\\\\'); url = url.replaceAll(/\(/g, '\\('); url = url.replaceAll(/\)/g, '\\)'); } diff --git a/packages/mermaid/src/diagrams/er/erRenderer.js b/packages/mermaid/src/diagrams/er/erRenderer.js index 0327bfc9d..5ee51ccc9 100644 --- a/packages/mermaid/src/diagrams/er/erRenderer.js +++ b/packages/mermaid/src/diagrams/er/erRenderer.js @@ -457,6 +457,7 @@ const drawRelationshipFromLayout = function (svg, rel, g, insert, diagObj) { window.location.host + window.location.pathname + window.location.search; + url = url.replace(/\\/g, '\\\\'); // Escape backslashes url = url.replace(/\(/g, '\\('); url = url.replace(/\)/g, '\\)'); } diff --git a/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts b/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts index 84bb15b15..0f2c602ca 100644 --- a/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts +++ b/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts @@ -455,6 +455,7 @@ const drawMessage = async function (diagram, msgModel, lineStartY: number, diagO window.location.host + window.location.pathname + window.location.search; + url = url.replace(/\\/g, '\\\\'); url = url.replace(/\(/g, '\\('); url = url.replace(/\)/g, '\\)'); } diff --git a/packages/mermaid/src/diagrams/state/shapes.js b/packages/mermaid/src/diagrams/state/shapes.js index 419d2a76e..7087f940b 100644 --- a/packages/mermaid/src/diagrams/state/shapes.js +++ b/packages/mermaid/src/diagrams/state/shapes.js @@ -450,6 +450,7 @@ export const drawEdge = function (elem, path, relation) { window.location.host + window.location.pathname + window.location.search; + url = url.replace(/\\/g, '\\\\'); url = url.replace(/\(/g, '\\('); url = url.replace(/\)/g, '\\)'); } diff --git a/packages/mermaid/src/rendering-util/rendering-elements/edges.js b/packages/mermaid/src/rendering-util/rendering-elements/edges.js index ce4244ff2..cca660935 100644 --- a/packages/mermaid/src/rendering-util/rendering-elements/edges.js +++ b/packages/mermaid/src/rendering-util/rendering-elements/edges.js @@ -637,7 +637,7 @@ export const insertEdge = function (elem, edge, clusterDb, diagramType, startNod window.location.host + window.location.pathname + window.location.search; - url = url.replace(/\(/g, '\\(').replace(/\)/g, '\\)'); + url = url.replace(/\\/g, '\\\\').replace(/\(/g, '\\(').replace(/\)/g, '\\)'); } log.info('arrowTypeStart', edge.arrowTypeStart); log.info('arrowTypeEnd', edge.arrowTypeEnd); From 1f897a851bafc40e28ecf87931b2d9178e7152f6 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Mon, 5 May 2025 12:30:50 +0000 Subject: [PATCH 02/10] [autofix.ci] apply automated fixes --- packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts b/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts index 0f2c602ca..9e126ca82 100644 --- a/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts +++ b/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts @@ -455,7 +455,7 @@ const drawMessage = async function (diagram, msgModel, lineStartY: number, diagO window.location.host + window.location.pathname + window.location.search; - url = url.replace(/\\/g, '\\\\'); + url = url.replace(/\\/g, '\\\\'); url = url.replace(/\(/g, '\\('); url = url.replace(/\)/g, '\\)'); } From e79147dcfcf9d3282f6f4b85ac6a377e28104bd4 Mon Sep 17 00:00:00 2001 From: Arpit Jain <3242828+arpitjain099@users.noreply.github.com> Date: Sat, 10 May 2025 05:12:48 +0000 Subject: [PATCH 03/10] update occurrences with getUrl and use CSS.escape function instead --- packages/mermaid/src/dagre-wrapper/edges.js | 12 ++---------- packages/mermaid/src/diagrams/class/svgDraw.js | 12 ++---------- packages/mermaid/src/diagrams/common/common.ts | 5 ++--- packages/mermaid/src/diagrams/er/erRenderer.js | 12 ++---------- .../src/diagrams/sequence/sequenceRenderer.ts | 11 ++--------- packages/mermaid/src/diagrams/state/shapes.js | 12 ++---------- .../src/rendering-util/rendering-elements/edges.js | 10 ++-------- 7 files changed, 14 insertions(+), 60 deletions(-) diff --git a/packages/mermaid/src/dagre-wrapper/edges.js b/packages/mermaid/src/dagre-wrapper/edges.js index 5f37009c9..f945caf94 100644 --- a/packages/mermaid/src/dagre-wrapper/edges.js +++ b/packages/mermaid/src/dagre-wrapper/edges.js @@ -4,7 +4,7 @@ import { createText } from '../rendering-util/createText.js'; import { line, curveBasis, select } from 'd3'; import { getConfig } from '../diagram-api/diagramAPI.js'; import utils from '../utils.js'; -import { evaluate } from '../diagrams/common/common.js'; +import { evaluate, getUrl } from '../diagrams/common/common.js'; import { getLineFunctionsWithOffset } from '../utils/lineWithOffset.js'; import { getSubGraphTitleMargins } from '../utils/subGraphTitleMargins.js'; import { addEdgeMarkers } from './edgeMarker.js'; @@ -440,15 +440,7 @@ export const insertEdge = function (elem, e, edge, clusterDb, diagramType, graph let url = ''; // // TODO: Can we load this config only from the rendered graph type? if (getConfig().flowchart.arrowMarkerAbsolute || getConfig().state.arrowMarkerAbsolute) { - url = - window.location.protocol + - '//' + - window.location.host + - window.location.pathname + - window.location.search; - url = url.replace(/\\/g, '\\\\'); - url = url.replace(/\(/g, '\\('); - url = url.replace(/\)/g, '\\)'); + url = getUrl(true); } addEdgeMarkers(svgPath, edge, url, id, diagramType); diff --git a/packages/mermaid/src/diagrams/class/svgDraw.js b/packages/mermaid/src/diagrams/class/svgDraw.js index 28e7955a1..ecea2b1de 100644 --- a/packages/mermaid/src/diagrams/class/svgDraw.js +++ b/packages/mermaid/src/diagrams/class/svgDraw.js @@ -1,7 +1,7 @@ import { line, curveBasis } from 'd3'; import utils from '../../utils.js'; import { log } from '../../logger.js'; -import { parseGenericTypes } from '../common/common.js'; +import { parseGenericTypes, getUrl } from '../common/common.js'; let edgeCount = 0; export const drawEdge = function (elem, path, relation, conf, diagObj) { @@ -42,15 +42,7 @@ export const drawEdge = function (elem, path, relation, conf, diagObj) { .attr('class', 'relation'); let url = ''; if (conf.arrowMarkerAbsolute) { - url = - window.location.protocol + - '//' + - window.location.host + - window.location.pathname + - window.location.search; - url = url.replace(/\\/g, '\\\\'); - url = url.replace(/\(/g, '\\('); - url = url.replace(/\)/g, '\\)'); + url = getUrl(true); } if (relation.relation.lineType == 1) { diff --git a/packages/mermaid/src/diagrams/common/common.ts b/packages/mermaid/src/diagrams/common/common.ts index fc5ae1aa3..bddf501ad 100644 --- a/packages/mermaid/src/diagrams/common/common.ts +++ b/packages/mermaid/src/diagrams/common/common.ts @@ -158,9 +158,8 @@ const getUrl = (useAbsolute: boolean): string => { window.location.host + window.location.pathname + window.location.search; - url = url.replace(/\\/g, '\\\\'); - url = url.replaceAll(/\(/g, '\\('); - url = url.replaceAll(/\)/g, '\\)'); + + url = CSS.escape(url); } return url; diff --git a/packages/mermaid/src/diagrams/er/erRenderer.js b/packages/mermaid/src/diagrams/er/erRenderer.js index 5ee51ccc9..a6f7745aa 100644 --- a/packages/mermaid/src/diagrams/er/erRenderer.js +++ b/packages/mermaid/src/diagrams/er/erRenderer.js @@ -6,7 +6,7 @@ import { log } from '../../logger.js'; import utils from '../../utils.js'; import erMarkers from './erMarkers.js'; import { configureSvgSize } from '../../setupGraphViewbox.js'; -import { parseGenericTypes } from '../common/common.js'; +import { parseGenericTypes, getUrl } from '../common/common.js'; import { v5 as uuid5 } from 'uuid'; /** Regex used to remove chars from the entity name so the result can be used in an id */ @@ -451,15 +451,7 @@ const drawRelationshipFromLayout = function (svg, rel, g, insert, diagObj) { // TODO: Understand this better let url = ''; if (conf.arrowMarkerAbsolute) { - url = - window.location.protocol + - '//' + - window.location.host + - window.location.pathname + - window.location.search; - url = url.replace(/\\/g, '\\\\'); // Escape backslashes - url = url.replace(/\(/g, '\\('); - url = url.replace(/\)/g, '\\)'); + url = getUrl(true); } // Decide which start and end markers it needs. It may be possible to be more concise here diff --git a/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts b/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts index 9e126ca82..cfba92b79 100644 --- a/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts +++ b/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts @@ -3,6 +3,7 @@ import { select } from 'd3'; import svgDraw, { drawKatex, ACTOR_TYPE_WIDTH, drawText, fixLifeLineHeights } from './svgDraw.js'; import { log } from '../../logger.js'; import common, { calculateMathMLDimensions, hasKatex } from '../common/common.js'; +import { getUrl } from '../common/common.js'; import * as svgDrawCommon from '../common/svgDrawCommon.js'; import { getConfig } from '../../diagram-api/diagramAPI.js'; import assignWithDepth from '../../assignWithDepth.js'; @@ -449,15 +450,7 @@ const drawMessage = async function (diagram, msgModel, lineStartY: number, diagO let url = ''; if (conf.arrowMarkerAbsolute) { - url = - window.location.protocol + - '//' + - window.location.host + - window.location.pathname + - window.location.search; - url = url.replace(/\\/g, '\\\\'); - url = url.replace(/\(/g, '\\('); - url = url.replace(/\)/g, '\\)'); + url = getUrl(true); } line.attr('stroke-width', 2); diff --git a/packages/mermaid/src/diagrams/state/shapes.js b/packages/mermaid/src/diagrams/state/shapes.js index 7087f940b..43913070c 100644 --- a/packages/mermaid/src/diagrams/state/shapes.js +++ b/packages/mermaid/src/diagrams/state/shapes.js @@ -1,7 +1,7 @@ import { line, curveBasis } from 'd3'; import { StateDB } from './stateDb.js'; import utils from '../../utils.js'; -import common from '../common/common.js'; +import common, {getUrl} from '../common/common.js'; import { getConfig } from '../../diagram-api/diagramAPI.js'; import { log } from '../../logger.js'; @@ -444,15 +444,7 @@ export const drawEdge = function (elem, path, relation) { .attr('class', 'transition'); let url = ''; if (getConfig().state.arrowMarkerAbsolute) { - url = - window.location.protocol + - '//' + - window.location.host + - window.location.pathname + - window.location.search; - url = url.replace(/\\/g, '\\\\'); - url = url.replace(/\(/g, '\\('); - url = url.replace(/\)/g, '\\)'); + url = getUrl(true); } svgPath.attr( diff --git a/packages/mermaid/src/rendering-util/rendering-elements/edges.js b/packages/mermaid/src/rendering-util/rendering-elements/edges.js index cca660935..a97668d5f 100644 --- a/packages/mermaid/src/rendering-util/rendering-elements/edges.js +++ b/packages/mermaid/src/rendering-util/rendering-elements/edges.js @@ -1,5 +1,5 @@ import { getConfig } from '../../diagram-api/diagramAPI.js'; -import { evaluate } from '../../diagrams/common/common.js'; +import { evaluate, getUrl } from '../../diagrams/common/common.js'; import { log } from '../../logger.js'; import { createText } from '../createText.js'; import utils from '../../utils.js'; @@ -631,13 +631,7 @@ export const insertEdge = function (elem, edge, clusterDb, diagramType, startNod let url = ''; if (getConfig().flowchart.arrowMarkerAbsolute || getConfig().state.arrowMarkerAbsolute) { - url = - window.location.protocol + - '//' + - window.location.host + - window.location.pathname + - window.location.search; - url = url.replace(/\\/g, '\\\\').replace(/\(/g, '\\(').replace(/\)/g, '\\)'); + url = getUrl(true); } log.info('arrowTypeStart', edge.arrowTypeStart); log.info('arrowTypeEnd', edge.arrowTypeEnd); From 82874c6584aef1d28927368f2a008f17a3dec844 Mon Sep 17 00:00:00 2001 From: Arpit Jain <3242828+arpitjain099@users.noreply.github.com> Date: Sat, 10 May 2025 05:28:44 +0000 Subject: [PATCH 04/10] Add missing export command --- packages/mermaid/src/diagrams/common/common.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mermaid/src/diagrams/common/common.ts b/packages/mermaid/src/diagrams/common/common.ts index bddf501ad..00c9b8313 100644 --- a/packages/mermaid/src/diagrams/common/common.ts +++ b/packages/mermaid/src/diagrams/common/common.ts @@ -149,7 +149,7 @@ const breakToPlaceholder = (s: string): string => { * @param useAbsolute - Whether to return the absolute URL or not * @returns The current URL */ -const getUrl = (useAbsolute: boolean): string => { +export const getUrl = (useAbsolute: boolean): string => { let url = ''; if (useAbsolute) { url = From 96bd7de40debe340f8c3e8207f5bd13a6cf55fe5 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Sat, 10 May 2025 05:33:28 +0000 Subject: [PATCH 05/10] [autofix.ci] apply automated fixes --- packages/mermaid/src/diagrams/state/shapes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mermaid/src/diagrams/state/shapes.js b/packages/mermaid/src/diagrams/state/shapes.js index 43913070c..f9b34506f 100644 --- a/packages/mermaid/src/diagrams/state/shapes.js +++ b/packages/mermaid/src/diagrams/state/shapes.js @@ -1,7 +1,7 @@ import { line, curveBasis } from 'd3'; import { StateDB } from './stateDb.js'; import utils from '../../utils.js'; -import common, {getUrl} from '../common/common.js'; +import common, { getUrl } from '../common/common.js'; import { getConfig } from '../../diagram-api/diagramAPI.js'; import { log } from '../../logger.js'; From e46fd145b261ba15d1534a13940908d98996eb85 Mon Sep 17 00:00:00 2001 From: Alois Klink Date: Mon, 12 May 2025 11:56:26 +0200 Subject: [PATCH 06/10] test(e2e): skip `arrowMarkerAbsolute: 'false'` This feature looks like it's been broken for a long time, but something about the Cypress environment was rewriting the URL to be relative, causing this test to incorrectly pass. --- cypress/integration/other/configuration.spec.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cypress/integration/other/configuration.spec.js b/cypress/integration/other/configuration.spec.js index ad6b21e29..569ce4eff 100644 --- a/cypress/integration/other/configuration.spec.js +++ b/cypress/integration/other/configuration.spec.js @@ -69,7 +69,9 @@ describe('Configuration', () => { .and('include', 'url(#'); }); }); - it('should handle arrowMarkerAbsolute explicitly set to "false" as false', () => { + // This has been broken for a long time, but something about the Cypress environment was + // rewriting the URL to be relative, causing the test to incorrectly pass. + it.skip('should handle arrowMarkerAbsolute explicitly set to "false" as false', () => { renderGraph( `graph TD A[Christmas] -->|Get money| B(Go shopping) From f20c6779824988506193d5afcbd500f16d76d16d Mon Sep 17 00:00:00 2001 From: Alois Klink Date: Mon, 12 May 2025 12:00:09 +0200 Subject: [PATCH 07/10] test(e2e): fix `arrowMarkerAbsolute: true` test The `/` and `:` characters in `url()` are now escaped using `CSS.escape`. See: e79147dcfcf9d3282f6f4b85ac6a377e28104bd4 --- cypress/integration/other/configuration.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cypress/integration/other/configuration.spec.js b/cypress/integration/other/configuration.spec.js index 569ce4eff..b48a197a4 100644 --- a/cypress/integration/other/configuration.spec.js +++ b/cypress/integration/other/configuration.spec.js @@ -114,7 +114,7 @@ describe('Configuration', () => { .first() .should('have.attr', 'marker-end') .should('exist') - .and('include', 'url(http://localhost'); + .and('include', 'url(http\\:\\/\\/localhost'); }); }); it('should not taint the initial configuration when using multiple directives', () => { From bca6ed67c3e0db910bf498fdd0fc0346c02d392b Mon Sep 17 00:00:00 2001 From: Arpit Jain <3242828+arpitjain099@users.noreply.github.com> Date: Mon, 12 May 2025 10:38:54 +0000 Subject: [PATCH 08/10] Add pnpm message for change --- .changeset/yellow-walls-fry.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/yellow-walls-fry.md diff --git a/.changeset/yellow-walls-fry.md b/.changeset/yellow-walls-fry.md new file mode 100644 index 000000000..f30c6fcba --- /dev/null +++ b/.changeset/yellow-walls-fry.md @@ -0,0 +1,5 @@ +--- +'mermaid': major +--- + +fix: Fixes unsafe or incomplete string escaping in URL manipulation logic by ensuring backslashes are escaped before other characters like parentheses. From cadcddd40ce2cfb7e6db5555cc3941026ae7f6a6 Mon Sep 17 00:00:00 2001 From: Arpit Jain <3242828+arpitjain099@users.noreply.github.com> Date: Mon, 12 May 2025 11:13:04 +0000 Subject: [PATCH 09/10] update change version to patch --- .changeset/yellow-walls-fry.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/yellow-walls-fry.md b/.changeset/yellow-walls-fry.md index f30c6fcba..b62902421 100644 --- a/.changeset/yellow-walls-fry.md +++ b/.changeset/yellow-walls-fry.md @@ -1,5 +1,5 @@ --- -'mermaid': major +'mermaid': patch --- fix: Fixes unsafe or incomplete string escaping in URL manipulation logic by ensuring backslashes are escaped before other characters like parentheses. From 942ae8dcbcb30d07307e52c45d890244c89de11e Mon Sep 17 00:00:00 2001 From: Arpit Jain <3242828+arpitjain099@users.noreply.github.com> Date: Mon, 12 May 2025 11:14:36 +0000 Subject: [PATCH 10/10] Update text associated with change --- .changeset/yellow-walls-fry.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/yellow-walls-fry.md b/.changeset/yellow-walls-fry.md index b62902421..75349fd19 100644 --- a/.changeset/yellow-walls-fry.md +++ b/.changeset/yellow-walls-fry.md @@ -2,4 +2,4 @@ 'mermaid': patch --- -fix: Fixes unsafe or incomplete string escaping in URL manipulation logic by ensuring backslashes are escaped before other characters like parentheses. +fix: Fix incomplete string escaping in URL manipulation logic when `arrowMarkerAbsolute: true` by ensuring all unsafe characters are escaped.