From 5476163ec7bbab20bdfc39d82efde06f37089f49 Mon Sep 17 00:00:00 2001 From: pmario Date: Fri, 26 Apr 2024 10:22:57 +0200 Subject: [PATCH 1/8] wip proposal still contains commented old code - tests are missing --- core/modules/editor/engines/framed.js | 10 +++++----- core/modules/editor/engines/simple.js | 9 +++++---- core/modules/utils/dom/dom.js | 25 ++++++++++++++++++++----- core/modules/widgets/button.js | 9 +++++---- core/modules/widgets/draggable.js | 8 ++++---- core/modules/widgets/droppable.js | 7 ++++--- core/modules/widgets/element.js | 7 ++++--- core/modules/widgets/eventcatcher.js | 7 ++++--- core/modules/widgets/keyboard.js | 9 +++++---- core/modules/widgets/link.js | 7 ++++--- core/modules/widgets/reveal.js | 11 ++++++----- 11 files changed, 66 insertions(+), 43 deletions(-) diff --git a/core/modules/editor/engines/framed.js b/core/modules/editor/engines/framed.js index 34d11dec7..12c48bcb5 100644 --- a/core/modules/editor/engines/framed.js +++ b/core/modules/editor/engines/framed.js @@ -48,10 +48,10 @@ function FramedEngine(options) { this.iframeDoc.body.style.padding = "0"; this.widget.domNodes.push(this.iframeNode); // Construct the textarea or input node - var tag = this.widget.editTag; - if($tw.config.htmlUnsafeElements.indexOf(tag) !== -1) { - tag = "input"; - } + var tag = $tw.utils.isTagNameSafe(this.widget.editTag, "input"); + // if($tw.config.htmlUnsafeElements.indexOf(tag) !== -1) { + // tag = "input"; + // } this.domNode = this.iframeDoc.createElement(tag); // Set the text if(this.widget.editTag === "textarea") { @@ -200,7 +200,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 809dc58ea..20aa1afac 100644 --- a/core/modules/editor/engines/simple.js +++ b/core/modules/editor/engines/simple.js @@ -22,10 +22,11 @@ function SimpleEngine(options) { this.parentNode = options.parentNode; this.nextSibling = options.nextSibling; // Construct the textarea or input node - var tag = this.widget.editTag; - if($tw.config.htmlUnsafeElements.indexOf(tag) !== -1) { - tag = "input"; - } + var tag = $tw.utils.isTagNameSafe(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 338d96280..c066cca8b 100644 --- a/core/modules/utils/dom/dom.js +++ b/core/modules/utils/dom/dom.js @@ -289,7 +289,7 @@ exports.copyToClipboard = function(text,options) { var succeeded = false; try { succeeded = document.execCommand("copy"); - } catch (err) { + } catch(err) { } if(!options.doNotNotify) { $tw.notifier.display(succeeded ? "$:/language/Notifications/CopiedToClipboard/Succeeded" : "$:/language/Notifications/CopiedToClipboard/Failed"); @@ -306,8 +306,8 @@ 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(); @@ -324,7 +324,7 @@ exports.collectDOMVariables = function(selectedNode,domNode,event) { variables["tv-popup-coords"] = Popup.buildCoordinates(Popup.coordinatePrefix.csOffsetParent,nodeRect); var absRect = $tw.utils.extend({}, nodeRect); - for (var currentNode = selectedNode.offsetParent; currentNode; currentNode = currentNode.offsetParent) { + for(var currentNode = selectedNode.offsetParent; currentNode; currentNode = currentNode.offsetParent) { absRect.left += currentNode.offsetLeft; absRect.top += currentNode.offsetTop; } @@ -366,7 +366,7 @@ exports.collectDOMVariables = function(selectedNode,domNode,event) { }; /* -Make sure the CSS selector is not invalid +Make sure the CSS selector is valid */ exports.querySelectorSafe = function(selector,baseElement) { baseElement = baseElement || document; @@ -386,4 +386,19 @@ exports.querySelectorAllSafe = function(selector,baseElement) { } }; +/* +Make sure HTML tag names are valid +*/ +exports.isTagNameSafe = function(tag,defaultTag) { + defaultTag = defaultTag || "SPAN"; + // This implements standard DOM elements: https://html.spec.whatwg.org/#syntax-tag-name + // It does _not_ implement Custom Elements: https://html.spec.whatwg.org/#valid-custom-element-name + var regexp = /[a-zA-Z0-9]/g; + if(tag && tag.match(regexp) && $tw.config.htmlUnsafeElements.indexOf(tag) === -1) { + return tag; + } + return defaultTag; +}; + + })(); diff --git a/core/modules/widgets/button.js b/core/modules/widgets/button.js index 958b6f6da..3fd7a90e2 100644 --- a/core/modules/widgets/button.js +++ b/core/modules/widgets/button.js @@ -38,9 +38,10 @@ ButtonWidget.prototype.render = function(parent,nextSibling) { this.computeAttributes(); this.execute(); // Create element - if(this.buttonTag && $tw.config.htmlUnsafeElements.indexOf(this.buttonTag) === -1) { - tag = this.buttonTag; - } + // if(this.buttonTag && $tw.config.htmlUnsafeElements.indexOf(this.buttonTag) === -1) { + // tag = this.buttonTag; + // } + tag = $tw.utils.isTagNameSafe(this.buttonTag,tag) domNode = this.document.createElement(tag); this.domNode = domNode; // Assign classes @@ -74,7 +75,7 @@ ButtonWidget.prototype.render = function(parent,nextSibling) { if(this["aria-label"]) { domNode.setAttribute("aria-label",this["aria-label"]); } - if (this.role) { + if(this.role) { domNode.setAttribute("role", this.role); } if(this.popup || this.popupTitle) { diff --git a/core/modules/widgets/draggable.js b/core/modules/widgets/draggable.js index 97e795ae4..bcbc7bc11 100644 --- a/core/modules/widgets/draggable.js +++ b/core/modules/widgets/draggable.js @@ -38,10 +38,10 @@ DraggableWidget.prototype.render = function(parent,nextSibling) { // Execute our logic this.execute(); // Sanitise the specified tag - tag = this.draggableTag; - if($tw.config.htmlUnsafeElements.indexOf(tag) !== -1) { - tag = "div"; - } + tag = $tw.utils.isTagNameSafe(this.draggableTag,"div"); + // 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 0dcba1688..d09a28d37 100644 --- a/core/modules/widgets/droppable.js +++ b/core/modules/widgets/droppable.js @@ -35,9 +35,10 @@ DroppableWidget.prototype.render = function(parent,nextSibling) { // Compute attributes and execute state this.computeAttributes(); this.execute(); - if(this.droppableTag && $tw.config.htmlUnsafeElements.indexOf(this.droppableTag) === -1) { - tag = this.droppableTag; - } + tag = $tw.utils.isTagNameSafe(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 716886e5b..76efae075 100755 --- a/core/modules/widgets/element.js +++ b/core/modules/widgets/element.js @@ -31,9 +31,10 @@ ElementWidget.prototype.render = function(parent,nextSibling) { this.computeAttributes(); // Neuter blacklisted elements this.tag = this.parseTreeNode.tag; - if($tw.config.htmlUnsafeElements.indexOf(this.tag) !== -1) { - this.tag = "safe-" + this.tag; - } + this.tag = $tw.utils.isTagNameSafe(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 diff --git a/core/modules/widgets/eventcatcher.js b/core/modules/widgets/eventcatcher.js index dad9503c6..3ada1a6e4 100644 --- a/core/modules/widgets/eventcatcher.js +++ b/core/modules/widgets/eventcatcher.js @@ -35,9 +35,10 @@ EventWidget.prototype.render = function(parent,nextSibling) { this.execute(); // Create element var tag = this.parseTreeNode.isBlock ? "div" : "span"; - if(this.elementTag && $tw.config.htmlUnsafeElements.indexOf(this.elementTag) === -1) { - tag = this.elementTag; - } + tag = $tw.utils.isTagNameSafe(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 ce3162f51..ca75fcaf5 100644 --- a/core/modules/widgets/keyboard.js +++ b/core/modules/widgets/keyboard.js @@ -34,9 +34,10 @@ KeyboardWidget.prototype.render = function(parent,nextSibling) { this.computeAttributes(); this.execute(); var tag = this.parseTreeNode.isBlock ? "div" : "span"; - if(this.tag && $tw.config.htmlUnsafeElements.indexOf(this.tag) === -1) { - tag = this.tag; - } + tag = $tw.utils.isTagNameSafe(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 @@ -53,7 +54,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 f02a7cae2..411e9de6b 100755 --- a/core/modules/widgets/link.js +++ b/core/modules/widgets/link.js @@ -61,9 +61,10 @@ LinkWidget.prototype.renderLink = function(parent,nextSibling) { var self = this; // Sanitise the specified tag var tag = this.linkTag; - if($tw.config.htmlUnsafeElements.indexOf(tag) !== -1) { - tag = "a"; - } + tag = $tw.utils.isTagNameSafe(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 3e3510f75..9515683dd 100755 --- a/core/modules/widgets/reveal.js +++ b/core/modules/widgets/reveal.js @@ -33,9 +33,10 @@ RevealWidget.prototype.render = function(parent,nextSibling) { this.computeAttributes(); this.execute(); var tag = this.parseTreeNode.isBlock ? "div" : "span"; - if(this.revealTag && $tw.config.htmlUnsafeElements.indexOf(this.revealTag) === -1) { - tag = this.revealTag; - } + tag = $tw.utils.isTagNameSafe(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(); @@ -96,9 +97,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; } From 8f5d2d34c6b483bdca6d246624effb4cd93c6586 Mon Sep 17 00:00:00 2001 From: pmario Date: Sat, 4 May 2024 15:41:28 +0200 Subject: [PATCH 2/8] rename isTagNameSafe to makeTagNameSafe --- core/modules/editor/engines/framed.js | 2 +- core/modules/editor/engines/simple.js | 2 +- core/modules/utils/dom/dom.js | 6 +++--- core/modules/widgets/button.js | 2 +- core/modules/widgets/draggable.js | 2 +- core/modules/widgets/droppable.js | 2 +- core/modules/widgets/element.js | 2 +- core/modules/widgets/eventcatcher.js | 2 +- core/modules/widgets/keyboard.js | 2 +- core/modules/widgets/link.js | 2 +- core/modules/widgets/reveal.js | 2 +- 11 files changed, 13 insertions(+), 13 deletions(-) diff --git a/core/modules/editor/engines/framed.js b/core/modules/editor/engines/framed.js index 12c48bcb5..3a5adea75 100644 --- a/core/modules/editor/engines/framed.js +++ b/core/modules/editor/engines/framed.js @@ -48,7 +48,7 @@ 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.isTagNameSafe(this.widget.editTag, "input"); + var tag = $tw.utils.makeTagNameSafe(this.widget.editTag, "input"); // if($tw.config.htmlUnsafeElements.indexOf(tag) !== -1) { // tag = "input"; // } diff --git a/core/modules/editor/engines/simple.js b/core/modules/editor/engines/simple.js index 20aa1afac..fbd02399b 100644 --- a/core/modules/editor/engines/simple.js +++ b/core/modules/editor/engines/simple.js @@ -22,7 +22,7 @@ function SimpleEngine(options) { this.parentNode = options.parentNode; this.nextSibling = options.nextSibling; // Construct the textarea or input node - var tag = $tw.utils.isTagNameSafe(this.widget.editTag,"input"); + var tag = $tw.utils.makeTagNameSafe(this.widget.editTag,"input"); // var tag = this.widget.editTag; // if($tw.config.htmlUnsafeElements.indexOf(tag) !== -1) { // tag = "input"; diff --git a/core/modules/utils/dom/dom.js b/core/modules/utils/dom/dom.js index c066cca8b..5e936e0b6 100644 --- a/core/modules/utils/dom/dom.js +++ b/core/modules/utils/dom/dom.js @@ -389,10 +389,10 @@ exports.querySelectorAllSafe = function(selector,baseElement) { /* Make sure HTML tag names are valid */ -exports.isTagNameSafe = function(tag,defaultTag) { +exports.makeTagNameSafe = function(tag,defaultTag) { defaultTag = defaultTag || "SPAN"; - // This implements standard DOM elements: https://html.spec.whatwg.org/#syntax-tag-name - // It does _not_ implement Custom Elements: https://html.spec.whatwg.org/#valid-custom-element-name + // This implements a check for standard DOM elements: https://html.spec.whatwg.org/#syntax-tag-name + // It does _not_ deal with Custom Elements: https://html.spec.whatwg.org/#valid-custom-element-name var regexp = /[a-zA-Z0-9]/g; if(tag && tag.match(regexp) && $tw.config.htmlUnsafeElements.indexOf(tag) === -1) { return tag; diff --git a/core/modules/widgets/button.js b/core/modules/widgets/button.js index 3fd7a90e2..712c15c26 100644 --- a/core/modules/widgets/button.js +++ b/core/modules/widgets/button.js @@ -41,7 +41,7 @@ ButtonWidget.prototype.render = function(parent,nextSibling) { // if(this.buttonTag && $tw.config.htmlUnsafeElements.indexOf(this.buttonTag) === -1) { // tag = this.buttonTag; // } - tag = $tw.utils.isTagNameSafe(this.buttonTag,tag) + tag = $tw.utils.makeTagNameSafe(this.buttonTag,tag) 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 bcbc7bc11..628c2cbff 100644 --- a/core/modules/widgets/draggable.js +++ b/core/modules/widgets/draggable.js @@ -38,7 +38,7 @@ DraggableWidget.prototype.render = function(parent,nextSibling) { // Execute our logic this.execute(); // Sanitise the specified tag - tag = $tw.utils.isTagNameSafe(this.draggableTag,"div"); + tag = $tw.utils.makeTagNameSafe(this.draggableTag,"div"); // if($tw.config.htmlUnsafeElements.indexOf(tag) !== -1) { // tag = "div"; // } diff --git a/core/modules/widgets/droppable.js b/core/modules/widgets/droppable.js index d09a28d37..e9ede0e50 100644 --- a/core/modules/widgets/droppable.js +++ b/core/modules/widgets/droppable.js @@ -35,7 +35,7 @@ DroppableWidget.prototype.render = function(parent,nextSibling) { // Compute attributes and execute state this.computeAttributes(); this.execute(); - tag = $tw.utils.isTagNameSafe(this.droppableTag,tag); + tag = $tw.utils.makeTagNameSafe(this.droppableTag,tag); // if(this.droppableTag && $tw.config.htmlUnsafeElements.indexOf(this.droppableTag) === -1) { // tag = this.droppableTag; // } diff --git a/core/modules/widgets/element.js b/core/modules/widgets/element.js index 76efae075..4b46e52d0 100755 --- a/core/modules/widgets/element.js +++ b/core/modules/widgets/element.js @@ -31,7 +31,7 @@ ElementWidget.prototype.render = function(parent,nextSibling) { this.computeAttributes(); // Neuter blacklisted elements this.tag = this.parseTreeNode.tag; - this.tag = $tw.utils.isTagNameSafe(this.tag, "safe" + this.tag); + this.tag = $tw.utils.makeTagNameSafe(this.tag, "safe" + this.tag); // if($tw.config.htmlUnsafeElements.indexOf(this.tag) !== -1) { // this.tag = "safe-" + this.tag; // } diff --git a/core/modules/widgets/eventcatcher.js b/core/modules/widgets/eventcatcher.js index 3ada1a6e4..6de3d9690 100644 --- a/core/modules/widgets/eventcatcher.js +++ b/core/modules/widgets/eventcatcher.js @@ -35,7 +35,7 @@ EventWidget.prototype.render = function(parent,nextSibling) { this.execute(); // Create element var tag = this.parseTreeNode.isBlock ? "div" : "span"; - tag = $tw.utils.isTagNameSafe(this.elementTag,tag) + tag = $tw.utils.makeTagNameSafe(this.elementTag,tag) // if(this.elementTag && $tw.config.htmlUnsafeElements.indexOf(this.elementTag) === -1) { // tag = this.elementTag; // } diff --git a/core/modules/widgets/keyboard.js b/core/modules/widgets/keyboard.js index ca75fcaf5..e6fd97694 100644 --- a/core/modules/widgets/keyboard.js +++ b/core/modules/widgets/keyboard.js @@ -34,7 +34,7 @@ KeyboardWidget.prototype.render = function(parent,nextSibling) { this.computeAttributes(); this.execute(); var tag = this.parseTreeNode.isBlock ? "div" : "span"; - tag = $tw.utils.isTagNameSafe(this.tag,tag); + tag = $tw.utils.makeTagNameSafe(this.tag,tag); // if(this.tag && $tw.config.htmlUnsafeElements.indexOf(this.tag) === -1) { // tag = this.tag; // } diff --git a/core/modules/widgets/link.js b/core/modules/widgets/link.js index 411e9de6b..b6e0da156 100755 --- a/core/modules/widgets/link.js +++ b/core/modules/widgets/link.js @@ -61,7 +61,7 @@ LinkWidget.prototype.renderLink = function(parent,nextSibling) { var self = this; // Sanitise the specified tag var tag = this.linkTag; - tag = $tw.utils.isTagNameSafe(tag,"a"); + tag = $tw.utils.makeTagNameSafe(tag,"a"); // if($tw.config.htmlUnsafeElements.indexOf(tag) !== -1) { // tag = "a"; // } diff --git a/core/modules/widgets/reveal.js b/core/modules/widgets/reveal.js index 9515683dd..3acc01841 100755 --- a/core/modules/widgets/reveal.js +++ b/core/modules/widgets/reveal.js @@ -33,7 +33,7 @@ RevealWidget.prototype.render = function(parent,nextSibling) { this.computeAttributes(); this.execute(); var tag = this.parseTreeNode.isBlock ? "div" : "span"; - tag = $tw.utils.isTagNameSafe(this.revealTag,tag); + tag = $tw.utils.makeTagNameSafe(this.revealTag,tag); // if(this.revealTag && $tw.config.htmlUnsafeElements.indexOf(this.revealTag) === -1) { // tag = this.revealTag; // } From 78ffa63ac56331d98e2a10c58d64ddc9946b0691 Mon Sep 17 00:00:00 2001 From: pmario Date: Sat, 4 May 2024 15:57:51 +0200 Subject: [PATCH 3/8] remove comments --- core/modules/editor/engines/framed.js | 3 --- core/modules/editor/engines/simple.js | 4 ---- core/modules/widgets/button.js | 3 --- core/modules/widgets/draggable.js | 3 --- core/modules/widgets/droppable.js | 3 --- core/modules/widgets/element.js | 3 --- core/modules/widgets/eventcatcher.js | 3 --- core/modules/widgets/keyboard.js | 3 --- core/modules/widgets/link.js | 3 --- core/modules/widgets/reveal.js | 3 --- 10 files changed, 31 deletions(-) diff --git a/core/modules/editor/engines/framed.js b/core/modules/editor/engines/framed.js index 3a5adea75..f37080082 100644 --- a/core/modules/editor/engines/framed.js +++ b/core/modules/editor/engines/framed.js @@ -49,9 +49,6 @@ function FramedEngine(options) { this.widget.domNodes.push(this.iframeNode); // Construct the textarea or input node var tag = $tw.utils.makeTagNameSafe(this.widget.editTag, "input"); - // if($tw.config.htmlUnsafeElements.indexOf(tag) !== -1) { - // tag = "input"; - // } this.domNode = this.iframeDoc.createElement(tag); // Set the text if(this.widget.editTag === "textarea") { diff --git a/core/modules/editor/engines/simple.js b/core/modules/editor/engines/simple.js index fbd02399b..cdb378816 100644 --- a/core/modules/editor/engines/simple.js +++ b/core/modules/editor/engines/simple.js @@ -23,10 +23,6 @@ function SimpleEngine(options) { 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/widgets/button.js b/core/modules/widgets/button.js index 712c15c26..9bae74238 100644 --- a/core/modules/widgets/button.js +++ b/core/modules/widgets/button.js @@ -38,9 +38,6 @@ ButtonWidget.prototype.render = function(parent,nextSibling) { this.computeAttributes(); this.execute(); // Create element - // if(this.buttonTag && $tw.config.htmlUnsafeElements.indexOf(this.buttonTag) === -1) { - // tag = this.buttonTag; - // } tag = $tw.utils.makeTagNameSafe(this.buttonTag,tag) domNode = this.document.createElement(tag); this.domNode = domNode; diff --git a/core/modules/widgets/draggable.js b/core/modules/widgets/draggable.js index 628c2cbff..9bc30b81c 100644 --- a/core/modules/widgets/draggable.js +++ b/core/modules/widgets/draggable.js @@ -39,9 +39,6 @@ DraggableWidget.prototype.render = function(parent,nextSibling) { this.execute(); // Sanitise the specified tag tag = $tw.utils.makeTagNameSafe(this.draggableTag,"div"); - // 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 e9ede0e50..5cbe50b7a 100644 --- a/core/modules/widgets/droppable.js +++ b/core/modules/widgets/droppable.js @@ -36,9 +36,6 @@ DroppableWidget.prototype.render = function(parent,nextSibling) { 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 4b46e52d0..0dd52eb30 100755 --- a/core/modules/widgets/element.js +++ b/core/modules/widgets/element.js @@ -32,9 +32,6 @@ ElementWidget.prototype.render = function(parent,nextSibling) { // Neuter blacklisted elements this.tag = this.parseTreeNode.tag; 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 diff --git a/core/modules/widgets/eventcatcher.js b/core/modules/widgets/eventcatcher.js index 6de3d9690..cad9121aa 100644 --- a/core/modules/widgets/eventcatcher.js +++ b/core/modules/widgets/eventcatcher.js @@ -36,9 +36,6 @@ EventWidget.prototype.render = function(parent,nextSibling) { // 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 e6fd97694..0d39225cd 100644 --- a/core/modules/widgets/keyboard.js +++ b/core/modules/widgets/keyboard.js @@ -35,9 +35,6 @@ KeyboardWidget.prototype.render = function(parent,nextSibling) { 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 diff --git a/core/modules/widgets/link.js b/core/modules/widgets/link.js index b6e0da156..506625409 100755 --- a/core/modules/widgets/link.js +++ b/core/modules/widgets/link.js @@ -62,9 +62,6 @@ LinkWidget.prototype.renderLink = function(parent,nextSibling) { // 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 3acc01841..2fdd9c134 100755 --- a/core/modules/widgets/reveal.js +++ b/core/modules/widgets/reveal.js @@ -34,9 +34,6 @@ RevealWidget.prototype.render = function(parent,nextSibling) { 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(); From 6172c7fae9379a0a1b815d520a760cf1681e013c Mon Sep 17 00:00:00 2001 From: pmario Date: Sat, 4 May 2024 16:15:48 +0200 Subject: [PATCH 4/8] remove redundant space --- core/modules/editor/engines/framed.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/modules/editor/engines/framed.js b/core/modules/editor/engines/framed.js index f37080082..1e9d69188 100644 --- a/core/modules/editor/engines/framed.js +++ b/core/modules/editor/engines/framed.js @@ -48,7 +48,7 @@ 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 = $tw.utils.makeTagNameSafe(this.widget.editTag,"input"); this.domNode = this.iframeDoc.createElement(tag); // Set the text if(this.widget.editTag === "textarea") { From 8345445b50994bff8b4e706961f92184f8493108 Mon Sep 17 00:00:00 2001 From: pmario Date: Sun, 9 Jun 2024 10:42:30 +0200 Subject: [PATCH 5/8] add htmlCustomPrimitives to the $tw.config object --- core/modules/config.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/core/modules/config.js b/core/modules/config.js index 399af598b..9f5f7a78e 100644 --- a/core/modules/config.js +++ b/core/modules/config.js @@ -38,4 +38,15 @@ exports.htmlBlockElements = "address,article,aside,audio,blockquote,canvas,dd,de exports.htmlUnsafeElements = "script".split(","); +// See: 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(","); + +// Unicode table with ranges see: https://symbl.cc/en/unicode-table +// The "prefix" is obligatory! +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]", + nonValidPCENChar: "[\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]" +}; + })(); From 68ba4d5bb1b1a4fdfbb44ac49da716aabb5814b6 Mon Sep 17 00:00:00 2001 From: pmario Date: Sun, 9 Jun 2024 10:42:56 +0200 Subject: [PATCH 6/8] remove tag-sanitation from element.js --- core/modules/widgets/element.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/modules/widgets/element.js b/core/modules/widgets/element.js index 0dd52eb30..534442759 100755 --- a/core/modules/widgets/element.js +++ b/core/modules/widgets/element.js @@ -33,7 +33,9 @@ ElementWidget.prototype.render = function(parent,nextSibling) { this.tag = this.parseTreeNode.tag; this.tag = $tw.utils.makeTagNameSafe(this.tag, "safe" + this.tag); // Restrict tag name to digits, letts and dashes - this.tag = this.tag.replace(/[^0-9a-zA-Z\-]/mg,""); + + // 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 From 04a916d4a3f6c4da2c953c328c5f152965497228 Mon Sep 17 00:00:00 2001 From: pmario Date: Sun, 9 Jun 2024 10:43:45 +0200 Subject: [PATCH 7/8] WIP - add html-element sanitation to the new makeTagNameSafe function, so it can be used globally --- core/modules/utils/dom/dom.js | 50 ++++++++++++++++++++++++++++++----- 1 file changed, 44 insertions(+), 6 deletions(-) diff --git a/core/modules/utils/dom/dom.js b/core/modules/utils/dom/dom.js index 5e936e0b6..f8f779582 100644 --- a/core/modules/utils/dom/dom.js +++ b/core/modules/utils/dom/dom.js @@ -388,16 +388,54 @@ exports.querySelectorAllSafe = function(selector,baseElement) { /* Make sure HTML tag names are valid + 1. Check the string, if it is a valid html tag - using this spec: https://html.spec.whatwg.org/#syntax-tag-name + 1.1 Tag names are "case-insensitive" + 2. Extend 1. and allow hyphens: "-" + 2.1 Browsers allow "AA-AA", so do we + 2.2 AA-AA cannot be used for styling + 2.3 aa-aa can be used for styling + + 3. Implement a forbidden list according to https://html.spec.whatwg.org/#valid-custom-element-name + 3.1 exports.htmlForbiddenTags = "annotation-xml,color-profile,font-face,font-face-src,font-face-uri,font-face-format,font-face-name,missing-glyph".split(","); + + 4. If 1., 2. and 3. pass return the HTML tag as a valid tag -- That should be fast and cover 90% of html tags users will use. + + 5. If 4. does not match, we check for invalid character ranges up to \uFFFF. So we can detect problems in a range JS RegExp can handle. + 6. We assume that everything out of js RegExp-range is valid, which would be OK for \u10000-\uEFFFF according to the spec. + */ exports.makeTagNameSafe = function(tag,defaultTag) { defaultTag = defaultTag || "SPAN"; - // This implements a check for standard DOM elements: https://html.spec.whatwg.org/#syntax-tag-name - // It does _not_ deal with Custom Elements: https://html.spec.whatwg.org/#valid-custom-element-name - var regexp = /[a-zA-Z0-9]/g; - if(tag && tag.match(regexp) && $tw.config.htmlUnsafeElements.indexOf(tag) === -1) { - return tag; + var result = defaultTag; + + // RegExp for valid standard HTML element, extended with hyphen "-" + var regexValidTag = /(?:[a-z]|[A-Z]|[0-9]|-)+/g; + + // Custom web-components must to be "lowercase()" + var regxInvalidlTag = new RegExp($tw.config.htmlCustomPrimitives.nonValidPCENChar,"mg"); + + // Check if tag matches regexp as a whole + if(tag && (tag.match(regexValidTag)[0] === tag)) { + result = tag; // valid tag } - return defaultTag; + + // Sanitize invalid characters in result + if( regxInvalidlTag.exec(result.toLowerCase()) !== null) { + result = result.toLowerCase().replace(regxInvalidlTag,""); + } + + // Check for unsafe tags + if( $tw.config.htmlUnsafeElements.indexOf(result) === -1) { + result = "safe-" + result; + } + + // Check for forbidden tag names and log info + 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; }; From 8a04a09a6804b49e9d7867ff9991aeb8342c5bd1 Mon Sep 17 00:00:00 2001 From: pmario Date: Sun, 9 Jun 2024 14:06:49 +0200 Subject: [PATCH 8/8] simplify sanitation logic and fix inline docs --- core/modules/config.js | 9 ++--- core/modules/utils/dom/dom.js | 58 +++++++++++++-------------------- core/modules/widgets/element.js | 11 ++----- 3 files changed, 31 insertions(+), 47 deletions(-) diff --git a/core/modules/config.js b/core/modules/config.js index 9f5f7a78e..9d9dd3376 100644 --- a/core/modules/config.js +++ b/core/modules/config.js @@ -38,15 +38,16 @@ exports.htmlBlockElements = "address,article,aside,audio,blockquote,canvas,dd,de exports.htmlUnsafeElements = "script".split(","); -// See: https://html.spec.whatwg.org/#valid-custom-element-name +// 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 -// The "prefix" is obligatory! 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]", - nonValidPCENChar: "[\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]" -}; + 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/utils/dom/dom.js b/core/modules/utils/dom/dom.js index f8f779582..ac41b528d 100644 --- a/core/modules/utils/dom/dom.js +++ b/core/modules/utils/dom/dom.js @@ -387,56 +387,44 @@ exports.querySelectorAllSafe = function(selector,baseElement) { }; /* -Make sure HTML tag names are valid +Sanitize HTML tag- and custom web component names 1. Check the string, if it is a valid html tag - using this spec: https://html.spec.whatwg.org/#syntax-tag-name - 1.1 Tag names are "case-insensitive" + 1.1 Tag names are "case insensitive" 2. Extend 1. and allow hyphens: "-" - 2.1 Browsers allow "AA-AA", so do we - 2.2 AA-AA cannot be used for styling - 2.3 aa-aa can be used for styling + 2.1 Browsers allow "AA-AA", so do we. Be aware there may be styling problems - 3. Implement a forbidden list according to https://html.spec.whatwg.org/#valid-custom-element-name - 3.1 exports.htmlForbiddenTags = "annotation-xml,color-profile,font-face,font-face-src,font-face-uri,font-face-format,font-face-name,missing-glyph".split(","); - - 4. If 1., 2. and 3. pass return the HTML tag as a valid tag -- That should be fast and cover 90% of html tags users will use. - - 5. If 4. does not match, we check for invalid character ranges up to \uFFFF. So we can detect problems in a range JS RegExp can handle. - 6. We assume that everything out of js RegExp-range is valid, which would be OK for \u10000-\uEFFFF according to the spec. + 3. Sanitize input parameters - spec: https://html.spec.whatwg.org/#valid-custom-element-name + 4. Implement a forbidden list: exports.htmlForbiddenTags - see: $:/core/modules/config.js + 5. Check function parameters for invalid character ranges up to \uFFFF. This detects problems in a range JS RegExp can handle + 6. 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) { - defaultTag = defaultTag || "SPAN"; - var result = defaultTag; + // Custom web-components need to be "lowercase()" + var regxSanitizeChars = new RegExp($tw.config.htmlCustomPrimitives.sanitizePCENChar,"mg"); + // Sanitize inputs to make the logic simple + defaultTag = (defaultTag) ? defaultTag.replace(regxSanitizeChars,"") : "SPAN"; + tag = (tag) ? tag.replace(regxSanitizeChars,"") : defaultTag; - // RegExp for valid standard HTML element, extended with hyphen "-" - var regexValidTag = /(?:[a-z]|[A-Z]|[0-9]|-)+/g; + // RegExp for valid standard HTML element, extended including hyphen "-" + var regexStandardChars = /(?:[a-z]|[A-Z]|[0-9]|-)+/g, + result = ""; - // Custom web-components must to be "lowercase()" - var regxInvalidlTag = new RegExp($tw.config.htmlCustomPrimitives.nonValidPCENChar,"mg"); - - // Check if tag matches regexp as a whole - if(tag && (tag.match(regexValidTag)[0] === tag)) { - result = tag; // valid tag + // Check if tag matches standard HTML spec + if(tag.match(regexStandardChars)[0] === tag) { + result = tag; } - - // Sanitize invalid characters in result - if( regxInvalidlTag.exec(result.toLowerCase()) !== null) { - result = result.toLowerCase().replace(regxInvalidlTag,""); + // 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 unsafe tags - if( $tw.config.htmlUnsafeElements.indexOf(result) === -1) { - result = "safe-" + result; - } - - // Check for forbidden tag names and log info + // Check for forbidden tag names according to spec and log info to help users 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/element.js b/core/modules/widgets/element.js index 534442759..3f8017960 100755 --- a/core/modules/widgets/element.js +++ b/core/modules/widgets/element.js @@ -29,15 +29,10 @@ Render this widget into the DOM ElementWidget.prototype.render = function(parent,nextSibling) { this.parentDomNode = parent; this.computeAttributes(); - // Neuter blacklisted elements + // Eliminate blacklisted elements this.tag = this.parseTreeNode.tag; - this.tag = $tw.utils.makeTagNameSafe(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"; + // Sanitize tag name if needed according to Custom Web-Componenets spec + this.tag = $tw.utils.makeTagNameSafe(this.tag, "safe-" + this.tag); // Adjust headings by the current base level var headingLevel = ["h1","h2","h3","h4","h5","h6"].indexOf(this.tag); if(headingLevel !== -1) {