diff --git a/core/modules/config.js b/core/modules/config.js index 3667d4387..3546a21bc 100644 --- a/core/modules/config.js +++ b/core/modules/config.js @@ -34,15 +34,3 @@ exports.htmlVoidElements = "area,base,br,col,command,embed,hr,img,input,keygen,l exports.htmlBlockElements = "address,article,aside,audio,blockquote,canvas,dd,details,div,dl,dt,fieldset,figcaption,figure,footer,form,h1,h2,h3,h4,h5,h6,header,hgroup,hr,li,nav,ol,p,pre,section,summary,table,tfoot,ul,video".split(","); exports.htmlUnsafeElements = "script".split(","); - -// Custom Web Components: https://html.spec.whatwg.org/#valid-custom-element-name -exports.htmlForbiddenTags = "annotation-xml,color-profile,font-face,font-face-src,font-face-uri,font-face-format,font-face-name,missing-glyph".split(","); - -// (EBNF notation) - PotentialCustomElementName ::= [a-z] (PCENChar)* '-' (PCENChar)* -// Unicode table with ranges see: https://symbl.cc/en/unicode-table -exports.htmlCustomPrimitives = { - prefix: "[a-z]", - validPCENChar: ".|[0-9]|_|[a-z]|\xB7|[\xC0-\xD6]|[\xD8-\xF6]|[\u00F8-\u037D]|[\u037F-\u1FFF]|[\u200C-\u200D]|[\u203F-\u2040]|[\u2070-\u218F]|[\u2C00-\u2FEF]|[\u3001-\uD7FF]|[\uF900-\uFDCF]|[\uFDF0-\uFFFD]", - sanitizePCENChar: "[\x00-\x2C]|\x2F|[\x3A-\x40]|[\x5B-\x60]|[\x7B-\xB6]|[\xB8-\xBF]|\xD7|\xF7|\x37E|[\u2000\u200B]|[\u200E-\u203E]|[\u2041-\u206F]|[\u2190-\u2BFF]|[\u2FF0-\u3000]|[\uD800-\uF8FF]|[\uFDD0-\uFDEF]|[\uFFFE-\uFFFF]" - }; -exports.htmlCustomPrimitives.nonValidPCENChar = "[A-Z]|" + exports.htmlCustomPrimitives.sanitizePCENChar; diff --git a/core/modules/editor/engines/framed.js b/core/modules/editor/engines/framed.js index ca6d0fac6..1a4b11c45 100644 --- a/core/modules/editor/engines/framed.js +++ b/core/modules/editor/engines/framed.js @@ -45,7 +45,10 @@ function FramedEngine(options) { this.iframeDoc.body.style.padding = "0"; this.widget.domNodes.push(this.iframeNode); // Construct the textarea or input node - var tag = $tw.utils.makeTagNameSafe(this.widget.editTag,"input"); + var tag = this.widget.editTag; + if($tw.config.htmlUnsafeElements.indexOf(tag) !== -1) { + tag = "input"; + } this.domNode = this.iframeDoc.createElement(tag); // Set the text if(this.widget.editTag === "textarea") { @@ -194,7 +197,7 @@ FramedEngine.prototype.handleFocusEvent = function(event) { Handle a keydown event */ FramedEngine.prototype.handleKeydownEvent = function(event) { - if($tw.keyboardManager.handleKeydownEvent(event, {onlyPriority: true})) { + if ($tw.keyboardManager.handleKeydownEvent(event, {onlyPriority: true})) { return true; } diff --git a/core/modules/editor/engines/simple.js b/core/modules/editor/engines/simple.js index 0d498bc10..93f021522 100644 --- a/core/modules/editor/engines/simple.js +++ b/core/modules/editor/engines/simple.js @@ -19,7 +19,10 @@ function SimpleEngine(options) { this.parentNode = options.parentNode; this.nextSibling = options.nextSibling; // Construct the textarea or input node - var tag = $tw.utils.makeTagNameSafe(this.widget.editTag,"input"); + var tag = this.widget.editTag; + if($tw.config.htmlUnsafeElements.indexOf(tag) !== -1) { + tag = "input"; + } this.domNode = this.widget.document.createElement(tag); // Set the text if(this.widget.editTag === "textarea") { diff --git a/core/modules/utils/dom/dom.js b/core/modules/utils/dom/dom.js index 5a60c80de..849c5a88b 100644 --- a/core/modules/utils/dom/dom.js +++ b/core/modules/utils/dom/dom.js @@ -306,13 +306,13 @@ Collect DOM variables */ exports.collectDOMVariables = function(selectedNode,domNode,event) { var variables = {}, - selectedNodeRect, - domNodeRect; + selectedNodeRect, + domNodeRect; if(selectedNode) { $tw.utils.each(selectedNode.attributes,function(attribute) { variables["dom-" + attribute.name] = attribute.value.toString(); }); - + if("offsetLeft" in selectedNode) { // Add variables with a (relative and absolute) popup coordinate string for the selected node var nodeRect = { @@ -337,7 +337,7 @@ exports.collectDOMVariables = function(selectedNode,domNode,event) { variables["tv-selectednode-height"] = selectedNode.offsetHeight.toString(); } } - + if(domNode && ("offsetWidth" in domNode)) { variables["tv-widgetnode-width"] = domNode.offsetWidth.toString(); variables["tv-widgetnode-height"] = domNode.offsetHeight.toString(); @@ -366,7 +366,7 @@ exports.collectDOMVariables = function(selectedNode,domNode,event) { }; /* -Make sure the CSS selector is valid +Make sure the CSS selector is not invalid */ exports.querySelectorSafe = function(selector,baseElement) { baseElement = baseElement || document; @@ -385,37 +385,3 @@ exports.querySelectorAllSafe = function(selector,baseElement) { console.log("Invalid selector: ",selector); } }; - -/* -Sanitize HTML tag- and custom web component names - Check function parameters for invalid character ranges up to \uFFFF. This detects problems in a range JS RegExp can handle - We assume that everything out of js RegExp-range is valid, which is OK for \u10000-\uEFFFF according to the spec - Unicode overview: https://symbl.cc/en/unicode-table/ -*/ -exports.makeTagNameSafe = function(tag,defaultTag) { - // Custom web-components need to be "lowercase()" https://html.spec.whatwg.org/#valid-custom-element-name - var regxSanitizeChars = new RegExp($tw.config.htmlCustomPrimitives.sanitizePCENChar,"mg"); - - // Sanitize inputs to make the logic simpler - defaultTag = (defaultTag) ? defaultTag.replace(regxSanitizeChars,"") : "SPAN"; - tag = (tag) ? tag.replace(regxSanitizeChars,"") : defaultTag; - - // RegExp for valid standard HTML element, extended including hyphen "-" because browsers allow it - var regexStandardChars = /(?:[a-z]|[A-Z]|[0-9]|-)+/g, - result = ""; - - // Check if tag matches standard HTML spec https://html.spec.whatwg.org/#syntax-tag-name - if(tag.match(regexStandardChars)[0] === tag) { - result = tag; - } - // Check for unsafe tag and unsafe defaultTag - if($tw.config.htmlUnsafeElements.indexOf(result.toLowerCase()) !== -1) { - result = ($tw.config.htmlUnsafeElements.indexOf(defaultTag.toLowerCase()) !== -1) ? "safe-" + defaultTag : defaultTag; - } - // Check for forbidden tag names according to spec and log info to help users. See: $:/core/modules/config.js - if($tw.config.htmlForbiddenTags.indexOf(result.toLowerCase()) >= 0) { - console.log("Forbidden custom element:\"" + result.toLowerCase() + "\" See: https://html.spec.whatwg.org/#valid-custom-element-name") - result = "safe-" + result; - } - return result; -}; diff --git a/core/modules/widgets/button.js b/core/modules/widgets/button.js index 0701f30bf..8f6f14376 100644 --- a/core/modules/widgets/button.js +++ b/core/modules/widgets/button.js @@ -35,7 +35,9 @@ ButtonWidget.prototype.render = function(parent,nextSibling) { this.computeAttributes(); this.execute(); // Create element - tag = $tw.utils.makeTagNameSafe(this.buttonTag,tag) + if(this.buttonTag && $tw.config.htmlUnsafeElements.indexOf(this.buttonTag) === -1) { + tag = this.buttonTag; + } domNode = this.document.createElement(tag); this.domNode = domNode; // Assign classes diff --git a/core/modules/widgets/draggable.js b/core/modules/widgets/draggable.js index 0b3d1cc13..0bb2fc169 100644 --- a/core/modules/widgets/draggable.js +++ b/core/modules/widgets/draggable.js @@ -35,7 +35,10 @@ DraggableWidget.prototype.render = function(parent,nextSibling) { // Execute our logic this.execute(); // Sanitise the specified tag - tag = $tw.utils.makeTagNameSafe(this.draggableTag,"div"); + tag = this.draggableTag; + if($tw.config.htmlUnsafeElements.indexOf(tag) !== -1) { + tag = "div"; + } // Create our element domNode = this.document.createElement(tag); // Assign classes diff --git a/core/modules/widgets/droppable.js b/core/modules/widgets/droppable.js index 5998cbe65..fe528d08f 100644 --- a/core/modules/widgets/droppable.js +++ b/core/modules/widgets/droppable.js @@ -32,7 +32,9 @@ DroppableWidget.prototype.render = function(parent,nextSibling) { // Compute attributes and execute state this.computeAttributes(); this.execute(); - tag = $tw.utils.makeTagNameSafe(this.droppableTag,tag); + if(this.droppableTag && $tw.config.htmlUnsafeElements.indexOf(this.droppableTag) === -1) { + tag = this.droppableTag; + } // Create element and assign classes domNode = this.document.createElement(tag); this.domNode = domNode; diff --git a/core/modules/widgets/element.js b/core/modules/widgets/element.js index 1475a7bde..8b0a88e86 100755 --- a/core/modules/widgets/element.js +++ b/core/modules/widgets/element.js @@ -26,10 +26,15 @@ Render this widget into the DOM ElementWidget.prototype.render = function(parent,nextSibling) { this.parentDomNode = parent; this.computeAttributes(); - // Eliminate blacklisted elements + // Neuter blacklisted elements this.tag = this.parseTreeNode.tag; - // Sanitize tag name if needed according to Custom Web-Componenets spec - this.tag = $tw.utils.makeTagNameSafe(this.tag, "safe-" + this.tag); + if($tw.config.htmlUnsafeElements.indexOf(this.tag) !== -1) { + this.tag = "safe-" + this.tag; + } + // Restrict tag name to digits, letts and dashes + this.tag = this.tag.replace(/[^0-9a-zA-Z\-]/mg,""); + // Default to a span + this.tag = this.tag || "span"; // Adjust headings by the current base level var headingLevel = ["h1","h2","h3","h4","h5","h6"].indexOf(this.tag); if(headingLevel !== -1) { diff --git a/core/modules/widgets/eventcatcher.js b/core/modules/widgets/eventcatcher.js index 2cb9b9670..aac6519a4 100644 --- a/core/modules/widgets/eventcatcher.js +++ b/core/modules/widgets/eventcatcher.js @@ -32,7 +32,9 @@ EventWidget.prototype.render = function(parent,nextSibling) { this.execute(); // Create element var tag = this.parseTreeNode.isBlock ? "div" : "span"; - tag = $tw.utils.makeTagNameSafe(this.elementTag,tag) + if(this.elementTag && $tw.config.htmlUnsafeElements.indexOf(this.elementTag) === -1) { + tag = this.elementTag; + } var domNode = this.document.createElement(tag); this.domNode = domNode; // Assign classes diff --git a/core/modules/widgets/keyboard.js b/core/modules/widgets/keyboard.js index 110d0c709..f4f6c2906 100644 --- a/core/modules/widgets/keyboard.js +++ b/core/modules/widgets/keyboard.js @@ -31,7 +31,9 @@ KeyboardWidget.prototype.render = function(parent,nextSibling) { this.computeAttributes(); this.execute(); var tag = this.parseTreeNode.isBlock ? "div" : "span"; - tag = $tw.utils.makeTagNameSafe(this.tag,tag); + if(this.tag && $tw.config.htmlUnsafeElements.indexOf(this.tag) === -1) { + tag = this.tag; + } // Create element var domNode = this.document.createElement(tag); // Assign classes @@ -48,7 +50,7 @@ KeyboardWidget.prototype.render = function(parent,nextSibling) { }; KeyboardWidget.prototype.handleChangeEvent = function(event) { - if($tw.keyboardManager.handleKeydownEvent(event, {onlyPriority: true})) { + if ($tw.keyboardManager.handleKeydownEvent(event, {onlyPriority: true})) { return true; } diff --git a/core/modules/widgets/link.js b/core/modules/widgets/link.js index 94c7cbb36..d2c599542 100755 --- a/core/modules/widgets/link.js +++ b/core/modules/widgets/link.js @@ -62,7 +62,9 @@ LinkWidget.prototype.renderLink = function(parent,nextSibling) { var self = this; // Sanitise the specified tag var tag = this.linkTag; - tag = $tw.utils.makeTagNameSafe(tag,"a"); + if($tw.config.htmlUnsafeElements.indexOf(tag) !== -1) { + tag = "a"; + } // Create our element var namespace = this.getVariable("namespace",{defaultValue: "http://www.w3.org/1999/xhtml"}), domNode = this.document.createElementNS(namespace,tag); diff --git a/core/modules/widgets/reveal.js b/core/modules/widgets/reveal.js index a42efe2b8..f57f1cf42 100755 --- a/core/modules/widgets/reveal.js +++ b/core/modules/widgets/reveal.js @@ -30,7 +30,9 @@ RevealWidget.prototype.render = function(parent,nextSibling) { this.computeAttributes(); this.execute(); var tag = this.parseTreeNode.isBlock ? "div" : "span"; - tag = $tw.utils.makeTagNameSafe(this.revealTag,tag); + if(this.revealTag && $tw.config.htmlUnsafeElements.indexOf(this.revealTag) === -1) { + tag = this.revealTag; + } var domNode = this.document.createElement(tag); this.domNode = domNode; this.assignDomNodeClasses(); @@ -91,9 +93,9 @@ RevealWidget.prototype.positionPopup = function(domNode) { left = Math.max(0,left); top = Math.max(0,top); } - if(this.popup.absolute) { + if (this.popup.absolute) { // Traverse the offsetParent chain and correct the offset to make it relative to the parent node. - for(var offsetParentDomNode = domNode.offsetParent; offsetParentDomNode; offsetParentDomNode = offsetParentDomNode.offsetParent) { + for (var offsetParentDomNode = domNode.offsetParent; offsetParentDomNode; offsetParentDomNode = offsetParentDomNode.offsetParent) { left -= offsetParentDomNode.offsetLeft; top -= offsetParentDomNode.offsetTop; }