Compare commits

...

5 Commits

Author SHA1 Message Date
shubhamparikh2704
80dcf2eaad chore: fixed formatting 2025-08-19 14:10:15 +05:30
shubhamparikh2704
cdff69fe13 chore: updated upload artifact version 2025-08-19 13:55:15 +05:30
Sidharth Vinod
2ce5801568 chore: bump mermaid version to 10.9.4 2025-08-18 17:10:57 +05:30
Sidharth Vinod
2efe338cb5 fix: sanitize addHtmlLabel in createLabel
Co-authored-by: Chris Grieger <chris@scolp.de>
2025-08-18 16:50:53 +05:30
Sidharth Vinod
7509b066f1 fix: Sanitize Katex 2025-08-08 11:02:44 +05:30
8 changed files with 75 additions and 54 deletions

View File

@@ -37,13 +37,13 @@ jobs:
run: pnpm run build run: pnpm run build
- name: Upload Mermaid Build as Artifact - name: Upload Mermaid Build as Artifact
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: mermaid-build name: mermaid-build
path: packages/mermaid/dist path: packages/mermaid/dist
- name: Upload Mermaid Mindmap Build as Artifact - name: Upload Mermaid Mindmap Build as Artifact
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: mermaid-mindmap-build name: mermaid-mindmap-build
path: packages/mermaid-mindmap/dist path: packages/mermaid-mindmap/dist

View File

@@ -1,6 +1,6 @@
{ {
"name": "mermaid", "name": "mermaid",
"version": "10.9.3", "version": "10.9.4",
"description": "Markdown-ish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.", "description": "Markdown-ish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.",
"type": "module", "type": "module",
"module": "./dist/mermaid.core.mjs", "module": "./dist/mermaid.core.mjs",

View File

@@ -1,7 +1,7 @@
import { select } from 'd3'; import { select } from 'd3';
import { log } from '../logger.js';
import { getConfig } from '../diagram-api/diagramAPI.js'; import { getConfig } from '../diagram-api/diagramAPI.js';
import { evaluate } from '../diagrams/common/common.js'; import { evaluate, sanitizeText } from '../diagrams/common/common.js';
import { log } from '../logger.js';
import { decodeEntities } from '../utils.js'; import { decodeEntities } from '../utils.js';
/** /**
@@ -16,22 +16,26 @@ function applyStyle(dom, styleFn) {
/** /**
* @param {any} node * @param {any} node
* @param config
* @returns {SVGForeignObjectElement} Node * @returns {SVGForeignObjectElement} Node
*/ */
function addHtmlLabel(node) { function addHtmlLabel(node, config) {
const fo = select(document.createElementNS('http://www.w3.org/2000/svg', 'foreignObject')); const fo = select(document.createElementNS('http://www.w3.org/2000/svg', 'foreignObject'));
const div = fo.append('xhtml:div'); const div = fo.append('xhtml:div');
const label = node.label; const label = node.label;
const labelClass = node.isNode ? 'nodeLabel' : 'edgeLabel'; const labelClass = node.isNode ? 'nodeLabel' : 'edgeLabel';
div.html( div.html(
'<span class="' + sanitizeText(
labelClass + '<span class="' +
'" ' + labelClass +
(node.labelStyle ? 'style="' + node.labelStyle + '"' : '') + '" ' +
'>' + (node.labelStyle ? 'style="' + node.labelStyle + '"' : '') +
label + '>' +
'</span>' label +
'</span>',
config
)
); );
applyStyle(div, node.labelStyle); applyStyle(div, node.labelStyle);
@@ -53,7 +57,8 @@ const createLabel = (_vertexText, style, isTitle, isNode) => {
if (typeof vertexText === 'object') { if (typeof vertexText === 'object') {
vertexText = vertexText[0]; vertexText = vertexText[0];
} }
if (evaluate(getConfig().flowchart.htmlLabels)) { const config = getConfig();
if (evaluate(config.flowchart.htmlLabels)) {
// TODO: addHtmlLabel accepts a labelStyle. Do we possibly have that? // TODO: addHtmlLabel accepts a labelStyle. Do we possibly have that?
vertexText = vertexText.replace(/\\n|\n/g, '<br />'); vertexText = vertexText.replace(/\\n|\n/g, '<br />');
log.debug('vertexText' + vertexText); log.debug('vertexText' + vertexText);
@@ -65,7 +70,7 @@ const createLabel = (_vertexText, style, isTitle, isNode) => {
), ),
labelStyle: style.replace('fill:', 'color:'), labelStyle: style.replace('fill:', 'color:'),
}; };
let vertexNode = addHtmlLabel(node); let vertexNode = addHtmlLabel(node, config);
// vertexNode.parentNode.removeChild(vertexNode); // vertexNode.parentNode.removeChild(vertexNode);
return vertexNode; return vertexNode;
} else { } else {

View File

@@ -311,9 +311,8 @@ export const hasKatex = (text: string): boolean => (text.match(katexRegex)?.leng
* @returns Object containing \{width, height\} * @returns Object containing \{width, height\}
*/ */
export const calculateMathMLDimensions = async (text: string, config: MermaidConfig) => { export const calculateMathMLDimensions = async (text: string, config: MermaidConfig) => {
text = await renderKatex(text, config);
const divElem = document.createElement('div'); const divElem = document.createElement('div');
divElem.innerHTML = text; divElem.innerHTML = await renderKatexSanitized(text, config);
divElem.id = 'katex-temp'; divElem.id = 'katex-temp';
divElem.style.visibility = 'hidden'; divElem.style.visibility = 'hidden';
divElem.style.position = 'absolute'; divElem.style.position = 'absolute';
@@ -325,14 +324,7 @@ export const calculateMathMLDimensions = async (text: string, config: MermaidCon
return dim; return dim;
}; };
/** const renderKatexUnsanitized = async (text: string, config: MermaidConfig): Promise<string> => {
* Attempts to render and return the KaTeX portion of a string with MathML
*
* @param text - The text to test
* @param config - Configuration for Mermaid
* @returns String containing MathML if KaTeX is supported, or an error message if it is not and stylesheets aren't present
*/
export const renderKatex = async (text: string, config: MermaidConfig): Promise<string> => {
if (!hasKatex(text)) { if (!hasKatex(text)) {
return text; return text;
} }
@@ -366,6 +358,20 @@ export const renderKatex = async (text: string, config: MermaidConfig): Promise<
); );
}; };
/**
* Attempts to render and return the KaTeX portion of a string with MathML
*
* @param text - The text to test
* @param config - Configuration for Mermaid
* @returns String containing MathML if KaTeX is supported, or an error message if it is not and stylesheets aren't present
*/
export const renderKatexSanitized = async (
text: string,
config: MermaidConfig
): Promise<string> => {
return sanitizeText(await renderKatexUnsanitized(text, config), config);
};
export default { export default {
getRows, getRows,
sanitizeText, sanitizeText,

View File

@@ -1,13 +1,12 @@
import * as graphlib from 'dagre-d3-es/src/graphlib/index.js'; import { curveLinear, select, selectAll } from 'd3';
import { select, curveLinear, selectAll } from 'd3';
import { getConfig } from '../../diagram-api/diagramAPI.js';
import utils from '../../utils.js';
import { render } from '../../dagre-wrapper/index.js';
import { addHtmlLabel } from 'dagre-d3-es/src/dagre-js/label/add-html-label.js'; import { addHtmlLabel } from 'dagre-d3-es/src/dagre-js/label/add-html-label.js';
import * as graphlib from 'dagre-d3-es/src/graphlib/index.js';
import { render } from '../../dagre-wrapper/index.js';
import { getConfig } from '../../diagram-api/diagramAPI.js';
import { log } from '../../logger.js'; import { log } from '../../logger.js';
import common, { evaluate, renderKatex } from '../common/common.js';
import { interpolateToCurve, getStylesFromArray } from '../../utils.js';
import { setupGraphViewbox } from '../../setupGraphViewbox.js'; import { setupGraphViewbox } from '../../setupGraphViewbox.js';
import utils, { getStylesFromArray, interpolateToCurve } from '../../utils.js';
import common, { evaluate, renderKatexSanitized } from '../common/common.js';
const conf = {}; const conf = {};
export const setConf = function (cnf) { export const setConf = function (cnf) {
@@ -140,7 +139,7 @@ export const addVertices = async function (vert, g, svgId, root, doc, diagObj) {
default: default:
_shape = 'rect'; _shape = 'rect';
} }
const labelText = await renderKatex(vertexText, getConfig()); const labelText = await renderKatexSanitized(vertexText, getConfig());
// Add the node // Add the node
g.setNode(vertex.id, { g.setNode(vertex.id, {
@@ -315,7 +314,10 @@ export const addEdges = async function (edges, g, diagObj) {
edgeData.labelpos = 'c'; edgeData.labelpos = 'c';
} }
edgeData.labelType = edge.labelType; edgeData.labelType = edge.labelType;
edgeData.label = await renderKatex(edge.text.replace(common.lineBreakRegex, '\n'), getConfig()); edgeData.label = await renderKatexSanitized(
edge.text.replace(common.lineBreakRegex, '\n'),
getConfig()
);
if (edge.style === undefined) { if (edge.style === undefined) {
edgeData.style = edgeData.style || 'stroke: #333; stroke-width: 1.5px;fill:none;'; edgeData.style = edgeData.style || 'stroke: #333; stroke-width: 1.5px;fill:none;';

View File

@@ -1,13 +1,13 @@
import * as graphlib from 'dagre-d3-es/src/graphlib/index.js'; import { curveLinear, select, selectAll } from 'd3';
import { select, curveLinear, selectAll } from 'd3';
import { getConfig } from '../../diagram-api/diagramAPI.js';
import { render as Render } from 'dagre-d3-es'; import { render as Render } from 'dagre-d3-es';
import { applyStyle } from 'dagre-d3-es/src/dagre-js/util.js';
import { addHtmlLabel } from 'dagre-d3-es/src/dagre-js/label/add-html-label.js'; import { addHtmlLabel } from 'dagre-d3-es/src/dagre-js/label/add-html-label.js';
import { applyStyle } from 'dagre-d3-es/src/dagre-js/util.js';
import * as graphlib from 'dagre-d3-es/src/graphlib/index.js';
import { getConfig } from '../../diagram-api/diagramAPI.js';
import { log } from '../../logger.js'; import { log } from '../../logger.js';
import common, { evaluate, renderKatex } from '../common/common.js';
import { interpolateToCurve, getStylesFromArray } from '../../utils.js';
import { setupGraphViewbox } from '../../setupGraphViewbox.js'; import { setupGraphViewbox } from '../../setupGraphViewbox.js';
import { getStylesFromArray, interpolateToCurve } from '../../utils.js';
import common, { evaluate, renderKatexSanitized } from '../common/common.js';
import flowChartShapes from './flowChartShapes.js'; import flowChartShapes from './flowChartShapes.js';
const conf = {}; const conf = {};
@@ -57,7 +57,7 @@ export const addVertices = async function (vert, g, svgId, root, _doc, diagObj)
if (evaluate(getConfig().flowchart.htmlLabels)) { if (evaluate(getConfig().flowchart.htmlLabels)) {
// TODO: addHtmlLabel accepts a labelStyle. Do we possibly have that? // TODO: addHtmlLabel accepts a labelStyle. Do we possibly have that?
const node = { const node = {
label: await renderKatex( label: await renderKatexSanitized(
vertexText.replace( vertexText.replace(
/fa[blrs]?:fa-[\w-]+/g, // cspell:disable-line /fa[blrs]?:fa-[\w-]+/g, // cspell:disable-line
(s) => `<i class='${s.replace(':', ' ')}'></i>` (s) => `<i class='${s.replace(':', ' ')}'></i>`
@@ -242,7 +242,7 @@ export const addEdges = async function (edges, g, diagObj) {
edgeData.labelType = 'html'; edgeData.labelType = 'html';
edgeData.label = `<span id="L-${linkId}" class="edgeLabel L-${linkNameStart}' L-${linkNameEnd}" style="${ edgeData.label = `<span id="L-${linkId}" class="edgeLabel L-${linkNameStart}' L-${linkNameEnd}" style="${
edgeData.labelStyle edgeData.labelStyle
}">${await renderKatex( }">${await renderKatexSanitized(
edge.text.replace( edge.text.replace(
/fa[blrs]?:fa-[\w-]+/g, // cspell:disable-line /fa[blrs]?:fa-[\w-]+/g, // cspell:disable-line
(s) => `<i class='${s.replace(':', ' ')}'></i>` (s) => `<i class='${s.replace(':', ' ')}'></i>`

View File

@@ -1,9 +1,12 @@
import common, { calculateMathMLDimensions, hasKatex, renderKatex } from '../common/common.js';
import * as svgDrawCommon from '../common/svgDrawCommon.js';
import { addFunction } from '../../interactionDb.js';
import { ZERO_WIDTH_SPACE, parseFontSize } from '../../utils.js';
import { sanitizeUrl } from '@braintree/sanitize-url'; import { sanitizeUrl } from '@braintree/sanitize-url';
import * as configApi from '../../config.js'; import * as configApi from '../../config.js';
import { ZERO_WIDTH_SPACE, parseFontSize } from '../../utils.js';
import common, {
calculateMathMLDimensions,
hasKatex,
renderKatexSanitized,
} from '../common/common.js';
import * as svgDrawCommon from '../common/svgDrawCommon.js';
export const ACTOR_TYPE_WIDTH = 18 * 2; export const ACTOR_TYPE_WIDTH = 18 * 2;
const TOP_ACTOR_CLASS = 'actor-top'; const TOP_ACTOR_CLASS = 'actor-top';
@@ -86,13 +89,13 @@ const popupMenuToggle = function (popId) {
export const drawKatex = async function (elem, textData, msgModel = null) { export const drawKatex = async function (elem, textData, msgModel = null) {
let textElem = elem.append('foreignObject'); let textElem = elem.append('foreignObject');
const lines = await renderKatex(textData.text, configApi.getConfig()); const linesSanitized = await renderKatexSanitized(textData.text, configApi.getConfig());
const divElem = textElem const divElem = textElem
.append('xhtml:div') .append('xhtml:div')
.attr('style', 'width: fit-content;') .attr('style', 'width: fit-content;')
.attr('xmlns', 'http://www.w3.org/1999/xhtml') .attr('xmlns', 'http://www.w3.org/1999/xhtml')
.html(lines); .html(linesSanitized);
const dim = divElem.node().getBoundingClientRect(); const dim = divElem.node().getBoundingClientRect();
textElem.attr('height', Math.round(dim.height)).attr('width', Math.round(dim.width)); textElem.attr('height', Math.round(dim.height)).attr('width', Math.round(dim.width));
@@ -963,7 +966,7 @@ const _drawTextCandidateFunc = (function () {
.append('div') .append('div')
.style('text-align', 'center') .style('text-align', 'center')
.style('vertical-align', 'middle') .style('vertical-align', 'middle')
.html(await renderKatex(content, configApi.getConfig())); .html(await renderKatexSanitized(content, configApi.getConfig()));
byTspan(content, s, x, y, width, height, textAttrs, conf); byTspan(content, s, x, y, width, height, textAttrs, conf);
_setTextAttrs(text, textAttrs); _setTextAttrs(text, textAttrs);

View File

@@ -1,6 +1,8 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
// @ts-nocheck TODO: Fix types // @ts-nocheck TODO: Fix types
import { getConfig } from '../config.js';
import type { Group } from '../diagram-api/types.js'; import type { Group } from '../diagram-api/types.js';
import { sanitizeText } from '../diagrams/common/common.js';
import type { D3TSpanElement, D3TextElement } from '../diagrams/common/commonTypes.js'; import type { D3TSpanElement, D3TextElement } from '../diagrams/common/commonTypes.js';
import { log } from '../logger.js'; import { log } from '../logger.js';
import { markdownToHTML, markdownToLines } from '../rendering-util/handle-markdown-text.js'; import { markdownToHTML, markdownToLines } from '../rendering-util/handle-markdown-text.js';
@@ -21,12 +23,15 @@ function addHtmlSpan(element, node, width, classes, addBackground = false) {
const label = node.label; const label = node.label;
const labelClass = node.isNode ? 'nodeLabel' : 'edgeLabel'; const labelClass = node.isNode ? 'nodeLabel' : 'edgeLabel';
div.html( div.html(
` sanitizeText(
`
<span class="${labelClass} ${classes}" ` + <span class="${labelClass} ${classes}" ` +
(node.labelStyle ? 'style="' + node.labelStyle + '"' : '') + (node.labelStyle ? 'style="' + node.labelStyle + '"' : '') +
'>' + '>' +
label + label +
'</span>' '</span>',
getConfig()
)
); );
applyStyle(div, node.labelStyle); applyStyle(div, node.labelStyle);