diff --git a/README.md b/README.md index 01e505792..1f94027cf 100644 --- a/README.md +++ b/README.md @@ -215,6 +215,14 @@ Mermaid is a growing community and is always accepting new contributors. There's Detailed information about how to contribute can be found in the [contribution guide](CONTRIBUTING.md) +## Security and safe diagrams + +For public sites, it can be precarious to retrieve text from users on the internet, storing that content for presentation in a browser at a later stage. The reason is that the user content can contain embedded malicious scripts that will run when the data is presented. For Mermaid this is a risk, specially as mermaid diagrams contain many characters that are used in html which makes the standard sanitation unusable as it also breaks the diagrams. We still make an effort to sanitise the incoming code and keep refining the process but it is hard to guarantee that there are no loop holes. + +As an extra level of security for sites with external users we are happy to introduce a new security level in which the diagram is rendered in a sandboxed iframe preventing javascript in the code from being executed. This is a great step forward for better security. + +*Unfortunately you can not have a cake and eat it at the same time which in this case means that some of the interactive functionality gets blocked along with the possible malicious code.* + ## Reporting vulnerabilities To report a vulnerability, please e-mail security@mermaid.live with a description of the issue, the steps you took to create the issue, affected versions, and if known, mitigations for the issue. diff --git a/cypress/platform/click_security_sandbox.html b/cypress/platform/click_security_sandbox.html new file mode 100644 index 000000000..275fea640 --- /dev/null +++ b/cypress/platform/click_security_sandbox.html @@ -0,0 +1,170 @@ + + + + + + Mermaid Quick Test Page + + + + +
+
+ graph TB + Function-->URL + click Function clickByFlow "Add a div" + click URL "http://localhost:9000/webpackUsage.html" "Visit mermaid docs" +
+
+ graph TB + 1Function--->2URL + click 1Function clickByFlow "Add a div" + click 2URL "http://localhost:9000/webpackUsage.html" "Visit mermaid docs" +
+
+ flowchart TB + Function-->URL + click Function clickByFlow "Add a div" + click URL "http://localhost:9000/webpackUsage.html" "Visit mermaid docs" _self +
+
+ flowchart TB + 1Function--->2URL + click 1Function clickByFlow "Add a div" + click 2URL "http://localhost:9000/webpackUsage.html" "Visit mermaid docs" _self +
+ +
+ classDiagram + class ShapeLink + link ShapeLink "http://localhost:9000/webpackUsage.html" "This is a tooltip for a link" + class ShapeCallback + callback ShapeCallback "clickByClass" "This is a tooltip for a callback" +
+
+ classDiagram-v2 + class ShapeLink2 + link ShapeLink2 "http://localhost:9000/webpackUsage.html" "This is a tooltip for a link" + class ShapeCallback2 + callback ShapeCallback2 "clickByClass" "This is a tooltip for a callback" +
+ +
+ +
+ gantt + dateFormat YYYY-MM-DD + axisFormat %d/%m + title Adding GANTT diagram to mermaid + excludes weekdays 2014-01-10 + + section A section + Completed task :done, des1, 2014-01-06,2014-01-08 + Active task :active, des2, 2014-01-09, 3d + Future task : des3, after des2, 5d + Future task2 : des4, after des3, 5d + + section Critical tasks + Completed task in the critical line :crit, done, 2014-01-06,24h + Implement parser and jison :crit, done, after des1, 2d + Create tests for parser :crit, active, 3d + Future task in critical line :crit, 5d + Create tests for renderer :2d + Add to mermaid :1d + + section Documentation + Describe gantt syntax :active, a1, after des1, 3d + Add gantt diagram to demo page :after a1 , 20h + Add another diagram to demo page :doc1, after a1 , 48h + + section Clickable + Visit mermaidjs :active, cl1, 2014-01-07,2014-01-10 + Calling a Callback (look at the console log) :cl2, after cl1, 3d + Calling a Callback with args :cl3, after cl1, 3d + + click cl1 href "http://localhost:9000/webpackUsage.html" + click cl2 call clickByGantt() + click cl3 call clickByGantt("test1", test2, test3) + + section Last section + Describe gantt syntax :after doc1, 3d + Add gantt diagram to demo page : 20h + Add another diagram to demo page : 48h +
+
+
+ graph TB + FunctionArg-->URL + click FunctionArg call clickByFlowArg(ARGUMENT) "Add a div" + click URL "http://localhost:9000/webpackUsage.html" "Visit mermaid docs" +
+
+ flowchart TB + FunctionArg-->URL + click FunctionArg call clickByFlowArg(ARGUMENT) "Add a div" + click URL "http://localhost:9000/webpackUsage.html" "Visit mermaid docs" +
+ +
+ classDiagram + class ShapeLink + link ShapeLink "http://localhost:9000/webpackUsage.html" "This is a tooltip for a link" + class ShapeCallback + click ShapeCallback call clickByClass(123) "This is a tooltip for a callback" +
+ +
+ classDiagram-v2 + class ShapeLink2 + link ShapeLink2 "http://localhost:9000/webpackUsage.html" "This is a tooltip for a link" + class ShapeCallback2 + click ShapeCallback2 call clickByClass(123) "This is a tooltip for a callback" +
+ +
+ + + + + diff --git a/cypress/platform/knsv.html b/cypress/platform/knsv.html index 2548d38d5..0ecd9d9dc 100644 --- a/cypress/platform/knsv.html +++ b/cypress/platform/knsv.html @@ -14,6 +14,7 @@ font-family: 'Arial'; /* font-size: 18px !important; */ width: 100%; + display: flex; } h1 { color: grey;} .mermaid2,.mermaid3 { @@ -25,15 +26,214 @@ -
info below
-
+
+pie title Pets adopted by volunteers + "Dogs" : 386 + "Cats" : 85 + "Rats" : 15 +
+
+gantt + title Adding GANTT diagram functionality to mermaid + excludes :excludes the named dates/days from being included in a charted task.. + section Screening + Lexplore :active, des1, 2023-01-06,2023-01-08 + H4 :active, des2, 2024-01-09, 3d + Future task : des3, after des2, 5d + Future task2 : des4, after des3, 5d -
-stateDiagram-v2 +
+
+info +
+
+gitGraph: +options +{ + "nodeSpacing": 150, + "nodeRadius": 10 +} +end +commit +branch newbranch +checkout newbranch +commit +commit +checkout master +commit +commit +merge newbranch +
+
+sequenceDiagram + participant a as Alice + participant j as John + note right of a: Hello world! + properties a: {"class": "internal-service-actor", "type": "@clock"} + properties j: {"class": "external-service-actor", "type": "@computer"} + links a: {"Repo": "https://www.contoso.com/repo", "Swagger": "https://www.contoso.com/swagger"} + links j: {"Repo": "https://www.contoso.com/repo"} + links a: {"Dashboard": "https://www.contoso.com/dashboard", "On-Call": "https://www.contoso.com/oncall"} + link a: Contacts @ https://contacts.contoso.com/?contact=alice@contoso.com + a->>j: Hello John, how are you? + j-->>a: Great!
+
+journey + title My working day + section Go to work + Make tea: 5: Me + Go upstairs: 3: Me + Do work: 1: Me, Cat + section Go home + Go downstairs: 5: Me + Sit down: 5: Me +
+
+requirementDiagram + + requirement test_req { + id: 1 + text: the test text. + risk: high + verifymethod: test + } + + functionalRequirement test_req2 { + id: 1.1 + text: the second test text. + risk: low + verifymethod: inspection + } + + performanceRequirement test_req3 { + id: 1.2 + text: the third test text. + risk: medium + verifymethod: demonstration + } + + element test_entity { + type: simulation + } + + element test_entity2 { + type: word doc + docRef: reqs/test_entity + } + + + test_entity - satisfies -> test_req2 + test_req - traces -> test_req2 + test_req - contains -> test_req3 + test_req <- copies - test_entity2 +
+
+ erDiagram + CUSTOMER }|..|{ DELIVERY-ADDRESS : has + CUSTOMER ||--o{ ORDER : places + CUSTOMER ||--o{ INVOICE : "liable for" + DELIVERY-ADDRESS ||--o{ ORDER : receives + INVOICE ||--|{ ORDER : covers + ORDER ||--|{ ORDER-ITEM : includes + PRODUCT-CATEGORY ||--|{ PRODUCT : contains + PRODUCT ||--o{ ORDER-ITEM : "ordered in" +
+
+ graph TB + subgraph One + a1-->a2-->a3 + end +
+
+ flowchart LR + Function-->URL-->A-->B-->C + click Function clickByFlow "Add a div" + click URL "https://mermaid-js.github.io/mermaid/#/" "Visit mermaid docs" _blank +
+
+ gantt + dateFormat YYYY-MM-DD + axisFormat %d/%m + title Adding GANTT diagram to mermaid + excludes weekdays 2014-01-10 + + section A section + Completed task :done, des1, 2014-01-06,2014-01-08 + Active task :active, des2, 2014-01-09, 3d + Future task : des3, after des2, 5d + Future task2 : des4, after des3, 5d + + section Critical tasks + Completed task in the critical line :crit, done, 2014-01-06,24h + Implement parser and jison :crit, done, after des1, 2d + Create tests for parser :crit, active, 3d + Future task in critical line :crit, 5d + Create tests for renderer :2d + Add to mermaid :1d + + section Documentation + Describe gantt syntax :active, a1, after des1, 3d + Add gantt diagram to demo page :after a1 , 20h + Add another diagram to demo page :doc1, after a1 , 48h + + section Clickable + Visit mermaidjs :active, cl1, 2014-01-07,2014-01-10 + Calling a Callback (look at the console log) :cl2, after cl1, 3d + Calling a Callback with args :cl3, after cl1, 3d + + click cl1 href "https://mermaid-js.github.io/mermaid/#/" + click cl2 call clickByGantt() + click cl3 call clickByGantt("test1", test2, test3) + + section Last section + Describe gantt syntax :after doc1, 3d + Add gantt diagram to demo page : 20h + Add another diagram to demo page : 48h +
+
+classDiagram +Class01 <|-- AveryLongClass : Cool +Class09 --> C2 : Where am i? +Class09 --* C3 +Class09 --|> Class07 +Class07 : equals() +Class07 : Object[] elementData +Class01 : size() +Class01 : int chimp +Class01 : int gorilla +class Class10 { + int id + size() +} +
+
+ stateDiagram [*] --> S1 state "Some long name" as S1 - -
+
+
+ classDiagram + Animal "1" <|-- Duck + Animal <|-- Fish + Animal <--o Zebra + Animal : +int age + Animal : +String gender + Animal: +isMammal() + Animal: +mate() + class Duck{ + +String beakColor + +swim() + +quack() + } + class Fish{ + -int sizeInFeet + -canEat() + } + class Zebra{ + +bool is_wild + +run() + } +
diff --git a/cypress/platform/render-after-error.html b/cypress/platform/render-after-error.html index 7b88ba5ba..e6b97aea2 100644 --- a/cypress/platform/render-after-error.html +++ b/cypress/platform/render-after-error.html @@ -12,9 +12,11 @@ - + ``` **Doing so will command the mermaid parser to look for the `
` tags with `class="mermaid"`. From these tags mermaid will try to read the diagram/chart definitions and render them into svg charts.** @@ -267,6 +268,15 @@ npm publish The above command generates files into the `dist` folder and publishes them to npmjs.org. +## Security and safe diagrams + +For public sites, it can be precarious to retrieve text from users on the internet, storing that content for presentation in a browser at a later stage. The reason is that the user content can contain embedded malicious scripts that will run when the data is presented. For Mermaid this is a risk, specially as mermaid diagrams contain many characters that are used in html which makes the standard sanitation unusable as it also breaks the diagrams. We still make an effort to sanitise the incoming code and keep refining the process but it is hard to guarantee that there are no loop holes. + +As an extra level of security for sites with external users we are happy to introduce a new security level in which the diagram is rendered in a sandboxed iframe preventing javascript in the code from being executed. This is a great step forward for better security. + +*Unfortunately you can not have a cake and eat it at the same time which in this case means that some of the interactive functionality gets blocked along with the possible malicious code.* + + ## Credits Many thanks to the [d3](http://d3js.org/) and [dagre-d3](https://github.com/cpettitt/dagre-d3) projects for providing the graphical layout and drawing libraries! diff --git a/docs/Setup.md b/docs/Setup.md index 85a4fdf3c..4ac10f130 100644 --- a/docs/Setup.md +++ b/docs/Setup.md @@ -1057,6 +1057,10 @@ Returns **any** - `conf` **any** +## reinitialize + +To be removed + ## initialize ### Parameters diff --git a/docs/diagrams-and-syntax-and-examples/flowchart.md b/docs/diagrams-and-syntax-and-examples/flowchart.md index 843da5ab1..d1b6b5cbe 100644 --- a/docs/diagrams-and-syntax-and-examples/flowchart.md +++ b/docs/diagrams-and-syntax-and-examples/flowchart.md @@ -255,7 +255,7 @@ graph TB B --> D ``` -### Beta: New arrow types +### New arrow types When using flowchart instead of graph there are new types of arrows supported as per below: @@ -266,7 +266,7 @@ flowchart LR ``` -### Beta: Multi directional arrows +### Multi directional arrows When using flowchart instead of graph there is the possibility to use multidirectional arrows. @@ -377,9 +377,9 @@ graph TB end ``` -## Beta: flowcharts +## flowcharts -With the graphtype flowcharts it is also possible to set edges to and from subgraphs as in the graph below. +With the graphtype flowchart it is also possible to set edges to and from subgraphs as in the graph below. ```mermaid-example flowchart TB diff --git a/docs/flowchart.md b/docs/flowchart.md index 9ac434452..795d27579 100644 --- a/docs/flowchart.md +++ b/docs/flowchart.md @@ -281,11 +281,11 @@ to node _E_, so that it spans two more ranks than regular links: ```mermaid-example flowchart TD - A[Start] --> B{Is it?}; - B -->|Yes| C[OK]; - C --> D[Rethink]; - D --> B; - B ---->|No| E[End]; + A[Start] --> B{Is it?} + B -->|Yes| C[OK] + C --> D[Rethink] + D --> B + B ---->|No| E[End] ``` > **Note** Links may still be made longer than the requested number of ranks @@ -297,11 +297,11 @@ the previous one: ```mermaid-example flowchart TD - A[Start] --> B{Is it?}; - B -- Yes --> C[OK]; - C --> D[Rethink]; - D --> B; - B -- No ----> E[End]; + A[Start] --> B{Is it?} + B -- Yes --> C[OK] + C --> D[Rethink] + D --> B + B -- No ----> E[End] ``` For dotted or thick links, the characters to add are equals signs or dots, @@ -370,9 +370,9 @@ flowchart TB end ``` -## Beta: flowcharts +## flowcharts -With the graphtype flowcharts it is also possible to set edges to and from subgraphs as in the flowchart below. +With the graphtype flowchart it is also possible to set edges to and from subgraphs as in the flowchart below. ```mermaid-example flowchart TB @@ -437,10 +437,10 @@ Examples of tooltip usage below: The tooltip text is surrounded in double quotes. The styles of the tooltip are set by the class `.mermaidTooltip`. ```mermaid-example -flowchart LR; - A-->B; - B-->C; - C-->D; +flowchart LR + A-->B + B-->C + C-->D click A callback "Tooltip for a callback" click B "http://www.github.com" "This is a tooltip for a link" click A call callback() "Tooltip for a callback" @@ -453,11 +453,11 @@ flowchart LR; Links are opened in the same browser tab/window by default. It is possible to change this by adding a link target to the click definition (`_self`, `_blank`, `_parent` and `_top` are supported): ```mermaid-example -flowchart LR; - A-->B; - B-->C; - C-->D; - D-->E; +flowchart LR + A-->B + B-->C + C-->D + D-->E click A "http://www.github.com" _blank click B "http://www.github.com" "Open this in a new tab" _blank click C href "http://www.github.com" _blank @@ -468,10 +468,10 @@ Beginners tip, a full example using interactive links in a html context: ```html
- flowchart LR; - A-->B; - B-->C; - C-->D; + flowchart LR + A-->B + B-->C + C-->D click A callback "Tooltip" click B "http://www.github.com" "This is a link" click C call callback() "Tooltip" @@ -587,9 +587,9 @@ below: ```mermaid-example flowchart LR; - A-->B[AAABBB]; - B-->D; - class A cssClass; + A-->B[AAABBB] + B-->D + class A cssClass ``` @@ -613,7 +613,7 @@ flowchart TD B["fa:fa-twitter for peace"] B-->C[fa:fa-ban forbidden] B-->D(fa:fa-spinner); - B-->E(A fa:fa-camera-retro perhaps?); + B-->E(A fa:fa-camera-retro perhaps?) ``` diff --git a/docs/usage.md b/docs/usage.md index 3b9138d8f..3a6ff2697 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -50,7 +50,8 @@ The easiest way to integrate mermaid on a web page requires three elements: 2. The `mermaidAPI` call, in a separate `script` tag. Example: ```html - + ``` 3. A graph definition, inside `
` tags labeled `class=mermaid`. Example: @@ -82,7 +83,8 @@ locate the graph definitions inside the `div` tags with `class="mermaid"` and re B-->D(fa:fa-spinner);
- + ``` @@ -105,17 +107,19 @@ Mermaid can load multiple diagrams, in the same page. | Parameter | Description | Type | Required | Values | | ------------- | --------------------------------- | ------ | -------- | ------------------------- | -| securityLevel | Level of trust for parsed diagram | String | Required | Strict, Loose, antiscript | +| securityLevel | Level of trust for parsed diagram | String | Required | Strict, Loose, antiscript , sandbox| Values: - **strict**: (**default**) tags in text are encoded, click functionality is disabled - **loose**: tags in text are allowed, click functionality is enabled - **antiscript**: html tags in text are allowed, (only script element is removed), click functionality is enabled +- **sandbox**: With this security level all rendering takes place in a sandboxed iframe. This prevent any javascript running in the context. This may hinder interactive functionality of the diagram like scripts, popups in sequence diagram or links to other tabs/targets etc. ```note This changes the default behaviour of mermaid so that after upgrade to 8.2,unless the `securityLevel` is not changed, tags in flowcharts are encoded as tags and clicking is disabled. +**sandbox** security level is still in the beta version. ``` **If you are taking responsibility for the diagram source security you can set the `securityLevel` to a value of your choosing . This allows clicks and tags are allowed.** @@ -203,20 +207,10 @@ The example below show an outline of how this could be used. The example just lo ``` @@ -345,13 +339,7 @@ on what kind of integration you use. ```html ``` diff --git a/package.json b/package.json index 6662aae84..25d562d4f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mermaid", - "version": "8.13.10", + "version": "8.14.0", "description": "Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.", "main": "dist/mermaid.core.js", "module": "dist/mermaid.esm.min.mjs", diff --git a/src/dagre-wrapper/nodes.js b/src/dagre-wrapper/nodes.js index 2b164ea92..cb25f7fc0 100644 --- a/src/dagre-wrapper/nodes.js +++ b/src/dagre-wrapper/nodes.js @@ -6,7 +6,9 @@ import intersect from './intersect/index.js'; import createLabel from './createLabel'; import note from './shapes/note'; import { parseMember } from '../diagrams/class/svgDraw'; -import { evaluate } from '../diagrams/common/common'; +import { evaluate, sanitizeText as sanitize } from '../diagrams/common/common'; + +const sanitizeText = (txt) => sanitize(txt, getConfig()); const question = (parent, node) => { const { shapeSvg, bbox } = labelHelper(parent, node, undefined, true); @@ -967,10 +969,13 @@ export const insertNode = (elem, node, dir) => { // Add link when appropriate if (node.link) { - newEl = elem - .insert('svg:a') - .attr('xlink:href', node.link) - .attr('target', node.linkTarget || '_blank'); + let target; + if (getConfig().securityLevel === 'sandbox') { + target = '_top'; + } else if (node.linkTarget) { + target = node.linkTarget || '_blank'; + } + newEl = elem.insert('svg:a').attr('xlink:href', node.link).attr('target', target); el = shapes[node.shape](newEl, node, dir); } else { el = shapes[node.shape](elem, node, dir); diff --git a/src/diagrams/class/classDb.js b/src/diagrams/class/classDb.js index 163ac10dd..abfb58a17 100644 --- a/src/diagrams/class/classDb.js +++ b/src/diagrams/class/classDb.js @@ -143,6 +143,7 @@ export const addMember = function (className, member) { if (memberString.startsWith('<<') && memberString.endsWith('>>')) { // Remove leading and trailing brackets + // theClass.annotations.push(memberString.substring(2, memberString.length - 2)); theClass.annotations.push(sanitizeText(memberString.substring(2, memberString.length - 2))); } else if (memberString.indexOf(')') > 0) { theClass.methods.push(sanitizeText(memberString)); @@ -212,8 +213,10 @@ export const setLink = function (ids, linkStr, target) { if (_id[0].match(/\d/)) id = MERMAID_DOM_ID_PREFIX + id; if (typeof classes[id] !== 'undefined') { classes[id].link = utils.formatUrl(linkStr, config); - if (typeof target === 'string') { - classes[id].linkTarget = target; + if (config.securityLevel === 'sandbox') { + classes[id].linkTarget = '_top'; + } else if (typeof target === 'string') { + classes[id].linkTarget = sanitizeText(target); } else { classes[id].linkTarget = '_blank'; } diff --git a/src/diagrams/class/classRenderer-v2.js b/src/diagrams/class/classRenderer-v2.js index 0f552d2ec..eda96e5a9 100644 --- a/src/diagrams/class/classRenderer-v2.js +++ b/src/diagrams/class/classRenderer-v2.js @@ -269,101 +269,8 @@ export const setConf = function (cnf) { * @param {string} text * @param {string} id */ -export const drawOld = function (text, id) { - idCache = {}; - parser.yy.clear(); - parser.parse(text); - - log.info('Rendering diagram ' + text); - - // Fetch the default direction, use TD if none was found - const diagram = select(`[id='${id}']`); - // insertMarkers(diagram); - - // Layout graph, Create a new directed graph - const g = new graphlib.Graph({ - multigraph: true, - }); - - // Set an object for the graph label - g.setGraph({ - isMultiGraph: true, - }); - - // Default to assigning a new object as a label for each new edge. - g.setDefaultEdgeLabel(function () { - return {}; - }); - - const classes = classDb.getClasses(); - log.info('classes:'); - log.info(classes); - const keys = Object.keys(classes); - for (let i = 0; i < keys.length; i++) { - const classDef = classes[keys[i]]; - const node = svgDraw.drawClass(diagram, classDef, conf); - idCache[node.id] = node; - - // Add nodes to the graph. The first argument is the node id. The second is - // metadata about the node. In this case we're going to add labels to each of - // our nodes. - g.setNode(node.id, node); - - log.info('Org height: ' + node.height); - } - - const relations = classDb.getRelations(); - log.info('relations:', relations); - relations.forEach(function (relation) { - log.info( - 'tjoho' + getGraphId(relation.id1) + getGraphId(relation.id2) + JSON.stringify(relation) - ); - g.setEdge( - getGraphId(relation.id1), - getGraphId(relation.id2), - { - relation: relation, - }, - relation.title || 'DEFAULT' - ); - }); - - dagre.layout(g); - g.nodes().forEach(function (v) { - if (typeof v !== 'undefined' && typeof g.node(v) !== 'undefined') { - log.debug('Node ' + v + ': ' + JSON.stringify(g.node(v))); - select('#' + lookUpDomId(v)).attr( - 'transform', - 'translate(' + - (g.node(v).x - g.node(v).width / 2) + - ',' + - (g.node(v).y - g.node(v).height / 2) + - ' )' - ); - } - }); - - g.edges().forEach(function (e) { - if (typeof e !== 'undefined' && typeof g.edge(e) !== 'undefined') { - log.debug('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(g.edge(e))); - svgDraw.drawEdge(diagram, g.edge(e), g.edge(e).relation, conf); - } - }); - - const svgBounds = diagram.node().getBBox(); - const width = svgBounds.width + padding * 2; - const height = svgBounds.height + padding * 2; - - configureSvgSize(diagram, height, width, conf.useMaxWidth); - - // Ensure the viewBox includes the whole svgBounds area with extra space for padding - const vBox = `${svgBounds.x - padding} ${svgBounds.y - padding} ${width} ${height}`; - log.debug(`viewBox ${vBox}`); - diagram.attr('viewBox', vBox); -}; - export const draw = function (text, id) { - log.info('Drawing class'); + log.info('Drawing class - ', id); classDb.clear(); // const parser = classDb.parser; // parser.yy = classDb; @@ -379,6 +286,7 @@ export const draw = function (text, id) { //let dir = 'TD'; const conf = getConfig().flowchart; + const securityLevel = getConfig().securityLevel; log.info('config:', conf); const nodeSpacing = conf.nodeSpacing || 50; const rankSpacing = conf.rankSpacing || 50; @@ -430,11 +338,19 @@ export const draw = function (text, id) { // flowChartShapes.addToRenderV2(addShape); // Set up an SVG group so that we can translate the final graph. - const svg = select(`[id="${id}"]`); + let sandboxElement; + if (securityLevel === 'sandbox') { + sandboxElement = select('#i' + id); + } + const root = + securityLevel === 'sandbox' + ? select(sandboxElement.nodes()[0].contentDocument.body) + : select('body'); + const svg = root.select(`[id="${id}"]`); svg.attr('xmlns:xlink', 'http://www.w3.org/1999/xlink'); // Run the renderer. This is what draws the final graph. - const element = select('#' + id + ' g'); + const element = root.select('#' + id + ' g'); render(element, g, ['aggregation', 'extension', 'composition', 'dependency'], 'classDiagram', id); // element.selectAll('g.node').attr('title', function() { @@ -462,14 +378,15 @@ export const draw = function (text, id) { // Add label rects for non html labels if (!conf.htmlLabels) { - const labels = document.querySelectorAll('[id="' + id + '"] .edgeLabel .label'); + const doc = securityLevel === 'sandbox' ? sandboxElement.nodes()[0].contentDocument : document; + const labels = doc.querySelectorAll('[id="' + id + '"] .edgeLabel .label'); for (let k = 0; k < labels.length; k++) { const label = labels[k]; // Get dimensions of label const dim = label.getBBox(); - const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect'); + const rect = doc.createElementNS('http://www.w3.org/2000/svg', 'rect'); rect.setAttribute('rx', 0); rect.setAttribute('ry', 0); rect.setAttribute('width', dim.width); diff --git a/src/diagrams/class/classRenderer.js b/src/diagrams/class/classRenderer.js index ca3fd1d94..b2a57d032 100644 --- a/src/diagrams/class/classRenderer.js +++ b/src/diagrams/class/classRenderer.js @@ -6,6 +6,7 @@ import classDb, { lookUpDomId } from './classDb'; import { parser } from './parser/classDiagram'; import svgDraw from './svgDraw'; import { configureSvgSize } from '../../utils'; +import { getConfig } from '../../config'; parser.yy = classDb; @@ -165,8 +166,20 @@ export const draw = function (text, id) { log.info('Rendering diagram ' + text); + const securityLevel = getConfig().securityLevel; + // Handle root and ocument for when rendering in sanbox mode + let sandboxElement; + if (securityLevel === 'sandbox') { + sandboxElement = select('#i' + id); + } + const root = + securityLevel === 'sandbox' + ? select(sandboxElement.nodes()[0].contentDocument.body) + : select('body'); + const doc = securityLevel === 'sandbox' ? sandboxElement.nodes()[0].contentDocument : document; + // Fetch the default direction, use TD if none was found - const diagram = select(`[id='${id}']`); + const diagram = root.select(`[id='${id}']`); diagram.attr('xmlns:xlink', 'http://www.w3.org/1999/xlink'); insertMarkers(diagram); @@ -220,14 +233,16 @@ export const draw = function (text, id) { g.nodes().forEach(function (v) { if (typeof v !== 'undefined' && typeof g.node(v) !== 'undefined') { log.debug('Node ' + v + ': ' + JSON.stringify(g.node(v))); - select('#' + lookUpDomId(v)).attr( - 'transform', - 'translate(' + - (g.node(v).x - g.node(v).width / 2) + - ',' + - (g.node(v).y - g.node(v).height / 2) + - ' )' - ); + root + .select('#' + lookUpDomId(v)) + .attr( + 'transform', + 'translate(' + + (g.node(v).x - g.node(v).width / 2) + + ',' + + (g.node(v).y - g.node(v).height / 2) + + ' )' + ); } }); diff --git a/src/diagrams/er/erRenderer.js b/src/diagrams/er/erRenderer.js index a1e027ccc..d2e2cc0d9 100644 --- a/src/diagrams/er/erRenderer.js +++ b/src/diagrams/er/erRenderer.js @@ -545,6 +545,17 @@ export const draw = function (text, id) { erDb.clear(); const parser = erParser.parser; parser.yy = erDb; + const securityLevel = getConfig().securityLevel; + // Handle root and ocument for when rendering in sanbox mode + let sandboxElement; + if (securityLevel === 'sandbox') { + sandboxElement = select('#i' + id); + } + const root = + securityLevel === 'sandbox' + ? select(sandboxElement.nodes()[0].contentDocument.body) + : select('body'); + const doc = securityLevel === 'sandbox' ? sandboxElement.nodes()[0].contentDocument : document; // Parse the text to populate erDb try { @@ -554,7 +565,7 @@ export const draw = function (text, id) { } // Get a reference to the svg node that contains the text - const svg = select(`[id='${id}']`); + const svg = root.select(`[id='${id}']`); // Add cardinality marker definitions to the svg erMarkers.insertMarkers(svg, conf); diff --git a/src/diagrams/flowchart/flowRenderer-v2.js b/src/diagrams/flowchart/flowRenderer-v2.js index b046d9c67..21300992c 100644 --- a/src/diagrams/flowchart/flowRenderer-v2.js +++ b/src/diagrams/flowchart/flowRenderer-v2.js @@ -25,9 +25,11 @@ export const setConf = function (cnf) { * @param vert Object containing the vertices. * @param g The graph that is to be drawn. * @param svgId + * @param root + * @param doc */ -export const addVertices = function (vert, g, svgId) { - const svg = select(`[id="${svgId}"]`); +export const addVertices = function (vert, g, svgId, root, doc) { + const svg = root.select(`[id="${svgId}"]`); const keys = Object.keys(vert); // Iterate through each item in the vertex object (containing all the vertices found) in the graph definition @@ -62,13 +64,13 @@ export const addVertices = function (vert, g, svgId) { vertexNode = addHtmlLabel(svg, node).node(); vertexNode.parentNode.removeChild(vertexNode); } else { - const svgLabel = document.createElementNS('http://www.w3.org/2000/svg', 'text'); + const svgLabel = doc.createElementNS('http://www.w3.org/2000/svg', 'text'); svgLabel.setAttribute('style', styles.labelStyle.replace('color:', 'fill:')); const rows = vertexText.split(common.lineBreakRegex); for (let j = 0; j < rows.length; j++) { - const tspan = document.createElementNS('http://www.w3.org/2000/svg', 'tspan'); + const tspan = doc.createElementNS('http://www.w3.org/2000/svg', 'tspan'); tspan.setAttributeNS('http://www.w3.org/XML/1998/namespace', 'xml:space', 'preserve'); tspan.setAttribute('dy', '1em'); tspan.setAttribute('x', '1'); @@ -374,6 +376,18 @@ export const draw = function (text, id) { const nodeSpacing = conf.nodeSpacing || 50; const rankSpacing = conf.rankSpacing || 50; + const securityLevel = getConfig().securityLevel; + // Handle root and ocument for when rendering in sanbox mode + let sandboxElement; + if (securityLevel === 'sandbox') { + sandboxElement = select('#i' + id); + } + const root = + securityLevel === 'sandbox' + ? select(sandboxElement.nodes()[0].contentDocument.body) + : select('body'); + const doc = securityLevel === 'sandbox' ? sandboxElement.nodes()[0].contentDocument : document; + // Create the input mermaid.graph const g = new graphlib.Graph({ multigraph: true, @@ -417,18 +431,18 @@ export const draw = function (text, id) { g.setParent(subG.nodes[j], subG.id); } } - addVertices(vert, g, id); + addVertices(vert, g, id, root, doc); addEdges(edges, g); // Add custom shapes // flowChartShapes.addToRenderV2(addShape); // Set up an SVG group so that we can translate the final graph. - const svg = select(`[id="${id}"]`); + const svg = root.select(`[id="${id}"]`); svg.attr('xmlns:xlink', 'http://www.w3.org/1999/xlink'); // Run the renderer. This is what draws the final graph. - const element = select('#' + id + ' g'); + const element = root.select('#' + id + ' g'); render(element, g, ['point', 'circle', 'cross'], 'flowchart', id); const padding = conf.diagramPadding; @@ -452,14 +466,14 @@ export const draw = function (text, id) { // Add label rects for non html labels if (!conf.htmlLabels) { - const labels = document.querySelectorAll('[id="' + id + '"] .edgeLabel .label'); + const labels = doc.querySelectorAll('[id="' + id + '"] .edgeLabel .label'); for (let k = 0; k < labels.length; k++) { const label = labels[k]; // Get dimensions of label const dim = label.getBBox(); - const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect'); + const rect = doc.createElementNS('http://www.w3.org/2000/svg', 'rect'); rect.setAttribute('rx', 0); rect.setAttribute('ry', 0); rect.setAttribute('width', dim.width); @@ -478,11 +492,13 @@ export const draw = function (text, id) { if (vertex.link) { const node = select('#' + id + ' [id="' + key + '"]'); if (node) { - const link = document.createElementNS('http://www.w3.org/2000/svg', 'a'); + const link = doc.createElementNS('http://www.w3.org/2000/svg', 'a'); link.setAttributeNS('http://www.w3.org/2000/svg', 'class', vertex.classes.join(' ')); link.setAttributeNS('http://www.w3.org/2000/svg', 'href', vertex.link); link.setAttributeNS('http://www.w3.org/2000/svg', 'rel', 'noopener'); - if (vertex.linkTarget) { + if (securityLevel === 'sandbox') { + link.setAttributeNS('http://www.w3.org/2000/svg', 'target', '_top'); + } else if (vertex.linkTarget) { link.setAttributeNS('http://www.w3.org/2000/svg', 'target', vertex.linkTarget); } diff --git a/src/diagrams/flowchart/flowRenderer.js b/src/diagrams/flowchart/flowRenderer.js index c65f4397a..7cc07e242 100644 --- a/src/diagrams/flowchart/flowRenderer.js +++ b/src/diagrams/flowchart/flowRenderer.js @@ -26,9 +26,15 @@ export const setConf = function (cnf) { * @param vert Object containing the vertices. * @param g The graph that is to be drawn. * @param svgId + * @param root + * @param doc + * @param _doc */ -export const addVertices = function (vert, g, svgId) { - const svg = select(`[id="${svgId}"]`); +export const addVertices = function (vert, g, svgId, root, _doc) { + const securityLevel = getConfig().securityLevel; + + const svg = !root ? select(`[id="${svgId}"]`) : root.select(`[id="${svgId}"]`); + const doc = !_doc ? document : _doc; const keys = Object.keys(vert); // Iterate through each item in the vertex object (containing all the vertices found) in the graph definition @@ -63,13 +69,13 @@ export const addVertices = function (vert, g, svgId) { vertexNode = addHtmlLabel(svg, node).node(); vertexNode.parentNode.removeChild(vertexNode); } else { - const svgLabel = document.createElementNS('http://www.w3.org/2000/svg', 'text'); + const svgLabel = doc.createElementNS('http://www.w3.org/2000/svg', 'text'); svgLabel.setAttribute('style', styles.labelStyle.replace('color:', 'fill:')); const rows = vertexText.split(common.lineBreakRegex); for (let j = 0; j < rows.length; j++) { - const tspan = document.createElementNS('http://www.w3.org/2000/svg', 'tspan'); + const tspan = doc.createElementNS('http://www.w3.org/2000/svg', 'tspan'); tspan.setAttributeNS('http://www.w3.org/XML/1998/namespace', 'xml:space', 'preserve'); tspan.setAttribute('dy', '1em'); tspan.setAttribute('x', '1'); @@ -293,6 +299,17 @@ export const draw = function (text, id) { const parser = flow.parser; parser.yy = flowDb; + const securityLevel = getConfig().securityLevel; + let sandboxElement; + if (securityLevel === 'sandbox') { + sandboxElement = select('#i' + id); + } + const root = + securityLevel === 'sandbox' + ? select(sandboxElement.nodes()[0].contentDocument.body) + : select('body'); + const doc = securityLevel === 'sandbox' ? sandboxElement.nodes()[0].contentDocument : document; + // Parse the graph definition // try { parser.parse(text); @@ -355,7 +372,7 @@ export const draw = function (text, id) { g.setParent(flowDb.lookUpDomId(subG.nodes[j]), flowDb.lookUpDomId(subG.id)); } } - addVertices(vert, g, id); + addVertices(vert, g, id, root, doc); addEdges(edges, g); // Create the renderer @@ -404,13 +421,13 @@ export const draw = function (text, id) { }; // Set up an SVG group so that we can translate the final graph. - const svg = select(`[id="${id}"]`); + const svg = root.select(`[id="${id}"]`); svg.attr('xmlns:xlink', 'http://www.w3.org/1999/xlink'); log.warn(g); // Run the renderer. This is what draws the final graph. - const element = select('#' + id + ' g'); + const element = root.select('#' + id + ' g'); render(element, g); element.selectAll('g.node').attr('title', function () { @@ -436,10 +453,10 @@ export const draw = function (text, id) { for (i = 0; i < subGraphs.length; i++) { subG = subGraphs[i]; if (subG.title !== 'undefined') { - const clusterRects = document.querySelectorAll( + const clusterRects = doc.querySelectorAll( '#' + id + ' [id="' + flowDb.lookUpDomId(subG.id) + '"] rect' ); - const clusterEl = document.querySelectorAll( + const clusterEl = doc.querySelectorAll( '#' + id + ' [id="' + flowDb.lookUpDomId(subG.id) + '"]' ); @@ -459,14 +476,14 @@ export const draw = function (text, id) { // Add label rects for non html labels if (!evaluate(conf.htmlLabels) || true) { // eslint-disable-line - const labels = document.querySelectorAll('[id="' + id + '"] .edgeLabel .label'); + const labels = doc.querySelectorAll('[id="' + id + '"] .edgeLabel .label'); for (let k = 0; k < labels.length; k++) { const label = labels[k]; // Get dimensions of label const dim = label.getBBox(); - const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect'); + const rect = doc.createElementNS('http://www.w3.org/2000/svg', 'rect'); rect.setAttribute('rx', 0); rect.setAttribute('ry', 0); rect.setAttribute('width', dim.width); @@ -483,13 +500,15 @@ export const draw = function (text, id) { const vertex = vert[key]; if (vertex.link) { - const node = select('#' + id + ' [id="' + flowDb.lookUpDomId(key) + '"]'); + const node = root.select('#' + id + ' [id="' + flowDb.lookUpDomId(key) + '"]'); if (node) { - const link = document.createElementNS('http://www.w3.org/2000/svg', 'a'); + const link = doc.createElementNS('http://www.w3.org/2000/svg', 'a'); link.setAttributeNS('http://www.w3.org/2000/svg', 'class', vertex.classes.join(' ')); link.setAttributeNS('http://www.w3.org/2000/svg', 'href', vertex.link); link.setAttributeNS('http://www.w3.org/2000/svg', 'rel', 'noopener'); - if (vertex.linkTarget) { + if (securityLevel === 'sandbox') { + link.setAttributeNS('http://www.w3.org/2000/svg', 'target', '_top'); + } else if (vertex.linkTarget) { link.setAttributeNS('http://www.w3.org/2000/svg', 'target', vertex.linkTarget); } diff --git a/src/diagrams/gantt/ganttDb.js b/src/diagrams/gantt/ganttDb.js index 1c06769c8..88c591520 100644 --- a/src/diagrams/gantt/ganttDb.js +++ b/src/diagrams/gantt/ganttDb.js @@ -10,6 +10,7 @@ let axisFormat = ''; let todayMarker = ''; let includes = []; let excludes = []; +let links = {}; let title = ''; let sections = []; let tasks = []; @@ -44,6 +45,7 @@ export const clear = function () { inclusiveEndDates = false; topAxis = false; lastOrder = 0; + links = {}; }; export const setAxisFormat = function (txt) { @@ -101,6 +103,10 @@ export const getExcludes = function () { return excludes; }; +export const getLinks = function () { + return links; +}; + export const setTitle = function (txt) { title = txt; }; @@ -505,6 +511,7 @@ export const setLink = function (ids, _linkStr) { pushFun(id, () => { window.open(linkStr, '_self'); }); + links[id] = linkStr; } }); setClass(ids, 'clickable'); @@ -642,6 +649,7 @@ export default { getExcludes, setClickEvent, setLink, + getLinks, bindFunctions, durationToDate, isInvalidDate, diff --git a/src/diagrams/gantt/ganttRenderer.js b/src/diagrams/gantt/ganttRenderer.js index c535a7285..899c61e02 100644 --- a/src/diagrams/gantt/ganttRenderer.js +++ b/src/diagrams/gantt/ganttRenderer.js @@ -29,7 +29,19 @@ export const draw = function (text, id) { parser.yy.clear(); parser.parse(text); - const elem = document.getElementById(id); + const securityLevel = getConfig().securityLevel; + // Handle root and ocument for when rendering in sanbox mode + let sandboxElement; + if (securityLevel === 'sandbox') { + sandboxElement = select('#i' + id); + } + const root = + securityLevel === 'sandbox' + ? select(sandboxElement.nodes()[0].contentDocument.body) + : select('body'); + const doc = securityLevel === 'sandbox' ? sandboxElement.nodes()[0].contentDocument : document; + + const elem = doc.getElementById(id); w = elem.parentElement.offsetWidth; if (typeof w === 'undefined') { @@ -47,7 +59,7 @@ export const draw = function (text, id) { // Set viewBox elem.setAttribute('viewBox', '0 0 ' + w + ' ' + h); - const svg = select(`[id="${id}"]`); + const svg = root.select(`[id="${id}"]`); // Set timescale const timeScale = scaleTime() @@ -173,6 +185,10 @@ export const draw = function (text, id) { // Draw the rects representing the tasks const rectangles = svg.append('g').selectAll('rect').data(theArray).enter(); + const links = ganttDb.getLinks(); + + // Render the tasks with links + // Render the other tasks rectangles .append('rect') .attr('id', function (d) { @@ -369,6 +385,32 @@ export const draw = function (text, id) { return classStr + ' taskText taskText' + secNum + ' ' + taskType + ' width-' + textWidth; } }); + + const securityLevel = getConfig().securityLevel; + + // Wrap the tasks in an a tag for working links without javascript + if (securityLevel === 'sandbox') { + let sandboxElement; + sandboxElement = select('#i' + id); + const root = select(sandboxElement.nodes()[0].contentDocument.body); + const doc = sandboxElement.nodes()[0].contentDocument; + + rectangles + .filter(function (d) { + return typeof links[d.id] !== 'undefined'; + }) + .each(function (o) { + var taskRect = doc.querySelector('#' + o.id); + var taskText = doc.querySelector('#' + o.id + '-text'); + const oldParent = taskRect.parentNode; + var Link = doc.createElement('a'); + Link.setAttribute('xlink:href', links[o.id]); + Link.setAttribute('target', '_top'); + oldParent.appendChild(Link); + Link.appendChild(taskRect); + Link.appendChild(taskText); + }); + } } /** * @param theGap @@ -505,11 +547,11 @@ export const draw = function (text, id) { const rows = d[0].split(common.lineBreakRegex); const dy = -(rows.length - 1) / 2; - const svgLabel = document.createElementNS('http://www.w3.org/2000/svg', 'text'); + const svgLabel = doc.createElementNS('http://www.w3.org/2000/svg', 'text'); svgLabel.setAttribute('dy', dy + 'em'); for (let j = 0; j < rows.length; j++) { - const tspan = document.createElementNS('http://www.w3.org/2000/svg', 'tspan'); + const tspan = doc.createElementNS('http://www.w3.org/2000/svg', 'tspan'); tspan.setAttribute('alignment-baseline', 'central'); tspan.setAttribute('x', '10'); if (j > 0) tspan.setAttribute('dy', '1em'); diff --git a/src/diagrams/git/gitGraphRenderer.js b/src/diagrams/git/gitGraphRenderer.js index e9da5faa1..49d04b759 100644 --- a/src/diagrams/git/gitGraphRenderer.js +++ b/src/diagrams/git/gitGraphRenderer.js @@ -4,6 +4,7 @@ import db from './gitGraphAst'; import gitGraphParser from './parser/gitGraph'; import { log } from '../../logger'; import { interpolateToCurve } from '../../utils'; +import { getConfig } from '../../config'; let allCommitsDict = {}; let branchNum; @@ -338,6 +339,18 @@ export const draw = function (txt, id, ver) { parser.yy = db; parser.yy.clear(); + const securityLevel = getConfig().securityLevel; + // Handle root and ocument for when rendering in sanbox mode + let sandboxElement; + if (securityLevel === 'sandbox') { + sandboxElement = select('#i' + id); + } + const root = + securityLevel === 'sandbox' + ? select(sandboxElement.nodes()[0].contentDocument.body) + : select('body'); + const doc = securityLevel === 'sandbox' ? sandboxElement.nodes()[0].contentDocument : document; + log.debug('in gitgraph renderer', txt + '\n', 'id:', id, ver); // Parse the graph definition parser.parse(txt + '\n'); @@ -352,7 +365,7 @@ export const draw = function (txt, id, ver) { config.nodeLabel.width = '100%'; config.nodeLabel.y = -1 * 2 * config.nodeRadius; } - const svg = select(`[id="${id}"]`); + const svg = root.select(`[id="${id}"]`); svgCreateDefs(svg); branchNum = 1; for (let branch in branches) { diff --git a/src/diagrams/info/infoRenderer.js b/src/diagrams/info/infoRenderer.js index 12d3217ee..b01931a22 100644 --- a/src/diagrams/info/infoRenderer.js +++ b/src/diagrams/info/infoRenderer.js @@ -3,6 +3,7 @@ import { select } from 'd3'; import db from './infoDb'; import infoParser from './parser/info'; import { log } from '../../logger'; +import { getConfig } from '../../config'; const conf = {}; export const setConf = function (cnf) { @@ -25,11 +26,24 @@ export const draw = (text, id, version) => { const parser = infoParser.parser; parser.yy = db; log.debug('Renering info diagram\n' + text); + + const securityLevel = getConfig().securityLevel; + // Handle root and ocument for when rendering in sanbox mode + let sandboxElement; + if (securityLevel === 'sandbox') { + sandboxElement = select('#i' + id); + } + const root = + securityLevel === 'sandbox' + ? select(sandboxElement.nodes()[0].contentDocument.body) + : select('body'); + const doc = securityLevel === 'sandbox' ? sandboxElement.nodes()[0].contentDocument : document; + // Parse the graph definition parser.parse(text); log.debug('Parsed info diagram'); // Fetch the default direction, use TD if none was found - const svg = select('#' + id); + const svg = root.select('#' + id); const g = svg.append('g'); diff --git a/src/diagrams/pie/pieRenderer.js b/src/diagrams/pie/pieRenderer.js index c405a5c6d..f8cf77380 100644 --- a/src/diagrams/pie/pieRenderer.js +++ b/src/diagrams/pie/pieRenderer.js @@ -22,11 +22,24 @@ export const draw = (txt, id) => { const parser = pieParser.parser; parser.yy = pieData; log.debug('Rendering info diagram\n' + txt); + + const securityLevel = configApi.getConfig().securityLevel; + // Handle root and ocument for when rendering in sanbox mode + let sandboxElement; + if (securityLevel === 'sandbox') { + sandboxElement = select('#i' + id); + } + const root = + securityLevel === 'sandbox' + ? select(sandboxElement.nodes()[0].contentDocument.body) + : select('body'); + const doc = securityLevel === 'sandbox' ? sandboxElement.nodes()[0].contentDocument : document; + // Parse the Pie Chart definition parser.yy.clear(); parser.parse(txt); log.debug('Parsed info diagram'); - const elem = document.getElementById(id); + const elem = doc.getElementById(id); width = elem.parentElement.offsetWidth; if (typeof width === 'undefined') { @@ -40,7 +53,7 @@ export const draw = (txt, id) => { width = conf.pie.useWidth; } - const diagram = select('#' + id); + const diagram = root.select('#' + id); configureSvgSize(diagram, height, width, conf.pie.useMaxWidth); // Set viewBox diff --git a/src/diagrams/requirement/requirementRenderer.js b/src/diagrams/requirement/requirementRenderer.js index 5ba75d464..0814b0088 100644 --- a/src/diagrams/requirement/requirementRenderer.js +++ b/src/diagrams/requirement/requirementRenderer.js @@ -8,6 +8,7 @@ import common from '../common/common'; import { parser } from './parser/requirementDiagram'; import requirementDb from './requirementDb'; import markers from './requirementMarkers'; +import { getConfig } from '../../config'; const conf = {}; let relCnt = 0; @@ -321,7 +322,19 @@ export const draw = (text, id) => { parser.yy.clear(); parser.parse(text); - const svg = select(`[id='${id}']`); + const securityLevel = getConfig().securityLevel; + // Handle root and ocument for when rendering in sanbox mode + let sandboxElement; + if (securityLevel === 'sandbox') { + sandboxElement = select('#i' + id); + } + const root = + securityLevel === 'sandbox' + ? select(sandboxElement.nodes()[0].contentDocument.body) + : select('body'); + const doc = securityLevel === 'sandbox' ? sandboxElement.nodes()[0].contentDocument : document; + + const svg = root.select(`[id='${id}']`); markers.insertLineEndings(svg, conf); const g = new graphlib.Graph({ diff --git a/src/diagrams/sequence/sequenceRenderer.js b/src/diagrams/sequence/sequenceRenderer.js index 5e7cc0c65..138ca7c6b 100644 --- a/src/diagrams/sequence/sequenceRenderer.js +++ b/src/diagrams/sequence/sequenceRenderer.js @@ -452,13 +452,20 @@ export const drawActors = function (diagram, actors, actorKeys, verticalPos) { bounds.bumpVerticalPos(maxHeight); }; -export const drawActorsPopup = function (diagram, actors, actorKeys) { +export const drawActorsPopup = function (diagram, actors, actorKeys, doc) { var maxHeight = 0; var maxWidth = 0; for (let i = 0; i < actorKeys.length; i++) { const actor = actors[actorKeys[i]]; const minMenuWidth = getRequiredPopupWidth(actor); - var menuDimensions = svgDraw.drawPopup(diagram, actor, minMenuWidth, conf, conf.forceMenus); + var menuDimensions = svgDraw.drawPopup( + diagram, + actor, + minMenuWidth, + conf, + conf.forceMenus, + doc + ); if (menuDimensions.height > maxHeight) { maxHeight = menuDimensions.height; } @@ -539,13 +546,26 @@ function adjustLoopHeightForWrap(loopWidths, msg, preMargin, postMargin, addLoop */ export const draw = function (text, id) { conf = configApi.getConfig().sequence; + const securityLevel = configApi.getConfig().securityLevel; + // Handle root and ocument for when rendering in sanbox mode + let sandboxElement; + if (securityLevel === 'sandbox') { + sandboxElement = select('#i' + id); + } + const root = + securityLevel === 'sandbox' + ? select(sandboxElement.nodes()[0].contentDocument.body) + : select('body'); + const doc = securityLevel === 'sandbox' ? sandboxElement.nodes()[0].contentDocument : document; + parser.yy.clear(); parser.yy.setWrap(conf.wrap); parser.parse(text + '\n'); bounds.init(); log.debug(`C:${JSON.stringify(conf, null, 2)}`); - const diagram = select(`[id="${id}"]`); + const diagram = + securityLevel === 'sandbox' ? root.select(`[id="${id}"]`) : select(`[id="${id}"]`); // Fetch data from the parsing const actors = parser.yy.getActors(); @@ -733,7 +753,7 @@ export const draw = function (text, id) { } // only draw popups for the top row of actors. - var requiredBoxSize = drawActorsPopup(diagram, actors, actorKeys); + var requiredBoxSize = drawActorsPopup(diagram, actors, actorKeys, doc); const { bounds: box } = bounds.getBounds(); diff --git a/src/diagrams/sequence/svgDraw.js b/src/diagrams/sequence/svgDraw.js index ea2340eae..a00d10169 100644 --- a/src/diagrams/sequence/svgDraw.js +++ b/src/diagrams/sequence/svgDraw.js @@ -30,6 +30,8 @@ export const drawRect = function (elem, rectData) { const addPopupInteraction = (id, actorCnt) => { addFunction(() => { const arr = document.querySelectorAll(id); + // This will be the case when running in sandboxed mode + if (arr.length === 0) return; arr[0].addEventListener('mouseover', function () { popupMenuUpFunc('actor' + actorCnt + '_popup'); }); diff --git a/src/diagrams/state/stateRenderer-v2.js b/src/diagrams/state/stateRenderer-v2.js index 9137d958e..6499d58a3 100644 --- a/src/diagrams/state/stateRenderer-v2.js +++ b/src/diagrams/state/stateRenderer-v2.js @@ -258,6 +258,8 @@ export const draw = function (text, id) { const nodeSpacing = conf.nodeSpacing || 50; const rankSpacing = conf.rankSpacing || 50; + const securityLevel = getConfig().securityLevel; + log.info(stateDb.getRootDocV2()); stateDb.extract(stateDb.getRootDocV2()); log.info(stateDb.getRootDocV2()); @@ -281,10 +283,20 @@ export const draw = function (text, id) { setupNode(g, undefined, stateDb.getRootDocV2(), true); // Set up an SVG group so that we can translate the final graph. - const svg = select(`[id="${id}"]`); + let sandboxElement; + if (securityLevel === 'sandbox') { + sandboxElement = select('#i' + id); + } + const root = + securityLevel === 'sandbox' + ? select(sandboxElement.nodes()[0].contentDocument.body) + : select('body'); + const doc = securityLevel === 'sandbox' ? sandboxElement.nodes()[0].contentDocument : document; + const svg = root.select(`[id="${id}"]`); // Run the renderer. This is what draws the final graph. - const element = select('#' + id + ' g'); + + const element = root.select('#' + id + ' g'); render(element, g, ['barb'], 'statediagram', id); const padding = 8; diff --git a/src/diagrams/state/stateRenderer.js b/src/diagrams/state/stateRenderer.js index 5a297aa05..e243987bc 100644 --- a/src/diagrams/state/stateRenderer.js +++ b/src/diagrams/state/stateRenderer.js @@ -46,12 +46,24 @@ const insertMarkers = function (elem) { */ export const draw = function (text, id) { conf = getConfig().state; + const securityLevel = getConfig().securityLevel; + // Handle root and ocument for when rendering in sanbox mode + let sandboxElement; + if (securityLevel === 'sandbox') { + sandboxElement = select('#i' + id); + } + const root = + securityLevel === 'sandbox' + ? select(sandboxElement.nodes()[0].contentDocument.body) + : select('body'); + const doc = securityLevel === 'sandbox' ? sandboxElement.nodes()[0].contentDocument : document; + parser.yy.clear(); parser.parse(text); log.debug('Rendering diagram ' + text); // Fetch the default direction, use TD if none was found - const diagram = select(`[id='${id}']`); + const diagram = root.select(`[id='${id}']`); insertMarkers(diagram); // Layout graph, Create a new directed graph @@ -69,7 +81,7 @@ export const draw = function (text, id) { }); const rootDoc = stateDb.getRootDoc(); - renderDoc(rootDoc, diagram, undefined, false); + renderDoc(rootDoc, diagram, undefined, false, root, doc); const padding = conf.padding; const bounds = diagram.node().getBBox(); @@ -90,7 +102,7 @@ const getLabelWidth = (text) => { return text ? text.length * conf.fontSizeFactor : 1; }; -const renderDoc = (doc, diagram, parentId, altBkg) => { +const renderDoc = (doc, diagram, parentId, altBkg, root, domDocument) => { // Layout graph, Create a new directed graph const graph = new graphlib.Graph({ compound: true, @@ -159,7 +171,7 @@ const renderDoc = (doc, diagram, parentId, altBkg) => { let node; if (stateDef.doc) { let sub = diagram.append('g').attr('id', stateDef.id).attr('class', 'stateGroup'); - node = renderDoc(stateDef.doc, sub, stateDef.id, !altBkg); + node = renderDoc(stateDef.doc, sub, stateDef.id, !altBkg, root, domDocument); if (first) { // first = false; @@ -234,21 +246,22 @@ const renderDoc = (doc, diagram, parentId, altBkg) => { graph.nodes().forEach(function (v) { if (typeof v !== 'undefined' && typeof graph.node(v) !== 'undefined') { log.warn('Node ' + v + ': ' + JSON.stringify(graph.node(v))); - select('#' + svgElem.id + ' #' + v).attr( - 'transform', - 'translate(' + - (graph.node(v).x - graph.node(v).width / 2) + - ',' + - (graph.node(v).y + - (transformationLog[v] ? transformationLog[v].y : 0) - - graph.node(v).height / 2) + - ' )' - ); - select('#' + svgElem.id + ' #' + v).attr( - 'data-x-shift', - graph.node(v).x - graph.node(v).width / 2 - ); - const dividers = document.querySelectorAll('#' + svgElem.id + ' #' + v + ' .divider'); + root + .select('#' + svgElem.id + ' #' + v) + .attr( + 'transform', + 'translate(' + + (graph.node(v).x - graph.node(v).width / 2) + + ',' + + (graph.node(v).y + + (transformationLog[v] ? transformationLog[v].y : 0) - + graph.node(v).height / 2) + + ' )' + ); + root + .select('#' + svgElem.id + ' #' + v) + .attr('data-x-shift', graph.node(v).x - graph.node(v).width / 2); + const dividers = domDocument.querySelectorAll('#' + svgElem.id + ' #' + v + ' .divider'); dividers.forEach((divider) => { const parent = divider.parentElement; let pWidth = 0; diff --git a/src/diagrams/user-journey/journeyRenderer.js b/src/diagrams/user-journey/journeyRenderer.js index 18ac05147..7ce2caa68 100644 --- a/src/diagrams/user-journey/journeyRenderer.js +++ b/src/diagrams/user-journey/journeyRenderer.js @@ -54,8 +54,20 @@ export const draw = function (text, id) { parser.yy.clear(); parser.parse(text + '\n'); + const securityLevel = getConfig().securityLevel; + // Handle root and ocument for when rendering in sanbox mode + let sandboxElement; + if (securityLevel === 'sandbox') { + sandboxElement = select('#i' + id); + } + const root = + securityLevel === 'sandbox' + ? select(sandboxElement.nodes()[0].contentDocument.body) + : select('body'); + const doc = securityLevel === 'sandbox' ? sandboxElement.nodes()[0].contentDocument : document; + bounds.init(); - const diagram = select('#' + id); + const diagram = root.select('#' + id); diagram.attr('xmlns:xlink', 'http://www.w3.org/1999/xlink'); svgDraw.initGraphics(diagram); diff --git a/src/mermaid.js b/src/mermaid.js index 2a113ee68..5d7a0090f 100644 --- a/src/mermaid.js +++ b/src/mermaid.js @@ -83,6 +83,7 @@ const init = function () { let txt; for (let i = 0; i < nodes.length; i++) { + // element is the current div with mermaid class const element = nodes[i]; /*! Check if previously processed */ diff --git a/src/mermaidAPI.js b/src/mermaidAPI.js index b0629becd..79ef86c13 100755 --- a/src/mermaidAPI.js +++ b/src/mermaidAPI.js @@ -232,10 +232,43 @@ const render = function (id, _txt, cb, container) { txt = 'graph TB;a[Maximum text size in diagram exceeded];style a fill:#faa'; } + // let d3Iframe; + let root = select('body'); + + // In regular execurtion the container will be the div with a mermaid class if (typeof container !== 'undefined') { + if (cnf.securityLevel === 'sandbox') { + // IF we are in sandboxed mode, we do everyting mermaid related + // in a sandboxed div + const iframe = select('body') + .append('iframe') + .attr('id', 'i' + id) + .attr('style', 'width: 100%; height: 100%;') + .attr('sandbox', ''); + // const iframeBody = ; + root = select(iframe.nodes()[0].contentDocument.body); + root.node().style.margin = 0; + } + + // A container was provided by the caller container.innerHTML = ''; - select(container) + if (cnf.securityLevel === 'sandbox') { + // IF we are in sandboxed mode, we do everyting mermaid related + // in a sandboxed div + const iframe = select(container) + .append('iframe') + .attr('id', 'i' + id) + .attr('style', 'width: 100%; height: 100%;') + .attr('sandbox', ''); + // const iframeBody = ; + root = select(iframe.nodes()[0].contentDocument.body); + root.node().style.margin = 0; + } else { + root = select(container); + } + + root .append('div') .attr('id', 'd' + id) .attr('style', 'font-family: ' + cnf.fontFamily) @@ -245,18 +278,57 @@ const render = function (id, _txt, cb, container) { .attr('xmlns', 'http://www.w3.org/2000/svg') .append('g'); } else { + // No container was provided + // If there is an existsing element with the id, we remove it + // this likley a previously rendered diagram const existingSvg = document.getElementById(id); if (existingSvg) { existingSvg.remove(); } - const element = document.querySelector('#' + 'd' + id); + + // Remove previous tpm element if it exists + let element; + if (cnf.securityLevel !== 'sandbox') { + element = document.querySelector('#' + 'd' + id); + } else { + element = document.querySelector('#' + 'i' + id); + } if (element) { element.remove(); } - select('body') + // if (cnf.securityLevel === 'sandbox') { + // const iframe = select('body') + // .append('iframe') + // .attr('id', 'i' + id) + // .attr('sandbox', ''); + // // const iframeBody = ; + // root = select(iframe.nodes()[0].contentDocument.body); + // } + + // Add the tmp div used for rendering with the id `d${id}` + // d+id it will contain a svg with the id "id" + + if (cnf.securityLevel === 'sandbox') { + // IF we are in sandboxed mode, we do everyting mermaid related + // in a sandboxed div + const iframe = select('body') + .append('iframe') + .attr('id', 'i' + id) + .attr('style', 'width: 100%; height: 100%;') + .attr('sandbox', ''); + // const iframeBody = ; + root = select(iframe.nodes()[0].contentDocument.body); + root.node().style.margin = 0; + } else { + root = select('body'); + } + + // This is the temporary div + root .append('div') .attr('id', 'd' + id) + // this is the seed of the svg to be rendered .append('svg') .attr('id', id) .attr('width', '100%') @@ -264,10 +336,10 @@ const render = function (id, _txt, cb, container) { .append('g'); } - window.txt = txt; txt = encodeEntities(txt); - const element = select('#d' + id).node(); + // Get the tmp element containing the the svg + const element = root.select('#d' + id).node(); const graphType = utils.detectType(txt, cnf); // insert inline style into svg @@ -430,14 +502,19 @@ const render = function (id, _txt, cb, container) { throw e; } - select(`[id="${id}"]`) + root + .select(`[id="${id}"]`) .selectAll('foreignobject > *') .attr('xmlns', 'http://www.w3.org/1999/xhtml'); // Fix for when the base tag is used - let svgCode = select('#d' + id).node().innerHTML; + let svgCode = root.select('#d' + id).node().innerHTML; + log.debug('cnf.arrowMarkerAbsolute', cnf.arrowMarkerAbsolute); - if (!cnf.arrowMarkerAbsolute || cnf.arrowMarkerAbsolute === 'false') { + if ( + (!cnf.arrowMarkerAbsolute || cnf.arrowMarkerAbsolute === 'false') && + cnf.arrowMarkerAbsolute !== 'sandbox' + ) { svgCode = svgCode.replace(/marker-end="url\(.*?#/g, 'marker-end="url(#', 'g'); } @@ -446,6 +523,21 @@ const render = function (id, _txt, cb, container) { // Fix for when the br tag is used svgCode = svgCode.replace(/
/g, '
'); + if (cnf.securityLevel === 'sandbox') { + let svgEl = root.select('#d' + id + ' svg').node(); + let width = '100%'; + let height = '100%'; + if (svgEl) { + // width = svgEl.viewBox.baseVal.width + 'px'; + height = svgEl.viewBox.baseVal.height + 'px'; + } + svgCode = ``; + } + if (typeof cb !== 'undefined') { switch (graphType) { case 'flowchart': @@ -467,11 +559,10 @@ const render = function (id, _txt, cb, container) { } attachFunctions(); - const node = select('#d' + id).node(); + const tmpElementSelector = cnf.securityLevel === 'sandbox' ? '#i' + id : '#d' + id; + const node = select(tmpElementSelector).node(); if (node !== null && typeof node.remove === 'function') { - select('#d' + id) - .node() - .remove(); + select(tmpElementSelector).node().remove(); } return svgCode; @@ -570,6 +661,7 @@ function updateRendererConfigs(conf) { errorRenderer.setConf(conf.class); } +/** To be removed */ function reinitialize() { // `mermaidAPI.reinitialize: v${pkg.version}`, // JSON.stringify(options),