feat: default options for controlling proto access
This commmit adds the runtime options - `allowProtoPropertiesByDefault` (boolean, default: false) and - `allowProtoMethodsByDefault` (boolean, default: false)` which can be used to allow access to prototype properties and functions in general. Specific properties and methods can still be disabled from access via `allowedProtoProperties` and `allowedProtoMethods` by setting the corresponding values to false. The methods `constructor`, `__defineGetter__`, `__defineSetter__`, `__lookupGetter__` and the property `__proto__` will be disabled, even if the allow...ByDefault-options are set to true. In order to allow access to those properties and methods, they have to be explicitly set to true in the 'allowedProto...'-options. A warning is logged when the a proto-access it attempted and denied by default (i.e. if no option is set by the user to make the access decision explicit)
This commit is contained in:
committed by
Nils Knappmeier
parent
91a1b5d2f4
commit
7af1c12db6
@@ -0,0 +1,55 @@
|
|||||||
|
import { createNewLookupObject } from './create-new-lookup-object';
|
||||||
|
|
||||||
|
export function createProtoAccessControl(runtimeOptions) {
|
||||||
|
let defaultMethodWhiteList = Object.create(null);
|
||||||
|
defaultMethodWhiteList['constructor'] = false;
|
||||||
|
defaultMethodWhiteList['__defineGetter__'] = false;
|
||||||
|
defaultMethodWhiteList['__defineSetter__'] = false;
|
||||||
|
defaultMethodWhiteList['__lookupGetter__'] = false;
|
||||||
|
|
||||||
|
let defaultPropertyWhiteList = Object.create(null);
|
||||||
|
// eslint-disable-next-line no-proto
|
||||||
|
defaultPropertyWhiteList['__proto__'] = false;
|
||||||
|
|
||||||
|
return {
|
||||||
|
properties: {
|
||||||
|
whitelist: createNewLookupObject(
|
||||||
|
defaultPropertyWhiteList,
|
||||||
|
runtimeOptions.allowedProtoProperties
|
||||||
|
),
|
||||||
|
defaultValue: runtimeOptions.allowProtoPropertiesByDefault
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
whitelist: createNewLookupObject(
|
||||||
|
defaultMethodWhiteList,
|
||||||
|
runtimeOptions.allowedProtoMethods
|
||||||
|
),
|
||||||
|
defaultValue: runtimeOptions.allowProtoMethodsByDefault
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resultIsAllowed(result, protoAccessControl, propertyName) {
|
||||||
|
if (typeof result === 'function') {
|
||||||
|
return checkWhiteList(protoAccessControl.methods, propertyName);
|
||||||
|
} else {
|
||||||
|
return checkWhiteList(protoAccessControl.properties, propertyName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkWhiteList(protoAccessControlForType, propertyName) {
|
||||||
|
if (protoAccessControlForType.whitelist[propertyName] !== undefined) {
|
||||||
|
return protoAccessControlForType.whitelist[propertyName] === true;
|
||||||
|
}
|
||||||
|
if (protoAccessControlForType.defaultValue !== undefined) {
|
||||||
|
return protoAccessControlForType.defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error(
|
||||||
|
`Handlebars: Access has been denied to resolve the property "${propertyName}" because it is not an "own property" of its parent.\n` +
|
||||||
|
`You can add a runtime option to disable the check or this warning:\n` +
|
||||||
|
`See http://localhost:8080/api-reference/runtime-options.html#options-to-control-prototype-access for details`
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
+13
-16
@@ -8,7 +8,10 @@ import {
|
|||||||
} from './base';
|
} from './base';
|
||||||
import { moveHelperToHooks } from './helpers';
|
import { moveHelperToHooks } from './helpers';
|
||||||
import { wrapHelper } from './internal/wrapHelper';
|
import { wrapHelper } from './internal/wrapHelper';
|
||||||
import { createNewLookupObject } from './internal/createNewLookupObject';
|
import {
|
||||||
|
createProtoAccessControl,
|
||||||
|
resultIsAllowed
|
||||||
|
} from './internal/proto-access';
|
||||||
|
|
||||||
export function checkRevision(compilerInfo) {
|
export function checkRevision(compilerInfo) {
|
||||||
const compilerRevision = (compilerInfo && compilerInfo[0]) || 1,
|
const compilerRevision = (compilerInfo && compilerInfo[0]) || 1,
|
||||||
@@ -73,8 +76,7 @@ export function template(templateSpec, env) {
|
|||||||
|
|
||||||
let extendedOptions = Utils.extend({}, options, {
|
let extendedOptions = Utils.extend({}, options, {
|
||||||
hooks: this.hooks,
|
hooks: this.hooks,
|
||||||
allowedProtoMethods: this.allowedProtoMethods,
|
protoAccessControl: this.protoAccessControl
|
||||||
allowedProtoProperties: this.allowedProtoProperties
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let result = env.VM.invokePartial.call(
|
let result = env.VM.invokePartial.call(
|
||||||
@@ -126,15 +128,14 @@ export function template(templateSpec, env) {
|
|||||||
},
|
},
|
||||||
lookupProperty: function(parent, propertyName) {
|
lookupProperty: function(parent, propertyName) {
|
||||||
let result = parent[propertyName];
|
let result = parent[propertyName];
|
||||||
|
if (result == null) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
|
if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
const whitelist =
|
|
||||||
typeof result === 'function'
|
|
||||||
? container.allowedProtoMethods
|
|
||||||
: container.allowedProtoProperties;
|
|
||||||
|
|
||||||
if (whitelist[propertyName] === true) {
|
if (resultIsAllowed(result, container.protoAccessControl, propertyName)) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
@@ -237,6 +238,7 @@ export function template(templateSpec, env) {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
main = executeDecorators(
|
main = executeDecorators(
|
||||||
templateSpec.main,
|
templateSpec.main,
|
||||||
main,
|
main,
|
||||||
@@ -247,6 +249,7 @@ export function template(templateSpec, env) {
|
|||||||
);
|
);
|
||||||
return main(context, options);
|
return main(context, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
ret.isTop = true;
|
ret.isTop = true;
|
||||||
|
|
||||||
ret._setup = function(options) {
|
ret._setup = function(options) {
|
||||||
@@ -271,12 +274,7 @@ export function template(templateSpec, env) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
container.hooks = {};
|
container.hooks = {};
|
||||||
container.allowedProtoProperties = createNewLookupObject(
|
container.protoAccessControl = createProtoAccessControl(options);
|
||||||
options.allowedProtoProperties
|
|
||||||
);
|
|
||||||
container.allowedProtoMethods = createNewLookupObject(
|
|
||||||
options.allowedProtoMethods
|
|
||||||
);
|
|
||||||
|
|
||||||
let keepHelperInHelpers =
|
let keepHelperInHelpers =
|
||||||
options.allowCallsToHelperMissing ||
|
options.allowCallsToHelperMissing ||
|
||||||
@@ -284,8 +282,7 @@ export function template(templateSpec, env) {
|
|||||||
moveHelperToHooks(container, 'helperMissing', keepHelperInHelpers);
|
moveHelperToHooks(container, 'helperMissing', keepHelperInHelpers);
|
||||||
moveHelperToHooks(container, 'blockHelperMissing', keepHelperInHelpers);
|
moveHelperToHooks(container, 'blockHelperMissing', keepHelperInHelpers);
|
||||||
} else {
|
} else {
|
||||||
container.allowedProtoProperties = options.allowedProtoProperties;
|
container.protoAccessControl = options.protoAccessControl; // internal option
|
||||||
container.allowedProtoMethods = options.allowedProtoMethods;
|
|
||||||
container.helpers = options.helpers;
|
container.helpers = options.helpers;
|
||||||
container.partials = options.partials;
|
container.partials = options.partials;
|
||||||
container.decorators = options.decorators;
|
container.decorators = options.decorators;
|
||||||
|
|||||||
+186
-111
@@ -149,176 +149,251 @@ describe('security issues', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('GH-1595', function() {
|
describe('GH-1595: dangerous properties', function() {
|
||||||
it('properties, that are required to be own properties', function() {
|
var templates = [
|
||||||
expectTemplate('{{constructor}}')
|
'{{constructor}}',
|
||||||
.withInput({})
|
'{{__defineGetter__}}',
|
||||||
.toCompileTo('');
|
'{{__defineSetter__}}',
|
||||||
|
'{{__lookupGetter__}}',
|
||||||
|
'{{__proto__}}',
|
||||||
|
'{{lookup this "constructor"}}',
|
||||||
|
'{{lookup this "__defineGetter__"}}',
|
||||||
|
'{{lookup this "__defineSetter__"}}',
|
||||||
|
'{{lookup this "__lookupGetter__"}}',
|
||||||
|
'{{lookup this "__proto__"}}'
|
||||||
|
];
|
||||||
|
|
||||||
expectTemplate('{{__defineGetter__}}')
|
templates.forEach(function(template) {
|
||||||
.withInput({})
|
describe('access should be denied to ' + template, function() {
|
||||||
.toCompileTo('');
|
it('by default', function() {
|
||||||
|
expectTemplate(template)
|
||||||
|
.withInput({})
|
||||||
|
.toCompileTo('');
|
||||||
|
});
|
||||||
|
it(' with proto-access enabled', function() {
|
||||||
|
expectTemplate(template)
|
||||||
|
.withInput({})
|
||||||
|
.withRuntimeOptions({
|
||||||
|
allowProtoPropertiesByDefault: true,
|
||||||
|
allowProtoMethodsByDefault: true
|
||||||
|
})
|
||||||
|
.toCompileTo('');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('GH-1631: disallow access to prototype functions', function() {
|
||||||
|
function TestClass() {}
|
||||||
|
|
||||||
expectTemplate('{{__defineSetter__}}')
|
TestClass.prototype.aProperty = 'propertyValue';
|
||||||
.withInput({})
|
TestClass.prototype.aMethod = function() {
|
||||||
.toCompileTo('');
|
return 'returnValue';
|
||||||
|
};
|
||||||
|
|
||||||
expectTemplate('{{__lookupGetter__}}')
|
afterEach(function() {
|
||||||
.withInput({})
|
sinon.restore();
|
||||||
.toCompileTo('');
|
|
||||||
|
|
||||||
expectTemplate('{{__proto__}}')
|
|
||||||
.withInput({})
|
|
||||||
.toCompileTo('');
|
|
||||||
|
|
||||||
expectTemplate('{{lookup this "constructor"}}')
|
|
||||||
.withInput({})
|
|
||||||
.toCompileTo('');
|
|
||||||
|
|
||||||
expectTemplate('{{lookup this "__defineGetter__"}}')
|
|
||||||
.withInput({})
|
|
||||||
.toCompileTo('');
|
|
||||||
|
|
||||||
expectTemplate('{{lookup this "__defineSetter__"}}')
|
|
||||||
.withInput({})
|
|
||||||
.toCompileTo('');
|
|
||||||
|
|
||||||
expectTemplate('{{lookup this "__lookupGetter__"}}')
|
|
||||||
.withInput({})
|
|
||||||
.toCompileTo('');
|
|
||||||
|
|
||||||
expectTemplate('{{lookup this "__proto__"}}')
|
|
||||||
.withInput({})
|
|
||||||
.toCompileTo('');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('GH-1631: disallow access to prototype functions', function() {
|
describe('control access to prototype methods via "allowedProtoMethods"', function() {
|
||||||
function TestClass() {}
|
checkProtoMethodAccess({});
|
||||||
|
|
||||||
TestClass.prototype.aProperty = 'propertyValue';
|
describe('in compat mode', function() {
|
||||||
TestClass.prototype.aMethod = function() {
|
checkProtoMethodAccess({ compat: true });
|
||||||
return 'returnValue';
|
});
|
||||||
};
|
|
||||||
|
function checkProtoMethodAccess(compileOptions) {
|
||||||
|
it('should be prohibited by default and log a warning', function() {
|
||||||
|
var spy = sinon.spy(console, 'error');
|
||||||
|
|
||||||
describe('control access to prototype methods via "allowedProtoMethods"', function() {
|
|
||||||
it('should be prohibited by default', function() {
|
|
||||||
expectTemplate('{{aMethod}}')
|
expectTemplate('{{aMethod}}')
|
||||||
.withInput(new TestClass())
|
.withInput(new TestClass())
|
||||||
|
.withCompileOptions(compileOptions)
|
||||||
.toCompileTo('');
|
.toCompileTo('');
|
||||||
|
|
||||||
|
expect(spy.calledOnce).to.be.true();
|
||||||
|
expect(spy.args[0][0]).to.match(/Handlebars: Access has been denied/);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can be allowed', function() {
|
it('can be allowed, which disables the warning', function() {
|
||||||
|
var spy = sinon.spy(console, 'error');
|
||||||
|
|
||||||
expectTemplate('{{aMethod}}')
|
expectTemplate('{{aMethod}}')
|
||||||
.withInput(new TestClass())
|
.withInput(new TestClass())
|
||||||
|
.withCompileOptions(compileOptions)
|
||||||
.withRuntimeOptions({
|
.withRuntimeOptions({
|
||||||
allowedProtoMethods: {
|
allowedProtoMethods: {
|
||||||
aMethod: true
|
aMethod: true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.toCompileTo('returnValue');
|
.toCompileTo('returnValue');
|
||||||
|
|
||||||
|
expect(spy.callCount).to.equal(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be prohibited by default (in "compat" mode)', function() {
|
it('can be turned on by default, which disables the warning', function() {
|
||||||
expectTemplate('{{aMethod}}')
|
var spy = sinon.spy(console, 'error');
|
||||||
.withInput(new TestClass())
|
|
||||||
.withCompileOptions({ compat: true })
|
|
||||||
.toCompileTo('');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('can be allowed (in "compat" mode)', function() {
|
|
||||||
expectTemplate('{{aMethod}}')
|
expectTemplate('{{aMethod}}')
|
||||||
.withInput(new TestClass())
|
.withInput(new TestClass())
|
||||||
.withCompileOptions({ compat: true })
|
.withCompileOptions(compileOptions)
|
||||||
.withRuntimeOptions({
|
.withRuntimeOptions({
|
||||||
allowedProtoMethods: {
|
allowProtoMethodsByDefault: true
|
||||||
aMethod: true
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.toCompileTo('returnValue');
|
.toCompileTo('returnValue');
|
||||||
|
|
||||||
|
expect(spy.callCount).to.equal(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should cause the recursive lookup by default (in "compat" mode)', function() {
|
it('can be turned off by default, which disables the warning', function() {
|
||||||
expectTemplate('{{#aString}}{{trim}}{{/aString}}')
|
var spy = sinon.spy(console, 'error');
|
||||||
.withInput({ aString: ' abc ', trim: 'trim' })
|
|
||||||
.withCompileOptions({ compat: true })
|
|
||||||
.toCompileTo('trim');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not cause the recursive lookup if allowed through options(in "compat" mode)', function() {
|
expectTemplate('{{aMethod}}')
|
||||||
expectTemplate('{{#aString}}{{trim}}{{/aString}}')
|
.withInput(new TestClass())
|
||||||
.withInput({ aString: ' abc ', trim: 'trim' })
|
.withCompileOptions(compileOptions)
|
||||||
.withCompileOptions({ compat: true })
|
|
||||||
.withRuntimeOptions({
|
.withRuntimeOptions({
|
||||||
|
allowProtoMethodsByDefault: false
|
||||||
|
})
|
||||||
|
.toCompileTo('');
|
||||||
|
|
||||||
|
expect(spy.callCount).to.equal(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can be turned off, if turned on by default', function() {
|
||||||
|
expectTemplate('{{aMethod}}')
|
||||||
|
.withInput(new TestClass())
|
||||||
|
.withCompileOptions(compileOptions)
|
||||||
|
.withRuntimeOptions({
|
||||||
|
allowProtoMethodsByDefault: true,
|
||||||
allowedProtoMethods: {
|
allowedProtoMethods: {
|
||||||
trim: true
|
aMethod: false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.toCompileTo('abc');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('control access to prototype non-methods via "allowedProtoProperties"', function() {
|
|
||||||
it('should be prohibited by default', function() {
|
|
||||||
expectTemplate('{{aProperty}}')
|
|
||||||
.withInput(new TestClass())
|
|
||||||
.toCompileTo('');
|
.toCompileTo('');
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
it('should cause the recursive lookup by default (in "compat" mode)', function() {
|
||||||
|
expectTemplate('{{#aString}}{{trim}}{{/aString}}')
|
||||||
|
.withInput({ aString: ' abc ', trim: 'trim' })
|
||||||
|
.withCompileOptions({ compat: true })
|
||||||
|
.toCompileTo('trim');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not cause the recursive lookup if allowed through options(in "compat" mode)', function() {
|
||||||
|
expectTemplate('{{#aString}}{{trim}}{{/aString}}')
|
||||||
|
.withInput({ aString: ' abc ', trim: 'trim' })
|
||||||
|
.withCompileOptions({ compat: true })
|
||||||
|
.withRuntimeOptions({
|
||||||
|
allowedProtoMethods: {
|
||||||
|
trim: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.toCompileTo('abc');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('control access to prototype non-methods via "allowedProtoProperties" and "allowProtoPropertiesByDefault', function() {
|
||||||
|
checkProtoPropertyAccess({});
|
||||||
|
|
||||||
|
describe('in compat-mode', function() {
|
||||||
|
checkProtoPropertyAccess({ compat: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
function checkProtoPropertyAccess(compileOptions) {
|
||||||
|
it('should be prohibited by default and log a warning', function() {
|
||||||
|
var spy = sinon.spy(console, 'error');
|
||||||
|
|
||||||
it('can be turned on', function() {
|
|
||||||
expectTemplate('{{aProperty}}')
|
expectTemplate('{{aProperty}}')
|
||||||
.withInput(new TestClass())
|
.withInput(new TestClass())
|
||||||
|
.withCompileOptions(compileOptions)
|
||||||
|
.toCompileTo('');
|
||||||
|
|
||||||
|
expect(spy.calledOnce).to.be.true();
|
||||||
|
expect(spy.args[0][0]).to.match(/Handlebars: Access has been denied/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can be explicitly prohibited by default, which disables the warning', function() {
|
||||||
|
var spy = sinon.spy(console, 'error');
|
||||||
|
|
||||||
|
expectTemplate('{{aProperty}}')
|
||||||
|
.withInput(new TestClass())
|
||||||
|
.withCompileOptions(compileOptions)
|
||||||
|
.withRuntimeOptions({
|
||||||
|
allowProtoPropertiesByDefault: false
|
||||||
|
})
|
||||||
|
.toCompileTo('');
|
||||||
|
|
||||||
|
expect(spy.callCount).to.equal(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can be turned on, which disables the warning', function() {
|
||||||
|
var spy = sinon.spy(console, 'error');
|
||||||
|
|
||||||
|
expectTemplate('{{aProperty}}')
|
||||||
|
.withInput(new TestClass())
|
||||||
|
.withCompileOptions(compileOptions)
|
||||||
.withRuntimeOptions({
|
.withRuntimeOptions({
|
||||||
allowedProtoProperties: {
|
allowedProtoProperties: {
|
||||||
aProperty: true
|
aProperty: true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.toCompileTo('propertyValue');
|
.toCompileTo('propertyValue');
|
||||||
|
|
||||||
|
expect(spy.callCount).to.equal(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be prohibited by default (in "compat" mode)', function() {
|
it('can be turned on by default, which disables the warning', function() {
|
||||||
expectTemplate('{{aProperty}}')
|
var spy = sinon.spy(console, 'error');
|
||||||
.withInput(new TestClass())
|
|
||||||
.withCompileOptions({ compat: true })
|
|
||||||
.toCompileTo('');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('can be turned on (in "compat" mode)', function() {
|
|
||||||
expectTemplate('{{aProperty}}')
|
expectTemplate('{{aProperty}}')
|
||||||
.withInput(new TestClass())
|
.withInput(new TestClass())
|
||||||
.withCompileOptions({ compat: true })
|
.withCompileOptions(compileOptions)
|
||||||
.withRuntimeOptions({
|
.withRuntimeOptions({
|
||||||
allowedProtoProperties: {
|
allowProtoPropertiesByDefault: true
|
||||||
aProperty: true
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.toCompileTo('propertyValue');
|
.toCompileTo('propertyValue');
|
||||||
|
|
||||||
|
expect(spy.callCount).to.equal(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can be turned off, if turned on by default', function() {
|
||||||
|
expectTemplate('{{aProperty}}')
|
||||||
|
.withInput(new TestClass())
|
||||||
|
.withCompileOptions(compileOptions)
|
||||||
|
.withRuntimeOptions({
|
||||||
|
allowProtoPropertiesByDefault: true,
|
||||||
|
allowedProtoProperties: {
|
||||||
|
aProperty: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.toCompileTo('');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('compatibility with old runtimes, that do not provide the function "container.lookupProperty"', function() {
|
||||||
|
beforeEach(function simulateRuntimeWithoutLookupProperty() {
|
||||||
|
var oldTemplateMethod = handlebarsEnv.template;
|
||||||
|
sinon.replace(handlebarsEnv, 'template', function(templateSpec) {
|
||||||
|
templateSpec.main = wrapToAdjustContainer(templateSpec.main);
|
||||||
|
return oldTemplateMethod.call(this, templateSpec);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('compatibility with old runtimes, that do not provide the function "container.lookupProperty"', function() {
|
afterEach(function() {
|
||||||
beforeEach(function simulateRuntimeWithoutLookupProperty() {
|
sinon.restore();
|
||||||
var oldTemplateMethod = handlebarsEnv.template;
|
});
|
||||||
sinon.replace(handlebarsEnv, 'template', function(templateSpec) {
|
|
||||||
templateSpec.main = wrapToAdjustContainer(templateSpec.main);
|
|
||||||
return oldTemplateMethod.call(this, templateSpec);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(function() {
|
it('should work with simple properties', function() {
|
||||||
sinon.restore();
|
expectTemplate('{{aProperty}}')
|
||||||
});
|
.withInput({ aProperty: 'propertyValue' })
|
||||||
|
.toCompileTo('propertyValue');
|
||||||
|
});
|
||||||
|
|
||||||
it('should work with simple properties', function() {
|
it('should work with Array.prototype.length', function() {
|
||||||
expectTemplate('{{aProperty}}')
|
expectTemplate('{{anArray.length}}')
|
||||||
.withInput({ aProperty: 'propertyValue' })
|
.withInput({ anArray: ['a', 'b', 'c'] })
|
||||||
.toCompileTo('propertyValue');
|
.toCompileTo('3');
|
||||||
});
|
|
||||||
|
|
||||||
it('should work with Array.prototype.length', function() {
|
|
||||||
expectTemplate('{{anArray.length}}')
|
|
||||||
.withInput({ anArray: ['a', 'b', 'c'] })
|
|
||||||
.toCompileTo('3');
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Vendored
+4
-2
@@ -30,8 +30,10 @@ declare namespace Handlebars {
|
|||||||
data?: any;
|
data?: any;
|
||||||
blockParams?: any[];
|
blockParams?: any[];
|
||||||
allowCallsToHelperMissing?: boolean;
|
allowCallsToHelperMissing?: boolean;
|
||||||
allowedProtoProperties?: { [name: string]: boolean }
|
allowedProtoProperties?: { [name: string]: boolean };
|
||||||
allowedProtoMethods?: { [name: string]: boolean }
|
allowedProtoMethods?: { [name: string]: boolean };
|
||||||
|
allowProtoPropertiesByDefault?: boolean;
|
||||||
|
allowProtoMethodsByDefault?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface HelperOptions {
|
export interface HelperOptions {
|
||||||
|
|||||||
+4
-2
@@ -241,12 +241,14 @@ function testExceptionWithNodeTypings() {
|
|||||||
let stack: string | undefined = exception.stack;
|
let stack: string | undefined = exception.stack;
|
||||||
}
|
}
|
||||||
|
|
||||||
function testProtoPropertyControlOptions() {
|
function testProtoAccessControlControlOptions() {
|
||||||
Handlebars.compile('test')(
|
Handlebars.compile('test')(
|
||||||
{},
|
{},
|
||||||
{
|
{
|
||||||
allowedProtoMethods: { allowedMethod: true, forbiddenMethod: false },
|
allowedProtoMethods: { allowedMethod: true, forbiddenMethod: false },
|
||||||
allowedProtoProperties: { allowedProperty: true, forbiddenProperty: false }
|
allowedProtoProperties: { allowedProperty: true, forbiddenProperty: false },
|
||||||
|
allowProtoMethodsByDefault: true,
|
||||||
|
allowProtoPropertiesByDefault: false,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user