Protect against script injection

This commit is contained in:
barsdeveloper
2021-12-16 22:57:47 +01:00
parent 43439bbcd3
commit 8f0893447e
14 changed files with 178 additions and 107 deletions

187
dist/ueblueprint.js vendored
View File

@@ -1,38 +1,3 @@
const html = String.raw;
const div = document.createElement("div");
function sanitizeText(value) {
div.textContent = value;
value = div.textContent;
div.innerHTML = "";
return value
}
/**
* @typedef {import("../graph/GraphElement").default} GraphElement
*/
class Template {
/**
* Computes the html content of the target element.
* @param {GraphElement} entity Element of the graph
* @returns The result html
*/
render(entity) {
return ""
}
/**
* Applies the style to the element.
* @param {GraphElement} element Element of the graph
*/
apply(element) {
// TODO replace with the safer element.setHTML(...) when it will be available
element.innerHTML = this.render(element);
}
}
class OrderedIndexArray { class OrderedIndexArray {
/** /**
@@ -359,6 +324,47 @@ class GraphElement extends HTMLElement {
} }
} }
document.createElement("div");
const tagReplacement = {
'&': '&',
'<': '&lt;',
'>': '&gt;',
"'": '&#39;',
'"': '&quot;'
};
function sanitizeText(value) {
if (value.constructor === String) {
return value.replace(/[&<>'"]/g, tag => tagReplacement[tag])
}
return value
}
/**
* @typedef {import("../graph/GraphElement").default} GraphElement
*/
class Template {
/**
* Computes the html content of the target element.
* @param {GraphElement} entity Element of the graph
* @returns The result html
*/
render(entity) {
return ""
}
/**
* Applies the style to the element.
* @param {GraphElement} element Element of the graph
*/
apply(element) {
// TODO replace with the safer element.setHTML(...) when it will be available
element.innerHTML = this.render(element);
}
}
/** /**
* @typedef {import("../graph/GraphSelector").default} GraphSelector * @typedef {import("../graph/GraphSelector").default} GraphSelector
*/ */
@@ -380,11 +386,11 @@ class SelectorTemplate extends Template {
*/ */
applyStartSelecting(selector, initialPosition) { applyStartSelecting(selector, initialPosition) {
// Set initial position // Set initial position
selector.style.setProperty("--ueb-select-from-x", initialPosition[0]); selector.style.setProperty("--ueb-select-from-x", sanitizeText(initialPosition[0]));
selector.style.setProperty("--ueb-select-from-y", initialPosition[1]); selector.style.setProperty("--ueb-select-from-y", sanitizeText(initialPosition[1]));
// Final position coincide with the initial position, at the beginning of selection // Final position coincide with the initial position, at the beginning of selection
selector.style.setProperty("--ueb-select-to-x", initialPosition[0]); selector.style.setProperty("--ueb-select-to-x", sanitizeText(initialPosition[0]));
selector.style.setProperty("--ueb-select-to-y", initialPosition[1]); selector.style.setProperty("--ueb-select-to-y", sanitizeText(initialPosition[1]));
selector.dataset.selecting = "true"; selector.dataset.selecting = "true";
} }
@@ -393,8 +399,8 @@ class SelectorTemplate extends Template {
* @param {GraphSelector} selector Selector element * @param {GraphSelector} selector Selector element
*/ */
applyDoSelecting(selector, finalPosition) { applyDoSelecting(selector, finalPosition) {
selector.style.setProperty("--ueb-select-to-x", finalPosition[0]); selector.style.setProperty("--ueb-select-to-x", sanitizeText(finalPosition[0]));
selector.style.setProperty("--ueb-select-to-y", finalPosition[1]); selector.style.setProperty("--ueb-select-to-y", sanitizeText(finalPosition[1]));
} }
/** /**
@@ -443,6 +449,8 @@ class GraphSelector extends GraphElement {
customElements.define("ueb-selector", GraphSelector); customElements.define("ueb-selector", GraphSelector);
const html = String.raw;
/** @typedef {import("../Blueprint").default} Blueprint */ /** @typedef {import("../Blueprint").default} Blueprint */
class BlueprintTemplate extends Template { class BlueprintTemplate extends Template {
header(element) { header(element) {
@@ -515,7 +523,7 @@ class BlueprintTemplate extends Template {
*/ */
applyZoom(blueprint, newZoom) { applyZoom(blueprint, newZoom) {
blueprint.classList.remove(`ueb-zoom-${blueprint.zoom}`); blueprint.classList.remove(`ueb-zoom-${blueprint.zoom}`);
blueprint.classList.add(`ueb-zoom-${newZoom}`); blueprint.classList.add(sanitizeText`ueb-zoom-${newZoom}`);
} }
/** /**
@@ -523,8 +531,8 @@ class BlueprintTemplate extends Template {
* @param {Blueprint} brueprint The blueprint element * @param {Blueprint} brueprint The blueprint element
*/ */
applyExpand(blueprint) { applyExpand(blueprint) {
blueprint.gridElement.style.setProperty("--ueb-additional-x", blueprint.additional[0]); blueprint.gridElement.style.setProperty("--ueb-additional-x", sanitizeText(blueprint.additional[0]));
blueprint.gridElement.style.setProperty("--ueb-additional-y", blueprint.additional[1]); blueprint.gridElement.style.setProperty("--ueb-additional-y", sanitizeText(blueprint.additional[1]));
} }
/** /**
@@ -532,8 +540,8 @@ class BlueprintTemplate extends Template {
* @param {Blueprint} brueprint The blueprint element * @param {Blueprint} brueprint The blueprint element
*/ */
applyTranlate(blueprint) { applyTranlate(blueprint) {
blueprint.gridElement.style.setProperty("--ueb-translate-x", blueprint.translateValue[0]); blueprint.gridElement.style.setProperty("--ueb-translate-x", sanitizeText(blueprint.translateValue[0]));
blueprint.gridElement.style.setProperty("--ueb-translate-y", blueprint.translateValue[1]); blueprint.gridElement.style.setProperty("--ueb-translate-y", sanitizeText(blueprint.translateValue[1]));
} }
} }
@@ -1461,6 +1469,16 @@ class KeyboardShortcut extends Context {
}; };
} }
/**
*
* @param {String} keyString
* @returns {Object}
*/
static keyOptionsParse(options, keyString) {
options.key = keyString;
return options
}
blueprintFocused() { blueprintFocused() {
document.addEventListener("keydown", this.keyDownHandler); document.addEventListener("keydown", this.keyDownHandler);
} }
@@ -1473,10 +1491,21 @@ class KeyboardShortcut extends Context {
} }
} }
class Configuration {
static deleteNodesKeyboardKey = "Delete"
}
class KeyvoardCanc extends KeyboardShortcut { class KeyvoardCanc extends KeyboardShortcut {
/**
*
* @param {HTMLElement} target
* @param {import("../Blueprint").default} blueprint
* @param {OBject} options
*/
constructor(target, blueprint, options = {}) { constructor(target, blueprint, options = {}) {
options.key = "Delete"; options = KeyboardShortcut.keyOptionsParse(options, Configuration.deleteNodesKeyboardKey);
super(target, blueprint, options); super(target, blueprint, options);
} }
@@ -1506,33 +1535,6 @@ class MouseTracking extends Pointing {
} }
} }
/**
* @typedef {import("../graph/SelectableDraggable").default} SelectableDraggable
*/
class SelectableDraggableTemplate extends Template {
/**
* Returns the html elements rendered from this template.
* @param {SelectableDraggable} element Element of the graph
*/
applyLocation(element) {
element.style.setProperty("--ueb-position-x", element.location[0]);
element.style.setProperty("--ueb-position-y", element.location[1]);
}
/**
* Returns the html elements rendered from this template.
* @param {SelectableDraggable} element Element of the graph
*/
applySelected(element) {
if (element.selected) {
element.classList.add("ueb-selected");
} else {
element.classList.remove("ueb-selected");
}
}
}
/** /**
* @typedef {import("../graph/GraphPin").default} GraphPin * @typedef {import("../graph/GraphPin").default} GraphPin
*/ */
@@ -1563,7 +1565,7 @@ class PinTemplate extends Template {
*/ */
apply(pin) { apply(pin) {
super.apply(pin); super.apply(pin);
pin.classList.add("ueb-node-" + pin.isInput() ? "input" : "output", "ueb-node-value-" + pin.getType()); pin.classList.add("ueb-node-" + pin.isInput() ? "input" : "output", "ueb-node-value-" + sanitizeText(pin.getType()));
} }
} }
@@ -1610,6 +1612,33 @@ class GraphPin extends GraphElement {
customElements.define("ueb-pin", GraphPin); customElements.define("ueb-pin", GraphPin);
/**
* @typedef {import("../graph/SelectableDraggable").default} SelectableDraggable
*/
class SelectableDraggableTemplate extends Template {
/**
* Returns the html elements rendered from this template.
* @param {SelectableDraggable} element Element of the graph
*/
applyLocation(element) {
element.style.setProperty("--ueb-position-x", sanitizeText(element.location[0]));
element.style.setProperty("--ueb-position-y", sanitizeText(element.location[1]));
}
/**
* Returns the html elements rendered from this template.
* @param {SelectableDraggable} element Element of the graph
*/
applySelected(element) {
if (element.selected) {
element.classList.add("ueb-selected");
} else {
element.classList.remove("ueb-selected");
}
}
}
/** /**
* @typedef {import("../graph/GraphNode").default} GraphNode * @typedef {import("../graph/GraphNode").default} GraphNode
*/ */
@@ -1650,7 +1679,7 @@ class NodeTemplate extends SelectableDraggableTemplate {
<div class="ueb-node-header"> <div class="ueb-node-header">
<span class="ueb-node-name"> <span class="ueb-node-name">
<span class="ueb-node-symbol"></span> <span class="ueb-node-symbol"></span>
<span class="ueb-node-text">${node.entity.getNodeDisplayName()}</span> <span class="ueb-node-text">${sanitizeText(node.entity.getNodeDisplayName())}</span>
</span> </span>
</div> </div>
<div class="ueb-node-body"> <div class="ueb-node-body">
@@ -1671,8 +1700,8 @@ class NodeTemplate extends SelectableDraggableTemplate {
if (node.selected) { if (node.selected) {
node.classList.add("ueb-selected"); node.classList.add("ueb-selected");
} }
node.style.setProperty("--ueb-position-x", node.location[0]); node.style.setProperty("--ueb-position-x", sanitizeText(node.location[0]));
node.style.setProperty("--ueb-position-y", node.location[1]); node.style.setProperty("--ueb-position-y", sanitizeText(node.location[1]));
/** @type {HTMLElement} */ /** @type {HTMLElement} */
let inputContainer = node.querySelector(".ueb-node-inputs"); let inputContainer = node.querySelector(".ueb-node-inputs");
/** @type {HTMLElement} */ /** @type {HTMLElement} */
@@ -2478,4 +2507,4 @@ function initializeSerializerFactory() {
initializeSerializerFactory(); initializeSerializerFactory();
export { Blueprint, GraphLink, GraphNode }; export { Blueprint, Configuration, GraphLink, GraphNode };

View File

@@ -14,7 +14,8 @@
<body> <body>
<div>Hello</div> <div>Hello</div>
<script type="module"> <script type="module">
import { Blueprint, GraphNode } from "./dist/ueblueprint.js" import { Blueprint, GraphNode, Configuration } from "./dist/ueblueprint.js"
Configuration.deleteNodesKeyboardKey = "Delete"
let node1 = GraphNode.fromSerializedObject(`Begin Object Class=/Script/BlueprintGraph.K2Node_CommutativeAssociativeBinaryOperator Name="K2Node_CommutativeAssociativeBinaryOperator_1" let node1 = GraphNode.fromSerializedObject(`Begin Object Class=/Script/BlueprintGraph.K2Node_CommutativeAssociativeBinaryOperator Name="K2Node_CommutativeAssociativeBinaryOperator_1"
bIsPureFunc=True bIsPureFunc=True
FunctionReference=(MemberParent=Class'"/Script/Engine.KismetStringLibrary"',MemberName="Concat_StrStr") FunctionReference=(MemberParent=Class'"/Script/Engine.KismetStringLibrary"',MemberName="Concat_StrStr")

4
js/Configuration.js Normal file
View File

@@ -0,0 +1,4 @@
export default class Configuration {
static deleteNodesKeyboardKey = "Delete"
}

8
js/action/Actions.js Normal file
View File

@@ -0,0 +1,8 @@
export default class Action {
apply() {
}
revert() {
}
}

View File

@@ -1,4 +1,5 @@
import Blueprint from "./Blueprint" import Blueprint from "./Blueprint"
import Configuration from "./Configuration"
import GraphLink from "./graph/GraphLink" import GraphLink from "./graph/GraphLink"
import GraphNode from "./graph/GraphNode" import GraphNode from "./graph/GraphNode"
@@ -6,4 +7,4 @@ import initializeSerializerFactory from "./serialization/initializeSerializerFac
initializeSerializerFactory() initializeSerializerFactory()
export { Blueprint as Blueprint, GraphNode as GraphNode, GraphLink as GraphLink } export { Blueprint as Blueprint, GraphNode as GraphNode, GraphLink as GraphLink, Configuration as Configuration }

View File

@@ -1,10 +1,17 @@
import KeyboardShortcut from "./KeyboardShortcut"; import KeyboardShortcut from "./KeyboardShortcut"
import Configuration from "../Configuration"
export default class KeyvoardCanc extends KeyboardShortcut { export default class KeyvoardCanc extends KeyboardShortcut {
/**
*
* @param {HTMLElement} target
* @param {import("../Blueprint").default} blueprint
* @param {OBject} options
*/
constructor(target, blueprint, options = {}) { constructor(target, blueprint, options = {}) {
options.key = "Delete" options = KeyboardShortcut.keyOptionsParse(options, Configuration.deleteNodesKeyboardKey)
super(target, blueprint, options) super(target, blueprint, options)
} }

View File

@@ -27,6 +27,16 @@ export default class KeyboardShortcut extends Context {
} }
} }
/**
*
* @param {String} keyString
* @returns {Object}
*/
static keyOptionsParse(options, keyString) {
options.key = keyString
return options
}
blueprintFocused() { blueprintFocused() {
document.addEventListener("keydown", this.keyDownHandler) document.addEventListener("keydown", this.keyDownHandler)
} }

View File

@@ -5,10 +5,10 @@ import LocalizedTextEntity from "../entity/LocalizedTextEntity"
import ObjectEntity from "../entity/ObjectEntity" import ObjectEntity from "../entity/ObjectEntity"
import ObjectReferenceEntity from "../entity/ObjectReferenceEntity" import ObjectReferenceEntity from "../entity/ObjectReferenceEntity"
import Parsimmon from "parsimmon" import Parsimmon from "parsimmon"
import PathSymbolEntity from "../entity/PathSymbolEntity"
import PinEntity from "../entity/PinEntity" import PinEntity from "../entity/PinEntity"
import PinReferenceEntity from "../entity/PinReferenceEntity" import PinReferenceEntity from "../entity/PinReferenceEntity"
import Utility from "../Utility" import Utility from "../Utility"
import PathSymbolEntity from "../entity/PathSymbolEntity"
let P = Parsimmon let P = Parsimmon

View File

@@ -1,7 +1,7 @@
import GraphSelector from "../graph/GraphSelector"
import html from "./html" import html from "./html"
import sanitizeText from "./sanitizeText" import sanitizeText from "./sanitizeText"
import Template from "./Template" import Template from "./Template"
import GraphSelector from "../graph/GraphSelector"
/** @typedef {import("../Blueprint").default} Blueprint */ /** @typedef {import("../Blueprint").default} Blueprint */
export default class BlueprintTemplate extends Template { export default class BlueprintTemplate extends Template {
@@ -75,7 +75,7 @@ export default class BlueprintTemplate extends Template {
*/ */
applyZoom(blueprint, newZoom) { applyZoom(blueprint, newZoom) {
blueprint.classList.remove(`ueb-zoom-${blueprint.zoom}`) blueprint.classList.remove(`ueb-zoom-${blueprint.zoom}`)
blueprint.classList.add(`ueb-zoom-${newZoom}`) blueprint.classList.add(sanitizeText`ueb-zoom-${newZoom}`)
} }
/** /**
@@ -83,8 +83,8 @@ export default class BlueprintTemplate extends Template {
* @param {Blueprint} brueprint The blueprint element * @param {Blueprint} brueprint The blueprint element
*/ */
applyExpand(blueprint) { applyExpand(blueprint) {
blueprint.gridElement.style.setProperty("--ueb-additional-x", blueprint.additional[0]) blueprint.gridElement.style.setProperty("--ueb-additional-x", sanitizeText(blueprint.additional[0]))
blueprint.gridElement.style.setProperty("--ueb-additional-y", blueprint.additional[1]) blueprint.gridElement.style.setProperty("--ueb-additional-y", sanitizeText(blueprint.additional[1]))
} }
/** /**
@@ -92,7 +92,7 @@ export default class BlueprintTemplate extends Template {
* @param {Blueprint} brueprint The blueprint element * @param {Blueprint} brueprint The blueprint element
*/ */
applyTranlate(blueprint) { applyTranlate(blueprint) {
blueprint.gridElement.style.setProperty("--ueb-translate-x", blueprint.translateValue[0]) blueprint.gridElement.style.setProperty("--ueb-translate-x", sanitizeText(blueprint.translateValue[0]))
blueprint.gridElement.style.setProperty("--ueb-translate-y", blueprint.translateValue[1]) blueprint.gridElement.style.setProperty("--ueb-translate-y", sanitizeText(blueprint.translateValue[1]))
} }
} }

View File

@@ -1,7 +1,8 @@
import GraphPin from "../graph/GraphPin"
import html from "./html" import html from "./html"
import PinEntity from "../entity/PinEntity" import PinEntity from "../entity/PinEntity"
import sanitizeText from "./sanitizeText"
import SelectableDraggableTemplate from "./SelectableDraggableTemplate" import SelectableDraggableTemplate from "./SelectableDraggableTemplate"
import GraphPin from "../graph/GraphPin"
/** /**
* @typedef {import("../graph/GraphNode").default} GraphNode * @typedef {import("../graph/GraphNode").default} GraphNode
@@ -43,7 +44,7 @@ export default class NodeTemplate extends SelectableDraggableTemplate {
<div class="ueb-node-header"> <div class="ueb-node-header">
<span class="ueb-node-name"> <span class="ueb-node-name">
<span class="ueb-node-symbol"></span> <span class="ueb-node-symbol"></span>
<span class="ueb-node-text">${node.entity.getNodeDisplayName()}</span> <span class="ueb-node-text">${sanitizeText(node.entity.getNodeDisplayName())}</span>
</span> </span>
</div> </div>
<div class="ueb-node-body"> <div class="ueb-node-body">
@@ -64,8 +65,8 @@ export default class NodeTemplate extends SelectableDraggableTemplate {
if (node.selected) { if (node.selected) {
node.classList.add("ueb-selected") node.classList.add("ueb-selected")
} }
node.style.setProperty("--ueb-position-x", node.location[0]) node.style.setProperty("--ueb-position-x", sanitizeText(node.location[0]))
node.style.setProperty("--ueb-position-y", node.location[1]) node.style.setProperty("--ueb-position-y", sanitizeText(node.location[1]))
/** @type {HTMLElement} */ /** @type {HTMLElement} */
let inputContainer = node.querySelector(".ueb-node-inputs") let inputContainer = node.querySelector(".ueb-node-inputs")
/** @type {HTMLElement} */ /** @type {HTMLElement} */

View File

@@ -1,4 +1,5 @@
import html from "./html" import html from "./html"
import sanitizeText from "./sanitizeText"
import Template from "./Template" import Template from "./Template"
/** /**
@@ -31,6 +32,6 @@ export default class PinTemplate extends Template {
*/ */
apply(pin) { apply(pin) {
super.apply(pin) super.apply(pin)
pin.classList.add("ueb-node-" + pin.isInput() ? "input" : "output", "ueb-node-value-" + pin.getType()) pin.classList.add("ueb-node-" + pin.isInput() ? "input" : "output", "ueb-node-value-" + sanitizeText(pin.getType()))
} }
} }

View File

@@ -1,4 +1,4 @@
import html from "./html" import sanitizeText from "./sanitizeText"
import Template from "./Template" import Template from "./Template"
/** /**
@@ -11,8 +11,8 @@ export default class SelectableDraggableTemplate extends Template {
* @param {SelectableDraggable} element Element of the graph * @param {SelectableDraggable} element Element of the graph
*/ */
applyLocation(element) { applyLocation(element) {
element.style.setProperty("--ueb-position-x", element.location[0]) element.style.setProperty("--ueb-position-x", sanitizeText(element.location[0]))
element.style.setProperty("--ueb-position-y", element.location[1]) element.style.setProperty("--ueb-position-y", sanitizeText(element.location[1]))
} }
/** /**

View File

@@ -1,3 +1,4 @@
import sanitizeText from "./sanitizeText"
import Template from "./Template" import Template from "./Template"
/** /**
@@ -21,11 +22,11 @@ export default class SelectorTemplate extends Template {
*/ */
applyStartSelecting(selector, initialPosition) { applyStartSelecting(selector, initialPosition) {
// Set initial position // Set initial position
selector.style.setProperty("--ueb-select-from-x", initialPosition[0]) selector.style.setProperty("--ueb-select-from-x", sanitizeText(initialPosition[0]))
selector.style.setProperty("--ueb-select-from-y", initialPosition[1]) selector.style.setProperty("--ueb-select-from-y", sanitizeText(initialPosition[1]))
// Final position coincide with the initial position, at the beginning of selection // Final position coincide with the initial position, at the beginning of selection
selector.style.setProperty("--ueb-select-to-x", initialPosition[0]) selector.style.setProperty("--ueb-select-to-x", sanitizeText(initialPosition[0]))
selector.style.setProperty("--ueb-select-to-y", initialPosition[1]) selector.style.setProperty("--ueb-select-to-y", sanitizeText(initialPosition[1]))
selector.dataset.selecting = "true" selector.dataset.selecting = "true"
} }
@@ -34,8 +35,8 @@ export default class SelectorTemplate extends Template {
* @param {GraphSelector} selector Selector element * @param {GraphSelector} selector Selector element
*/ */
applyDoSelecting(selector, finalPosition) { applyDoSelecting(selector, finalPosition) {
selector.style.setProperty("--ueb-select-to-x", finalPosition[0]) selector.style.setProperty("--ueb-select-to-x", sanitizeText(finalPosition[0]))
selector.style.setProperty("--ueb-select-to-y", finalPosition[1]) selector.style.setProperty("--ueb-select-to-y", sanitizeText(finalPosition[1]))
} }
/** /**

View File

@@ -1,9 +1,17 @@
const div = document.createElement("div") const div = document.createElement("div")
const tagReplacement = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
"'": '&#39;',
'"': '&quot;'
}
function sanitizeText(value) { function sanitizeText(value) {
div.textContent = value if (value.constructor === String) {
value = div.textContent return value.replace(/[&<>'"]/g, tag => tagReplacement[tag])
div.innerHTML = "" }
return value return value
} }