From 1f9f708909dc3b05ca583dff6605ca84a1472f47 Mon Sep 17 00:00:00 2001 From: Bharat Rajagopalan Date: Tue, 24 Nov 2020 23:13:02 +0100 Subject: [PATCH 1/3] changes for interactive plantuml --- .../rendering/html-security/renderer.js | 53 +++++++++++++ .../rendering/markdown-plantuml/renderer.js | 76 +++++++++++++++++-- 2 files changed, 121 insertions(+), 8 deletions(-) diff --git a/server/modules/rendering/html-security/renderer.js b/server/modules/rendering/html-security/renderer.js index 3bf9b2dc..9d53433a 100644 --- a/server/modules/rendering/html-security/renderer.js +++ b/server/modules/rendering/html-security/renderer.js @@ -32,6 +32,59 @@ module.exports = { allowedAttrs.push('allow') } + //Changes to keep interactive plantuml object tag + + //only allow specific attributes for plantuml object node + allowedTags.push('object') + allowedAttrs.push('data') + allowedAttrs.push('type') + allowedAttrs.push('style') + allowedAttrs.push('class') + allowedAttrs.push('alt') + + DOMPurify.addHook('uponSanitizeElement', (node, data) => { + // keep object node only if it is + // authorised plantuml using the configured plantuml server + // force attribute values to configured params + // insert the plantuml inside the object as text for search + let isPumlNode=false + + if (data.tagName === 'object') { + //console.log ("Found object node - validating") + //remove node if it doesn't conform to plantuml structure + if (!( 'data' in node.attributes + && 'class' in node.attributes + && 'style' in node.attributes + && 'type' in node.attributes + && 'alt' in node.attributes) + ) { + //console.log ("Attribute mismatch - removing object node") + return node.parentNode.removeChild(node) + } + + dataAttribute = node.getAttribute ('data') + + //only allow configured plantuml server and image format in url + if (dataAttribute + && dataAttribute.startsWith(`${pumlServer}/${pumlImageFormat}`) + ) { + //console.log ("Plantuml node found - setting atribute values") + isPumlNode=true + node.setAttribute ('type', `${pumlObjectType}`) + node.setAttribute ('style', `${pumlObjectStyle}`) + node.setAttribute ('class', `${pumlObjectClass}`) + node.setAttribute ('alt', '') + } + + //if not a plantuml node, then sanitise it + if (!isPumlNode) { + console.log ("Removing unknown object node") + return node.parentNode.removeChild(node) + } + } + }) + //End changes to keep interactive plantuml object tag + input = DOMPurify.sanitize(input, { ADD_ATTR: allowedAttrs, ADD_TAGS: allowedTags diff --git a/server/modules/rendering/markdown-plantuml/renderer.js b/server/modules/rendering/markdown-plantuml/renderer.js index 40eedc04..59135382 100644 --- a/server/modules/rendering/markdown-plantuml/renderer.js +++ b/server/modules/rendering/markdown-plantuml/renderer.js @@ -114,14 +114,74 @@ module.exports = { const zippedCode = encode64(zlib.deflateRawSync('@startuml\n' + contents + '\n@enduml').toString('binary')) - token = state.push('uml_diagram', 'img', 0) - // alt is constructed from children. No point in populating it here. - token.attrs = [ [ 'src', `${server}/${imageFormat}/${zippedCode}` ], [ 'alt', '' ], ['class', 'uml-diagram prefetch-candidate'] ] - token.block = true - token.children = altToken - token.info = params - token.map = [ startLine, nextLine ] - token.markup = markup + //Interactive plantuml changes start here + + //Global variables that will be used in html-security + pumlImageFormat = imageFormat + pumlServer = server + pumlObjectType = 'image/svg+xml' + pumlObjectStyle = 'max-width:100%;height:auto' + pumlObjectClass = 'uml-diagram prefetch-candidate' + + if (imageFormat === 'png') { + //non-interactive and not searchable - use the img tag + token = state.push('uml_diagram', 'img', 0) + // alt is constructed from children. No point in populating it here. + token.attrs = [ [ 'src' , `${server}/${imageFormat}/${zippedCode}` ], + [ 'alt' , '' ], + ['class', `${pumlObjectClass}` ] + ] + token.block = true + token.children = altToken + token.info = params + token.map = [ startLine, nextLine ] + token.markup = markup + } + else if (imageFormat === 'svg') { + // render svg interactively with object tag + /* //plantuml + alt='' class='uml-diagram prefetch-candidate' + style='max-width:100%;height:auto' type='image/svg+xml' > */ + token = state.push('uml_diagram_obj', 'object', 0) + token.attrs = [ [ 'data' , `${server}/${imageFormat}/${zippedCode}`], + [ 'alt' , '' ], + [ 'class', `${pumlObjectClass}` ], + [ 'style', `${pumlObjectStyle}` ], + [ 'type', `${pumlObjectType}` ] + ] + token.block = true + token.children = altToken + token.info = params + token.map = [ startLine, nextLine ] + token.markup = markup + + //backup img node for object render failure + //void element - doesn't need to be closed in html + /* //plantuml alt='' + class='uml-diagram prefetch-candidate' + type='image/svg+xml'> */ + token = state.push('uml_diagram_img', 'img', 0) + // alt is constructed from children. No point in populating it here. + token.attrs = [ [ 'src' , `${server}/${imageFormat}/${zippedCode}` ], + [ 'alt' , '' ], + ['class', `${pumlObjectClass}`] + ] + token.block = true + token.children = altToken + token.info = params + token.map = [ startLine, nextLine ] + token.markup = markup + + //add puml source in object node for wikijs search index + //as wikijs will not index svg content from object tag + //i.e. like @startuml...@enduml + token = state.push ('text', '', 0) + token.content = contents + + //close object tag - + token = state.push('uml_diagram_close', 'object', -1) + } + //Interactive plantuml changes end here state.line = nextLine + (autoClosed ? 1 : 0) From 571d1ddf0e2ead23ee1cb7691555fa7e1a81bd13 Mon Sep 17 00:00:00 2001 From: Bharat Rajagopalan Date: Tue, 29 Dec 2020 15:11:42 +0100 Subject: [PATCH 2/3] changes to optionally allow object tag --- .../modules/rendering/html-security/renderer.js | 14 ++++++++------ .../rendering/markdown-plantuml/renderer.js | 16 ++++++++++------ 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/server/modules/rendering/html-security/renderer.js b/server/modules/rendering/html-security/renderer.js index 9d53433a..956ae6c5 100644 --- a/server/modules/rendering/html-security/renderer.js +++ b/server/modules/rendering/html-security/renderer.js @@ -35,12 +35,14 @@ module.exports = { //Changes to keep interactive plantuml object tag //only allow specific attributes for plantuml object node - allowedTags.push('object') - allowedAttrs.push('data') - allowedAttrs.push('type') - allowedAttrs.push('style') - allowedAttrs.push('class') - allowedAttrs.push('alt') + if (`${pumlImageFormat}` == 'svg') { + allowedTags.push('object') + allowedAttrs.push('data') + allowedAttrs.push('type') + allowedAttrs.push('style') + allowedAttrs.push('class') + allowedAttrs.push('alt') + } DOMPurify.addHook('uponSanitizeElement', (node, data) => { // keep object node only if it is diff --git a/server/modules/rendering/markdown-plantuml/renderer.js b/server/modules/rendering/markdown-plantuml/renderer.js index 59135382..8c12961f 100644 --- a/server/modules/rendering/markdown-plantuml/renderer.js +++ b/server/modules/rendering/markdown-plantuml/renderer.js @@ -172,15 +172,19 @@ module.exports = { token.map = [ startLine, nextLine ] token.markup = markup - //add puml source in object node for wikijs search index - //as wikijs will not index svg content from object tag - //i.e. like @startuml...@enduml - token = state.push ('text', '', 0) - token.content = contents - //close object tag - token = state.push('uml_diagram_close', 'object', -1) } + + //Create hidden text inside html for wikijs search index + // text won't be visible or occupy any space but will be searchable + //i.e.

@startuml...@enduml

+ token = state.push ('hidden', 'p', 0) + token.attrs = [ [ 'style', 'display:none'] ] + token = state.push ('text', '', 0) + token.content = contents + token = state.push('hidden_close', 'p', -1) + //Interactive plantuml changes end here state.line = nextLine + (autoClosed ? 1 : 0) From 8a1fd73f3b1f17a8e9cd919f1f4ae85c785080d4 Mon Sep 17 00:00:00 2001 From: Bharat Rajagopalan Date: Tue, 29 Dec 2020 16:13:19 +0100 Subject: [PATCH 3/3] added null checks and cleanup --- .../modules/rendering/html-security/renderer.js | 17 ++++++++++++----- .../rendering/markdown-plantuml/renderer.js | 10 +++++----- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/server/modules/rendering/html-security/renderer.js b/server/modules/rendering/html-security/renderer.js index 956ae6c5..1ab73cbf 100644 --- a/server/modules/rendering/html-security/renderer.js +++ b/server/modules/rendering/html-security/renderer.js @@ -35,7 +35,9 @@ module.exports = { //Changes to keep interactive plantuml object tag //only allow specific attributes for plantuml object node - if (`${pumlImageFormat}` == 'svg') { + if (typeof pumlImageFormat !== 'undefined' && + pumlImageFormat && + pumlImageFormat == 'svg') { allowedTags.push('object') allowedAttrs.push('data') allowedAttrs.push('type') @@ -51,7 +53,12 @@ module.exports = { // insert the plantuml inside the object as text for search let isPumlNode=false - if (data.tagName === 'object') { + if (data.tagName === 'object' && + typeof pumlServer !== 'undefined' && pumlServer && + typeof pumlObjectStyle !== 'undefined' && pumlObjectStyle && + typeof pumlObjectType !== 'undefined' && pumlObjectType && + typeof pumlObjectClass !== 'undefined' && pumlObjectClass + ) { //console.log ("Found object node - validating") //remove node if it doesn't conform to plantuml structure if (!( 'data' in node.attributes @@ -72,9 +79,9 @@ module.exports = { ) { //console.log ("Plantuml node found - setting atribute values") isPumlNode=true - node.setAttribute ('type', `${pumlObjectType}`) - node.setAttribute ('style', `${pumlObjectStyle}`) - node.setAttribute ('class', `${pumlObjectClass}`) + node.setAttribute ('type', pumlObjectType) + node.setAttribute ('style', pumlObjectStyle) + node.setAttribute ('class', pumlObjectClass) node.setAttribute ('alt', '') } diff --git a/server/modules/rendering/markdown-plantuml/renderer.js b/server/modules/rendering/markdown-plantuml/renderer.js index 8c12961f..428086e2 100644 --- a/server/modules/rendering/markdown-plantuml/renderer.js +++ b/server/modules/rendering/markdown-plantuml/renderer.js @@ -129,7 +129,7 @@ module.exports = { // alt is constructed from children. No point in populating it here. token.attrs = [ [ 'src' , `${server}/${imageFormat}/${zippedCode}` ], [ 'alt' , '' ], - ['class', `${pumlObjectClass}` ] + ['class', pumlObjectClass ] ] token.block = true token.children = altToken @@ -145,9 +145,9 @@ module.exports = { token = state.push('uml_diagram_obj', 'object', 0) token.attrs = [ [ 'data' , `${server}/${imageFormat}/${zippedCode}`], [ 'alt' , '' ], - [ 'class', `${pumlObjectClass}` ], - [ 'style', `${pumlObjectStyle}` ], - [ 'type', `${pumlObjectType}` ] + [ 'class', pumlObjectClass ], + [ 'style', pumlObjectStyle ], + [ 'type', pumlObjectType ] ] token.block = true token.children = altToken @@ -164,7 +164,7 @@ module.exports = { // alt is constructed from children. No point in populating it here. token.attrs = [ [ 'src' , `${server}/${imageFormat}/${zippedCode}` ], [ 'alt' , '' ], - ['class', `${pumlObjectClass}`] + ['class', pumlObjectClass] ] token.block = true token.children = altToken