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 // replace it on the stack with the result of properly
// invoking blockHelperMissing. // invoking blockHelperMissing.
blockValue: function(name) { blockValue: function(name) {
let blockHelperMissing = this.aliasable('helpers.blockHelperMissing'), let blockHelperMissing = this.aliasable('container.hooks.blockHelperMissing'),
params = [this.contextName(0)]; params = [this.contextName(0)];
this.setupHelperArgs(name, 0, params); this.setupHelperArgs(name, 0, params);
@@ -329,7 +329,7 @@ JavaScriptCompiler.prototype = {
// On stack, after, if lastHelper: value // On stack, after, if lastHelper: value
ambiguousBlockValue: function() { ambiguousBlockValue: function() {
// We're being a bit cheeky and reusing the options value from the prior exec // 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)]; params = [this.contextName(0)];
this.setupHelperArgs('', 0, params, true); this.setupHelperArgs('', 0, params, true);
@@ -622,18 +622,32 @@ JavaScriptCompiler.prototype = {
// If the helper is not found, `helperMissing` is called. // If the helper is not found, `helperMissing` is called.
invokeHelper: function(paramSize, name, isSimple) { invokeHelper: function(paramSize, name, isSimple) {
let nonHelper = this.popStack(), let nonHelper = this.popStack(),
helper = this.setupHelper(paramSize, name), helper = this.setupHelper(paramSize, name);
simple = isSimple ? [helper.name, ' || '] : '';
let lookup = ['('].concat(simple, nonHelper); let possibleFunctionCalls = [];
if (!this.options.strict) {
lookup.push(' || ', this.aliasable('helpers.helperMissing')); 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] // [invokeKnownHelper]
// //
// On stack, before: hash, inverse, program, params..., ... // On stack, before: hash, inverse, program, params..., ...
@@ -673,7 +687,7 @@ JavaScriptCompiler.prototype = {
lookup[0] = '(helper = '; lookup[0] = '(helper = ';
lookup.push( lookup.push(
' != null ? helper : ', ' != null ? helper : ',
this.aliasable('helpers.helperMissing') this.aliasable('container.hooks.helperMissing')
); );
} }
+9
View File
@@ -15,3 +15,12 @@ export function registerDefaultHelpers(instance) {
registerLookup(instance); registerLookup(instance);
registerWith(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 * as Utils from './utils';
import Exception from './exception'; 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) { export function checkRevision(compilerInfo) {
const compilerRevision = compilerInfo && compilerInfo[0] || 1, const compilerRevision = compilerInfo && compilerInfo[0] || 1,
@@ -21,6 +22,7 @@ export function checkRevision(compilerInfo) {
} }
export function template(templateSpec, env) { export function template(templateSpec, env) {
/* istanbul ignore next */ /* istanbul ignore next */
if (!env) { if (!env) {
throw new Exception('No environment passed to template'); throw new Exception('No environment passed to template');
@@ -42,13 +44,15 @@ export function template(templateSpec, env) {
options.ids[0] = true; options.ids[0] = true;
} }
} }
partial = env.VM.resolvePartial.call(this, partial, context, options); 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) { if (result == null && env.compile) {
options.partials[options.name] = env.compile(partial, templateSpec.compilerOptions, env); 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 (result != null) {
if (options.indent) { if (options.indent) {
@@ -115,15 +119,6 @@ export function template(templateSpec, env) {
} }
return value; 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 // An empty object to use as replacement for null-contexts
nullContext: Object.seal({}), nullContext: Object.seal({}),
@@ -158,19 +153,27 @@ export function template(templateSpec, env) {
ret._setup = function(options) { ret._setup = function(options) {
if (!options.partial) { if (!options.partial) {
container.helpers = container.merge(options.helpers, env.helpers); container.helpers = Utils.extend({}, env.helpers, options.helpers);
if (templateSpec.usePartial) { if (templateSpec.usePartial) {
container.partials = container.merge(options.partials, env.partials); container.partials = Utils.extend({}, env.partials, options.partials);
} }
if (templateSpec.usePartial || templateSpec.useDecorators) { 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 { } else {
container.helpers = options.helpers; container.helpers = options.helpers;
container.partials = options.partials; container.partials = options.partials;
container.decorators = options.decorators; container.decorators = options.decorators;
container.hooks = options.hooks;
} }
}; };
ret._child = function(i, data, blockParams, depths) { ret._child = function(i, data, blockParams, depths) {
+1
View File
@@ -1,3 +1,4 @@
const escape = { const escape = {
'&': '&amp;', '&': '&amp;',
'<': '&lt;', '<': '&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 }; decorators?: { [name: string]: Function };
data?: any; data?: any;
blockParams?: any[]; blockParams?: any[];
allowCallsToHelperMissing: boolean;
} }
export interface HelperOptions { export interface HelperOptions {