Merge pull request #6566 from arpitjain099/develop

fix: escape backslashes before parentheses in URL replacement logic
This commit is contained in:
Alois Klink
2025-05-20 06:41:51 +00:00
committed by GitHub
9 changed files with 24 additions and 57 deletions

View File

@@ -0,0 +1,5 @@
---
'mermaid': patch
---
fix: Fix incomplete string escaping in URL manipulation logic when `arrowMarkerAbsolute: true` by ensuring all unsafe characters are escaped.

View File

@@ -69,7 +69,9 @@ describe('Configuration', () => {
.and('include', 'url(#'); .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( renderGraph(
`graph TD `graph TD
A[Christmas] -->|Get money| B(Go shopping) A[Christmas] -->|Get money| B(Go shopping)
@@ -112,7 +114,7 @@ describe('Configuration', () => {
.first() .first()
.should('have.attr', 'marker-end') .should('have.attr', 'marker-end')
.should('exist') .should('exist')
.and('include', 'url(http://localhost'); .and('include', 'url(http\\:\\/\\/localhost');
}); });
}); });
it('should not taint the initial configuration when using multiple directives', () => { it('should not taint the initial configuration when using multiple directives', () => {

View File

@@ -4,7 +4,7 @@ import { createText } from '../rendering-util/createText.js';
import { line, curveBasis, select } from 'd3'; import { line, curveBasis, select } from 'd3';
import { getConfig } from '../diagram-api/diagramAPI.js'; import { getConfig } from '../diagram-api/diagramAPI.js';
import utils from '../utils.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 { getLineFunctionsWithOffset } from '../utils/lineWithOffset.js';
import { getSubGraphTitleMargins } from '../utils/subGraphTitleMargins.js'; import { getSubGraphTitleMargins } from '../utils/subGraphTitleMargins.js';
import { addEdgeMarkers } from './edgeMarker.js'; import { addEdgeMarkers } from './edgeMarker.js';
@@ -440,14 +440,7 @@ export const insertEdge = function (elem, e, edge, clusterDb, diagramType, graph
let url = ''; let url = '';
// // TODO: Can we load this config only from the rendered graph type? // // TODO: Can we load this config only from the rendered graph type?
if (getConfig().flowchart.arrowMarkerAbsolute || getConfig().state.arrowMarkerAbsolute) { if (getConfig().flowchart.arrowMarkerAbsolute || getConfig().state.arrowMarkerAbsolute) {
url = url = getUrl(true);
window.location.protocol +
'//' +
window.location.host +
window.location.pathname +
window.location.search;
url = url.replace(/\(/g, '\\(');
url = url.replace(/\)/g, '\\)');
} }
addEdgeMarkers(svgPath, edge, url, id, diagramType); addEdgeMarkers(svgPath, edge, url, id, diagramType);

View File

@@ -1,7 +1,7 @@
import { line, curveBasis } from 'd3'; import { line, curveBasis } from 'd3';
import utils from '../../utils.js'; import utils from '../../utils.js';
import { log } from '../../logger.js'; import { log } from '../../logger.js';
import { parseGenericTypes } from '../common/common.js'; import { parseGenericTypes, getUrl } from '../common/common.js';
let edgeCount = 0; let edgeCount = 0;
export const drawEdge = function (elem, path, relation, conf, diagObj) { export const drawEdge = function (elem, path, relation, conf, diagObj) {
@@ -42,14 +42,7 @@ export const drawEdge = function (elem, path, relation, conf, diagObj) {
.attr('class', 'relation'); .attr('class', 'relation');
let url = ''; let url = '';
if (conf.arrowMarkerAbsolute) { if (conf.arrowMarkerAbsolute) {
url = url = getUrl(true);
window.location.protocol +
'//' +
window.location.host +
window.location.pathname +
window.location.search;
url = url.replace(/\(/g, '\\(');
url = url.replace(/\)/g, '\\)');
} }
if (relation.relation.lineType == 1) { if (relation.relation.lineType == 1) {

View File

@@ -149,7 +149,7 @@ const breakToPlaceholder = (s: string): string => {
* @param useAbsolute - Whether to return the absolute URL or not * @param useAbsolute - Whether to return the absolute URL or not
* @returns The current URL * @returns The current URL
*/ */
const getUrl = (useAbsolute: boolean): string => { export const getUrl = (useAbsolute: boolean): string => {
let url = ''; let url = '';
if (useAbsolute) { if (useAbsolute) {
url = url =
@@ -158,8 +158,8 @@ const getUrl = (useAbsolute: boolean): string => {
window.location.host + window.location.host +
window.location.pathname + window.location.pathname +
window.location.search; window.location.search;
url = url.replaceAll(/\(/g, '\\(');
url = url.replaceAll(/\)/g, '\\)'); url = CSS.escape(url);
} }
return url; return url;

View File

@@ -6,7 +6,7 @@ import { log } from '../../logger.js';
import utils from '../../utils.js'; import utils from '../../utils.js';
import erMarkers from './erMarkers.js'; import erMarkers from './erMarkers.js';
import { configureSvgSize } from '../../setupGraphViewbox.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'; import { v5 as uuid5 } from 'uuid';
/** Regex used to remove chars from the entity name so the result can be used in an id */ /** Regex used to remove chars from the entity name so the result can be used in an id */
@@ -451,14 +451,7 @@ const drawRelationshipFromLayout = function (svg, rel, g, insert, diagObj) {
// TODO: Understand this better // TODO: Understand this better
let url = ''; let url = '';
if (conf.arrowMarkerAbsolute) { if (conf.arrowMarkerAbsolute) {
url = url = getUrl(true);
window.location.protocol +
'//' +
window.location.host +
window.location.pathname +
window.location.search;
url = url.replace(/\(/g, '\\(');
url = url.replace(/\)/g, '\\)');
} }
// Decide which start and end markers it needs. It may be possible to be more concise here // Decide which start and end markers it needs. It may be possible to be more concise here

View File

@@ -3,6 +3,7 @@ import { select } from 'd3';
import svgDraw, { drawKatex, ACTOR_TYPE_WIDTH, drawText, fixLifeLineHeights } from './svgDraw.js'; import svgDraw, { drawKatex, ACTOR_TYPE_WIDTH, drawText, fixLifeLineHeights } from './svgDraw.js';
import { log } from '../../logger.js'; import { log } from '../../logger.js';
import common, { calculateMathMLDimensions, hasKatex } from '../common/common.js'; import common, { calculateMathMLDimensions, hasKatex } from '../common/common.js';
import { getUrl } from '../common/common.js';
import * as svgDrawCommon from '../common/svgDrawCommon.js'; import * as svgDrawCommon from '../common/svgDrawCommon.js';
import { getConfig } from '../../diagram-api/diagramAPI.js'; import { getConfig } from '../../diagram-api/diagramAPI.js';
import assignWithDepth from '../../assignWithDepth.js'; import assignWithDepth from '../../assignWithDepth.js';
@@ -449,14 +450,7 @@ const drawMessage = async function (diagram, msgModel, lineStartY: number, diagO
let url = ''; let url = '';
if (conf.arrowMarkerAbsolute) { if (conf.arrowMarkerAbsolute) {
url = url = getUrl(true);
window.location.protocol +
'//' +
window.location.host +
window.location.pathname +
window.location.search;
url = url.replace(/\(/g, '\\(');
url = url.replace(/\)/g, '\\)');
} }
line.attr('stroke-width', 2); line.attr('stroke-width', 2);

View File

@@ -1,7 +1,7 @@
import { line, curveBasis } from 'd3'; import { line, curveBasis } from 'd3';
import { StateDB } from './stateDb.js'; import { StateDB } from './stateDb.js';
import utils from '../../utils.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 { getConfig } from '../../diagram-api/diagramAPI.js';
import { log } from '../../logger.js'; import { log } from '../../logger.js';
@@ -444,14 +444,7 @@ export const drawEdge = function (elem, path, relation) {
.attr('class', 'transition'); .attr('class', 'transition');
let url = ''; let url = '';
if (getConfig().state.arrowMarkerAbsolute) { if (getConfig().state.arrowMarkerAbsolute) {
url = url = getUrl(true);
window.location.protocol +
'//' +
window.location.host +
window.location.pathname +
window.location.search;
url = url.replace(/\(/g, '\\(');
url = url.replace(/\)/g, '\\)');
} }
svgPath.attr( svgPath.attr(

View File

@@ -1,5 +1,5 @@
import { getConfig } from '../../diagram-api/diagramAPI.js'; 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 { log } from '../../logger.js';
import { createText } from '../createText.js'; import { createText } from '../createText.js';
import utils from '../../utils.js'; import utils from '../../utils.js';
@@ -631,13 +631,7 @@ export const insertEdge = function (elem, edge, clusterDb, diagramType, startNod
let url = ''; let url = '';
if (getConfig().flowchart.arrowMarkerAbsolute || getConfig().state.arrowMarkerAbsolute) { if (getConfig().flowchart.arrowMarkerAbsolute || getConfig().state.arrowMarkerAbsolute) {
url = url = getUrl(true);
window.location.protocol +
'//' +
window.location.host +
window.location.pathname +
window.location.search;
url = url.replace(/\(/g, '\\(').replace(/\)/g, '\\)');
} }
log.info('arrowTypeStart', edge.arrowTypeStart); log.info('arrowTypeStart', edge.arrowTypeStart);
log.info('arrowTypeEnd', edge.arrowTypeEnd); log.info('arrowTypeEnd', edge.arrowTypeEnd);