Disallow calling "helperMissing" and "blockHelperMissing" directly

closes #1558
This commit is contained in:
Nils Knappmeier
2019-09-16 23:42:09 +02:00
committed by Nils Knappmeier
parent fff3e40402
commit 2078c727c6
6 changed files with 110 additions and 26 deletions
+24 -10
View File
@@ -311,7 +311,7 @@ JavaScriptCompiler.prototype = {
// replace it on the stack with the result of properly
// invoking blockHelperMissing.
blockValue: function(name) {
let blockHelperMissing = this.aliasable('helpers.blockHelperMissing'),
let blockHelperMissing = this.aliasable('container.hooks.blockHelperMissing'),
params = [this.contextName(0)];
this.setupHelperArgs(name, 0, params);
@@ -329,7 +329,7 @@ JavaScriptCompiler.prototype = {
// On stack, after, if lastHelper: value
ambiguousBlockValue: function() {
// We're being a bit cheeky and reusing the options value from the prior exec
let blockHelperMissing = this.aliasable('helpers.blockHelperMissing'),
let blockHelperMissing = this.aliasable('container.hooks.blockHelperMissing'),
params = [this.contextName(0)];
this.setupHelperArgs('', 0, params, true);
@@ -622,18 +622,32 @@ JavaScriptCompiler.prototype = {
// If the helper is not found, `helperMissing` is called.
invokeHelper: function(paramSize, name, isSimple) {
let nonHelper = this.popStack(),
helper = this.setupHelper(paramSize, name),
simple = isSimple ? [helper.name, ' || '] : '';
helper = this.setupHelper(paramSize, name);
let lookup = ['('].concat(simple, nonHelper);
if (!this.options.strict) {
lookup.push(' || ', this.aliasable('helpers.helperMissing'));
let possibleFunctionCalls = [];
if (isSimple) { // direct call to helper
possibleFunctionCalls.push(helper.name);
}
// call a function from the input object
possibleFunctionCalls.push(nonHelper);
if (!this.options.strict) {
possibleFunctionCalls.push(this.aliasable('container.hooks.helperMissing'));
}
lookup.push(')');
this.push(this.source.functionCall(lookup, 'call', helper.callParams));
let functionLookupCode = ['(', this.itemsSeparatedBy(possibleFunctionCalls, '||'), ')'];
let functionCall = this.source.functionCall(functionLookupCode, 'call', helper.callParams);
this.push(functionCall);
},
itemsSeparatedBy: function(items, separator) {
let result = [];
result.push(items[0]);
for (let i = 1; i < items.length; i++) {
result.push(separator, items[i]);
}
return result;
},
// [invokeKnownHelper]
//
// On stack, before: hash, inverse, program, params..., ...
@@ -673,7 +687,7 @@ JavaScriptCompiler.prototype = {
lookup[0] = '(helper = ';
lookup.push(
' != null ? helper : ',
this.aliasable('helpers.helperMissing')
this.aliasable('container.hooks.helperMissing')
);
}
+9
View File
@@ -15,3 +15,12 @@ export function registerDefaultHelpers(instance) {
registerLookup(instance);
registerWith(instance);
}
export function moveHelperToHooks(instance, helperName, keepHelper) {
if (instance.helpers[helperName]) {
instance.hooks[helperName] = instance.helpers[helperName];
if (!keepHelper) {
delete instance.helpers[helperName];
}
}
}
+19 -16
View File
@@ -1,6 +1,7 @@
import * as Utils from './utils';
import Exception from './exception';
import { COMPILER_REVISION, REVISION_CHANGES, createFrame } from './base';
import {COMPILER_REVISION, createFrame, REVISION_CHANGES} from './base';
import {moveHelperToHooks} from './helpers';
export function checkRevision(compilerInfo) {
const compilerRevision = compilerInfo && compilerInfo[0] || 1,
@@ -21,6 +22,7 @@ export function checkRevision(compilerInfo) {
}
export function template(templateSpec, env) {
/* istanbul ignore next */
if (!env) {
throw new Exception('No environment passed to template');
@@ -42,13 +44,15 @@ export function template(templateSpec, env) {
options.ids[0] = true;
}
}
partial = env.VM.resolvePartial.call(this, partial, context, options);
let result = env.VM.invokePartial.call(this, partial, context, options);
let optionsWithHooks = Utils.extend({}, options, {hooks: this.hooks});
let result = env.VM.invokePartial.call(this, partial, context, optionsWithHooks);
if (result == null && env.compile) {
options.partials[options.name] = env.compile(partial, templateSpec.compilerOptions, env);
result = options.partials[options.name](context, options);
result = options.partials[options.name](context, optionsWithHooks);
}
if (result != null) {
if (options.indent) {
@@ -115,15 +119,6 @@ export function template(templateSpec, env) {
}
return value;
},
merge: function(param, common) {
let obj = param || common;
if (param && common && (param !== common)) {
obj = Utils.extend({}, common, param);
}
return obj;
},
// An empty object to use as replacement for null-contexts
nullContext: Object.seal({}),
@@ -158,19 +153,27 @@ export function template(templateSpec, env) {
ret._setup = function(options) {
if (!options.partial) {
container.helpers = container.merge(options.helpers, env.helpers);
container.helpers = Utils.extend({}, env.helpers, options.helpers);
if (templateSpec.usePartial) {
container.partials = container.merge(options.partials, env.partials);
container.partials = Utils.extend({}, env.partials, options.partials);
}
if (templateSpec.usePartial || templateSpec.useDecorators) {
container.decorators = container.merge(options.decorators, env.decorators);
container.decorators = Utils.extend({}, env.decorators, options.decorators);
}
container.hooks = {};
let keepHelper = options.allowCallsToHelperMissing;
moveHelperToHooks(container, 'helperMissing', keepHelper);
moveHelperToHooks(container, 'blockHelperMissing', keepHelper);
} else {
container.helpers = options.helpers;
container.partials = options.partials;
container.decorators = options.decorators;
container.hooks = options.hooks;
}
};
ret._child = function(i, data, blockParams, depths) {
+1
View File
@@ -1,3 +1,4 @@
const escape = {
'&': '&amp;',
'<': '&lt;',
+56
View File
@@ -32,4 +32,60 @@ describe('security issues', function() {
});
});
describe('GH-xxxx: Prevent explicit call of helperMissing-helpers', function() {
if (!Handlebars.compile) {
return;
}
describe('without the option "allowExplicitCallOfHelperMissing"', function() {
it('should throw an exception when calling "{{helperMissing}}" ', function() {
shouldThrow(function() {
var template = Handlebars.compile('{{helperMissing}}');
template({});
}, Error);
});
it('should throw an exception when calling "{{#helperMissing}}{{/helperMissing}}" ', function() {
shouldThrow(function() {
var template = Handlebars.compile('{{#helperMissing}}{{/helperMissing}}');
template({});
}, Error);
});
it('should throw an exception when calling "{{blockHelperMissing "abc" .}}" ', function() {
var functionCalls = [];
shouldThrow(function() {
var template = Handlebars.compile('{{blockHelperMissing "abc" .}}');
template({ fn: function() { functionCalls.push('called'); }});
}, Error);
equals(functionCalls.length, 0);
});
it('should throw an exception when calling "{{#blockHelperMissing .}}{{/blockHelperMissing}}"', function() {
shouldThrow(function() {
var template = Handlebars.compile('{{#blockHelperMissing .}}{{/blockHelperMissing}}');
template({ fn: function() { return 'functionInData';}});
}, Error);
});
});
describe('with the option "allowCallsToHelperMissing" set to true', function() {
it('should not throw an exception when calling "{{helperMissing}}" ', function() {
var template = Handlebars.compile('{{helperMissing}}');
template({}, {allowCallsToHelperMissing: true});
});
it('should not throw an exception when calling "{{#helperMissing}}{{/helperMissing}}" ', function() {
var template = Handlebars.compile('{{#helperMissing}}{{/helperMissing}}');
template({}, {allowCallsToHelperMissing: true});
});
it('should not throw an exception when calling "{{blockHelperMissing "abc" .}}" ', function() {
var functionCalls = [];
var template = Handlebars.compile('{{blockHelperMissing "abc" .}}');
template({ fn: function() { functionCalls.push('called'); }}, {allowCallsToHelperMissing: true});
equals(functionCalls.length, 1);
});
it('should not throw an exception when calling "{{#blockHelperMissing .}}{{/blockHelperMissing}}"', function() {
var template = Handlebars.compile('{{#blockHelperMissing true}}sdads{{/blockHelperMissing}}');
template({}, {allowCallsToHelperMissing: true});
});
});
});
});
+1
View File
@@ -29,6 +29,7 @@ declare namespace Handlebars {
decorators?: { [name: string]: Function };
data?: any;
blockParams?: any[];
allowCallsToHelperMissing: boolean;
}
export interface HelperOptions {