diff --git a/server/modules/rendering/html-security/renderer.js b/server/modules/rendering/html-security/renderer.js index b57aaf1b..20e98642 100644 --- a/server/modules/rendering/html-security/renderer.js +++ b/server/modules/rendering/html-security/renderer.js @@ -32,6 +32,68 @@ module.exports = { allowedAttrs.push('allow') } + //Changes to keep interactive plantuml object tag + + //only allow specific attributes for plantuml object node + if (typeof pumlImageFormat !== 'undefined' && + pumlImageFormat && + 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 + // 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' && + 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 + && '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..428086e2 100644 --- a/server/modules/rendering/markdown-plantuml/renderer.js +++ b/server/modules/rendering/markdown-plantuml/renderer.js @@ -114,14 +114,78 @@ 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 + + //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)