mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-11-15 02:04:08 +01:00
Merge branch 'develop' into fix/sequence-diagram-autonumber-arrow-positioning
This commit is contained in:
5
.changeset/brave-baths-behave.md
Normal file
5
.changeset/brave-baths-behave.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'mermaid': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fix: Prevent HTML tags from being escaped in sandbox label rendering
|
||||||
5
.changeset/ten-plums-bet.md
Normal file
5
.changeset/ten-plums-bet.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'mermaid': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fix: Support ComponentQueue_Ext to prevent parsing error
|
||||||
@@ -98,12 +98,21 @@ export const openURLAndVerifyRendering = (
|
|||||||
|
|
||||||
cy.visit(url);
|
cy.visit(url);
|
||||||
cy.window().should('have.property', 'rendered', true);
|
cy.window().should('have.property', 'rendered', true);
|
||||||
cy.get('svg').should('be.visible');
|
|
||||||
// cspell:ignore viewbox
|
|
||||||
cy.get('svg').should('not.have.attr', 'viewbox');
|
|
||||||
|
|
||||||
if (validation) {
|
// Handle sandbox mode where SVG is inside an iframe
|
||||||
cy.get('svg').should(validation);
|
if (options.securityLevel === 'sandbox') {
|
||||||
|
cy.get('iframe').should('be.visible');
|
||||||
|
if (validation) {
|
||||||
|
cy.get('iframe').should(validation);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cy.get('svg').should('be.visible');
|
||||||
|
// cspell:ignore viewbox
|
||||||
|
cy.get('svg').should('not.have.attr', 'viewbox');
|
||||||
|
|
||||||
|
if (validation) {
|
||||||
|
cy.get('svg').should(validation);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (screenshot) {
|
if (screenshot) {
|
||||||
|
|||||||
@@ -114,4 +114,28 @@ describe('C4 diagram', () => {
|
|||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
it('C4.6 should render C4Context diagram with ComponentQueue_Ext', () => {
|
||||||
|
imgSnapshotTest(
|
||||||
|
`
|
||||||
|
C4Context
|
||||||
|
title System Context diagram with ComponentQueue_Ext
|
||||||
|
|
||||||
|
Enterprise_Boundary(b0, "BankBoundary0") {
|
||||||
|
Person(customerA, "Banking Customer A", "A customer of the bank, with personal bank accounts.")
|
||||||
|
|
||||||
|
System(SystemAA, "Internet Banking System", "Allows customers to view information about their bank accounts, and make payments.")
|
||||||
|
|
||||||
|
Enterprise_Boundary(b1, "BankBoundary") {
|
||||||
|
ComponentQueue_Ext(msgQueue, "Message Queue", "RabbitMQ", "External message queue system for processing banking transactions")
|
||||||
|
System_Ext(SystemC, "E-mail system", "The internal Microsoft Exchange e-mail system.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BiRel(customerA, SystemAA, "Uses")
|
||||||
|
Rel(SystemAA, msgQueue, "Sends messages to")
|
||||||
|
Rel(SystemAA, SystemC, "Sends e-mails", "SMTP")
|
||||||
|
`,
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -79,6 +79,18 @@ describe('Flowchart v2', () => {
|
|||||||
{ htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' }
|
{ htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' }
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
it('6a: should render complex HTML in labels with sandbox security', () => {
|
||||||
|
imgSnapshotTest(
|
||||||
|
`flowchart TD
|
||||||
|
A[Christmas] -->|Get money| B(Go shopping)
|
||||||
|
B --> C{Let me think}
|
||||||
|
C -->|One| D[Laptop]
|
||||||
|
C -->|Two| E[iPhone]
|
||||||
|
C -->|Three| F[fa:fa-car Car]
|
||||||
|
`,
|
||||||
|
{ securityLevel: 'sandbox', flowchart: { htmlLabels: true } }
|
||||||
|
);
|
||||||
|
});
|
||||||
it('7: should render a flowchart when useMaxWidth is true (default)', () => {
|
it('7: should render a flowchart when useMaxWidth is true (default)', () => {
|
||||||
renderGraph(
|
renderGraph(
|
||||||
`flowchart TD
|
`flowchart TD
|
||||||
|
|||||||
58
packages/mermaid/src/diagrams/c4/parser/c4Component.spec.js
Normal file
58
packages/mermaid/src/diagrams/c4/parser/c4Component.spec.js
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import c4Db from '../c4Db.js';
|
||||||
|
import c4 from './c4Diagram.jison';
|
||||||
|
import { setConfig } from '../../../config.js';
|
||||||
|
|
||||||
|
setConfig({
|
||||||
|
securityLevel: 'strict',
|
||||||
|
});
|
||||||
|
|
||||||
|
describe.each([
|
||||||
|
['Component', 'component'],
|
||||||
|
['ComponentDb', 'component_db'],
|
||||||
|
['ComponentQueue', 'component_queue'],
|
||||||
|
['Component_Ext', 'external_component'],
|
||||||
|
['ComponentDb_Ext', 'external_component_db'],
|
||||||
|
['ComponentQueue_Ext', 'external_component_queue'],
|
||||||
|
])('parsing a C4 %s', function (macroName, elementName) {
|
||||||
|
beforeEach(function () {
|
||||||
|
c4.parser.yy = c4Db;
|
||||||
|
c4.parser.yy.clear();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse a C4 diagram with one Component correctly', function () {
|
||||||
|
c4.parser.parse(`C4Component
|
||||||
|
title Component diagram for Internet Banking Component
|
||||||
|
${macroName}(ComponentAA, "Internet Banking Component", "Technology", "Allows customers to view information about their bank accounts, and make payments.")`);
|
||||||
|
|
||||||
|
const yy = c4.parser.yy;
|
||||||
|
|
||||||
|
const shapes = yy.getC4ShapeArray();
|
||||||
|
expect(shapes.length).toBe(1);
|
||||||
|
const onlyShape = shapes[0];
|
||||||
|
|
||||||
|
expect(onlyShape).toMatchObject({
|
||||||
|
alias: 'ComponentAA',
|
||||||
|
descr: {
|
||||||
|
text: 'Allows customers to view information about their bank accounts, and make payments.',
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
text: 'Internet Banking Component',
|
||||||
|
},
|
||||||
|
techn: {
|
||||||
|
text: 'Technology',
|
||||||
|
},
|
||||||
|
typeC4Shape: {
|
||||||
|
text: elementName,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle a trailing whitespaces after Component', function () {
|
||||||
|
const whitespace = ' ';
|
||||||
|
const rendered = c4.parser.parse(`C4Component${whitespace}
|
||||||
|
title Component diagram for Internet Banking Component${whitespace}
|
||||||
|
${macroName}(ComponentAA, "Internet Banking Component", "Technology", "Allows customers to view information about their bank accounts, and make payments.")${whitespace}`);
|
||||||
|
|
||||||
|
expect(rendered).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -158,10 +158,10 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multiline");}
|
|||||||
"UpdateRelStyle" { this.begin("update_rel_style"); return 'UPDATE_REL_STYLE';}
|
"UpdateRelStyle" { this.begin("update_rel_style"); return 'UPDATE_REL_STYLE';}
|
||||||
"UpdateLayoutConfig" { this.begin("update_layout_config"); return 'UPDATE_LAYOUT_CONFIG';}
|
"UpdateLayoutConfig" { this.begin("update_layout_config"); return 'UPDATE_LAYOUT_CONFIG';}
|
||||||
|
|
||||||
<person,person_ext,system_ext_queue,system_ext_db,system_ext,system_queue,system_db,system,boundary,enterprise_boundary,system_boundary,container_ext_db,container_ext_queue,container_ext,container_queue,container_db,container,container_boundary,component_ext_db,component_ext,component_queue,component_db,component,node,node_l,node_r,rel,birel,rel_u,rel_d,rel_l,rel_r,rel_b,rel_index,update_el_style,update_rel_style,update_layout_config><<EOF>> return "EOF_IN_STRUCT";
|
<person,person_ext,system_ext_queue,system_ext_db,system_ext,system_queue,system_db,system,boundary,enterprise_boundary,system_boundary,container_ext_db,container_ext_queue,container_ext,container_queue,container_db,container,container_boundary,component_ext_db,component_ext_queue,component_ext,component_queue,component_db,component,node,node_l,node_r,rel,birel,rel_u,rel_d,rel_l,rel_r,rel_b,rel_index,update_el_style,update_rel_style,update_layout_config><<EOF>> return "EOF_IN_STRUCT";
|
||||||
<person,person_ext,system_ext_queue,system_ext_db,system_ext,system_queue,system_db,system,boundary,enterprise_boundary,system_boundary,container_ext_db,container_ext_queue,container_ext,container_queue,container_db,container,container_boundary,component_ext_db,component_ext,component_queue,component_db,component,node,node_l,node_r,rel,birel,rel_u,rel_d,rel_l,rel_r,rel_b,rel_index,update_el_style,update_rel_style,update_layout_config>[(][ ]*[,] { this.begin("attribute"); return "ATTRIBUTE_EMPTY";}
|
<person,person_ext,system_ext_queue,system_ext_db,system_ext,system_queue,system_db,system,boundary,enterprise_boundary,system_boundary,container_ext_db,container_ext_queue,container_ext,container_queue,container_db,container,container_boundary,component_ext_db,component_ext_queue,component_ext,component_queue,component_db,component,node,node_l,node_r,rel,birel,rel_u,rel_d,rel_l,rel_r,rel_b,rel_index,update_el_style,update_rel_style,update_layout_config>[(][ ]*[,] { this.begin("attribute"); return "ATTRIBUTE_EMPTY";}
|
||||||
<person,person_ext,system_ext_queue,system_ext_db,system_ext,system_queue,system_db,system,boundary,enterprise_boundary,system_boundary,container_ext_db,container_ext_queue,container_ext,container_queue,container_db,container,container_boundary,component_ext_db,component_ext,component_queue,component_db,component,node,node_l,node_r,rel,birel,rel_u,rel_d,rel_l,rel_r,rel_b,rel_index,update_el_style,update_rel_style,update_layout_config>[(] { this.begin("attribute"); }
|
<person,person_ext,system_ext_queue,system_ext_db,system_ext,system_queue,system_db,system,boundary,enterprise_boundary,system_boundary,container_ext_db,container_ext_queue,container_ext,container_queue,container_db,container,container_boundary,component_ext_db,component_ext_queue,component_ext,component_queue,component_db,component,node,node_l,node_r,rel,birel,rel_u,rel_d,rel_l,rel_r,rel_b,rel_index,update_el_style,update_rel_style,update_layout_config>[(] { this.begin("attribute"); }
|
||||||
<person,person_ext,system_ext_queue,system_ext_db,system_ext,system_queue,system_db,system,boundary,enterprise_boundary,system_boundary,container_ext_db,container_ext_queue,container_ext,container_queue,container_db,container,container_boundary,component_ext_db,component_ext,component_queue,component_db,component,node,node_l,node_r,rel,birel,rel_u,rel_d,rel_l,rel_r,rel_b,rel_index,update_el_style,update_rel_style,update_layout_config,attribute>[)] { this.popState();this.popState();}
|
<person,person_ext,system_ext_queue,system_ext_db,system_ext,system_queue,system_db,system,boundary,enterprise_boundary,system_boundary,container_ext_db,container_ext_queue,container_ext,container_queue,container_db,container,container_boundary,component_ext_db,component_ext_queue,component_ext,component_queue,component_db,component,node,node_l,node_r,rel,birel,rel_u,rel_d,rel_l,rel_r,rel_b,rel_index,update_el_style,update_rel_style,update_layout_config,attribute>[)] { this.popState();this.popState();}
|
||||||
|
|
||||||
<attribute>",," { return 'ATTRIBUTE_EMPTY';}
|
<attribute>",," { return 'ATTRIBUTE_EMPTY';}
|
||||||
<attribute>"," { }
|
<attribute>"," { }
|
||||||
|
|||||||
@@ -70,6 +70,31 @@ describe('Sanitize text', () => {
|
|||||||
});
|
});
|
||||||
expect(result).not.toContain('javascript:alert(1)');
|
expect(result).not.toContain('javascript:alert(1)');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should allow HTML tags in sandbox mode', () => {
|
||||||
|
const htmlStr = '<p>This is a <strong>bold</strong> text</p>';
|
||||||
|
const result = sanitizeText(htmlStr, {
|
||||||
|
securityLevel: 'sandbox',
|
||||||
|
flowchart: { htmlLabels: true },
|
||||||
|
});
|
||||||
|
expect(result).toContain('<p>');
|
||||||
|
expect(result).toContain('<strong>');
|
||||||
|
expect(result).toContain('</strong>');
|
||||||
|
expect(result).toContain('</p>');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove script tags in sandbox mode', () => {
|
||||||
|
const maliciousStr = '<p>Hello <script>alert(1)</script> world</p>';
|
||||||
|
const result = sanitizeText(maliciousStr, {
|
||||||
|
securityLevel: 'sandbox',
|
||||||
|
flowchart: { htmlLabels: true },
|
||||||
|
});
|
||||||
|
expect(result).not.toContain('<script>');
|
||||||
|
expect(result).not.toContain('alert(1)');
|
||||||
|
expect(result).toContain('<p>');
|
||||||
|
expect(result).toContain('Hello');
|
||||||
|
expect(result).toContain('world');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('generic parser', () => {
|
describe('generic parser', () => {
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ export const removeScript = (txt: string): string => {
|
|||||||
const sanitizeMore = (text: string, config: MermaidConfig) => {
|
const sanitizeMore = (text: string, config: MermaidConfig) => {
|
||||||
if (config.flowchart?.htmlLabels !== false) {
|
if (config.flowchart?.htmlLabels !== false) {
|
||||||
const level = config.securityLevel;
|
const level = config.securityLevel;
|
||||||
if (level === 'antiscript' || level === 'strict') {
|
if (level === 'antiscript' || level === 'strict' || level === 'sandbox') {
|
||||||
text = removeScript(text);
|
text = removeScript(text);
|
||||||
} else if (level !== 'loose') {
|
} else if (level !== 'loose') {
|
||||||
text = breakToPlaceholder(text);
|
text = breakToPlaceholder(text);
|
||||||
|
|||||||
Reference in New Issue
Block a user