config that disallows eval in htmx implementation

addressed https://github.com/bigskysoftware/htmx/issues/305
This commit is contained in:
carson 2021-01-04 10:36:56 -07:00
parent 3c7dde7e1f
commit 58f373d1e5
5 changed files with 42 additions and 8 deletions

View File

@ -42,6 +42,7 @@ return (function () {
requestClass:'htmx-request', requestClass:'htmx-request',
settlingClass:'htmx-settling', settlingClass:'htmx-settling',
swappingClass:'htmx-swapping', swappingClass:'htmx-swapping',
allowEval:true,
attributesToSettle:["class", "style", "width", "height"] attributesToSettle:["class", "style", "width", "height"]
}, },
parseInterval:parseInterval, parseInterval:parseInterval,
@ -248,7 +249,9 @@ return (function () {
//========================================================================================== //==========================================================================================
function internalEval(str){ function internalEval(str){
return maybeEval(getDocument().body, function () {
return eval(str); return eval(str);
});
} }
function onLoadHelper(callback) { function onLoadHelper(callback) {
@ -735,7 +738,7 @@ return (function () {
last !== "."; last !== ".";
} }
function maybeGenerateConditional(tokens, paramName) { function maybeGenerateConditional(elt, tokens, paramName) {
if (tokens[0] === '[') { if (tokens[0] === '[') {
tokens.shift(); tokens.shift();
var bracketCount = 1; var bracketCount = 1;
@ -752,7 +755,10 @@ return (function () {
tokens.shift(); tokens.shift();
conditionalSource += ")})"; conditionalSource += ")})";
try { try {
var conditionFunction = Function(conditionalSource)(); var conditionFunction = maybeEval(elt,function () {
return Function(conditionalSource)();
},
function(){return true})
conditionFunction.source = conditionalSource; conditionFunction.source = conditionalSource;
return conditionFunction; return conditionFunction;
} catch (e) { } catch (e) {
@ -800,7 +806,7 @@ return (function () {
triggerSpecs.push({trigger: 'sse', sseEvent: trigger.substr(4)}); triggerSpecs.push({trigger: 'sse', sseEvent: trigger.substr(4)});
} else { } else {
var triggerSpec = {trigger: trigger}; var triggerSpec = {trigger: trigger};
var eventFilter = maybeGenerateConditional(tokens, "event"); var eventFilter = maybeGenerateConditional(elt, tokens, "event");
if (eventFilter) { if (eventFilter) {
triggerSpec.eventFilter = eventFilter; triggerSpec.eventFilter = eventFilter;
} }
@ -1215,7 +1221,9 @@ return (function () {
function evalScript(script) { function evalScript(script) {
if (script.type === "text/javascript" || script.type === "") { if (script.type === "text/javascript" || script.type === "") {
try { try {
Function(script.innerText)(); maybeEval(script, function () {
Function(script.innerText)()
});
} catch (e) { } catch (e) {
logError(e); logError(e);
} }
@ -1803,9 +1811,18 @@ return (function () {
return getValuesForElement(parentElt(elt), attr, strToValues, expressionVars); return getValuesForElement(parentElt(elt), attr, strToValues, expressionVars);
} }
function maybeEval(elt, toEval, defaultVal) {
if (htmx.config.allowEval) {
return toEval();
} else {
triggerErrorEvent(elt, 'htmx:evalDisallowedError');
return defaultVal;
}
}
function getHXVarsForElement(elt, expressionVars) { function getHXVarsForElement(elt, expressionVars) {
return getValuesForElement(elt, "hx-vars", function(valueStr){ return getValuesForElement(elt, "hx-vars", function(valueStr){
return Function("return (" + valueStr + ")")() return maybeEval(elt,function () {return Function("return (" + valueStr + ")")();}, {});
}, expressionVars); }, expressionVars);
} }

View File

@ -246,5 +246,20 @@ describe("Core htmx API test", function(){
} }
}); });
it('eval can be suppressed', function () {
var calledEvent = false;
var handler = htmx.on("htmx:evalDisallowedError", function(){
calledEvent = true;
});
try {
htmx.config.allowEval = false;
should.equal(htmx._("tokenizeString"), undefined);
} finally {
htmx.config.allowEval = true;
htmx.off("htmx:evalDisallowedError", handler);
}
calledEvent.should.equal(true);
});
})
})

View File

@ -34,7 +34,7 @@ describe("Core htmx tokenizer tests", function(){
it('generates conditionals property', function() it('generates conditionals property', function()
{ {
var tokens = tokenize("[code==4||(code==5&&foo==true)]"); var tokens = tokenize("[code==4||(code==5&&foo==true)]");
var conditional = htmx._("maybeGenerateConditional")(tokens); var conditional = htmx._("maybeGenerateConditional")(null, tokens);
var func = eval(conditional); var func = eval(conditional);
func({code: 5, foo: true}).should.equal(true); func({code: 5, foo: true}).should.equal(true);
func({code: 5, foo: false}).should.equal(false); func({code: 5, foo: false}).should.equal(false);

View File

@ -95,6 +95,7 @@ Note that using a [meta tag](/docs/#config) is the preferred mechanism for setti
* `requestClass:'htmx-request'` - string: the class to place on triggering elements when a request is in flight * `requestClass:'htmx-request'` - string: the class to place on triggering elements when a request is in flight
* `settlingClass:'htmx-settling'` - string: the class to place on target elements when htmx is in the settling phase * `settlingClass:'htmx-settling'` - string: the class to place on target elements when htmx is in the settling phase
* `swappingClass:'htmx-swapping'` - string: the class to place on target elements when htmx is in the swapping phase * `swappingClass:'htmx-swapping'` - string: the class to place on target elements when htmx is in the swapping phase
* `allowEval:true` - boolean: allows the use of eval-like functionality in htmx, to enable `hx-vars`, trigger conditions & script tag evaluation. Can be set to `false` for CSP compatibility
##### Example ##### Example

View File

@ -817,6 +817,7 @@ Htmx allows you to configure a few defaults:
| `htmx.config.requestClass` | defaults to `htmx-request` | `htmx.config.requestClass` | defaults to `htmx-request`
| `htmx.config.settlingClass` | defaults to `htmx-settling` | `htmx.config.settlingClass` | defaults to `htmx-settling`
| `htmx.config.swappingClass` | defaults to `htmx-swapping` | `htmx.config.swappingClass` | defaults to `htmx-swapping`
| `htmx.config.allowEval` | defaults to `true`
</div> </div>