Skip to content

Commit 70d19e7

Browse files
committed
Partial Port of 1736 to 3.x
1 parent 16bd606 commit 70d19e7

File tree

5 files changed

+199
-18
lines changed

5 files changed

+199
-18
lines changed

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@
3636
"babel-loader": "^5.0.0",
3737
"babel-runtime": "^5.1.10",
3838
"benchmark": "~1.0",
39+
"chai": "^4.2.0",
40+
"chai-diff": "^1.0.1",
41+
"dirty-chai": "^2.0.1",
3942
"dustjs-linkedin": "^2.0.2",
4043
"eco": "~1.1.0-rc-3",
4144
"grunt": "~0.4.1",

spec/.eslintrc

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,11 @@
33
"CompilerContext": true,
44
"Handlebars": true,
55
"handlebarsEnv": true,
6-
76
"shouldCompileTo": true,
87
"shouldCompileToWithPartials": true,
98
"shouldThrow": true,
9+
"expectTemplate": true,
1010
"compileWithPartials": true,
11-
1211
"console": true,
1312
"require": true,
1413
"suite": true,
@@ -22,15 +21,17 @@
2221
"stop": true,
2322
"ok": true,
2423
"strictEqual": true,
25-
"define": true
24+
"define": true,
25+
"expect": true,
26+
"chai": true
2627
},
2728
"env": {
2829
"mocha": true
2930
},
3031
"rules": {
3132
// Disabling for tests, for now.
3233
"no-path-concat": 0,
33-
34+
"dot-notation": 0,
3435
"no-var": 0
3536
}
3637
}

spec/env/common.js

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,3 +72,121 @@ global.shouldThrow = function(callback, type, msg) {
7272
throw new Error('It failed to throw');
7373
}
7474
};
75+
76+
77+
global.expectTemplate = function(templateAsString) {
78+
return new HandlebarsTestBench(templateAsString);
79+
};
80+
81+
function HandlebarsTestBench(templateAsString) {
82+
this.templateAsString = templateAsString;
83+
this.helpers = {};
84+
this.partials = {};
85+
this.decorators = {};
86+
this.input = {};
87+
this.message =
88+
'Template' + templateAsString + ' does not evaluate to expected output';
89+
this.compileOptions = {};
90+
this.runtimeOptions = {};
91+
}
92+
93+
HandlebarsTestBench.prototype.withInput = function(input) {
94+
this.input = input;
95+
return this;
96+
};
97+
98+
HandlebarsTestBench.prototype.withHelper = function(name, helperFunction) {
99+
this.helpers[name] = helperFunction;
100+
return this;
101+
};
102+
103+
HandlebarsTestBench.prototype.withHelpers = function(helperFunctions) {
104+
var self = this;
105+
Object.keys(helperFunctions).forEach(function(name) {
106+
self.withHelper(name, helperFunctions[name]);
107+
});
108+
return this;
109+
};
110+
111+
HandlebarsTestBench.prototype.withPartial = function(name, partialAsString) {
112+
this.partials[name] = partialAsString;
113+
return this;
114+
};
115+
116+
HandlebarsTestBench.prototype.withPartials = function(partials) {
117+
var self = this;
118+
Object.keys(partials).forEach(function(name) {
119+
self.withPartial(name, partials[name]);
120+
});
121+
return this;
122+
};
123+
124+
HandlebarsTestBench.prototype.withDecorator = function(
125+
name,
126+
decoratorFunction
127+
) {
128+
this.decorators[name] = decoratorFunction;
129+
return this;
130+
};
131+
132+
HandlebarsTestBench.prototype.withDecorators = function(decorators) {
133+
var self = this;
134+
Object.keys(decorators).forEach(function(name) {
135+
self.withDecorator(name, decorators[name]);
136+
});
137+
return this;
138+
};
139+
140+
HandlebarsTestBench.prototype.withCompileOptions = function(compileOptions) {
141+
this.compileOptions = compileOptions;
142+
return this;
143+
};
144+
145+
HandlebarsTestBench.prototype.withRuntimeOptions = function(runtimeOptions) {
146+
this.runtimeOptions = runtimeOptions;
147+
return this;
148+
};
149+
150+
HandlebarsTestBench.prototype.withMessage = function(message) {
151+
this.message = message;
152+
return this;
153+
};
154+
155+
HandlebarsTestBench.prototype.toCompileTo = function(expectedOutputAsString) {
156+
expect(this._compileAndExecute()).to.equal(
157+
expectedOutputAsString,
158+
this.message
159+
);
160+
};
161+
162+
// see chai "to.throw" (https://www.chaijs.com/api/bdd/#method_throw)
163+
HandlebarsTestBench.prototype.toThrow = function(errorLike, errMsgMatcher) {
164+
var self = this;
165+
expect(function() {
166+
self._compileAndExecute();
167+
}).to.throw(errorLike, errMsgMatcher, this.message);
168+
};
169+
170+
HandlebarsTestBench.prototype._compileAndExecute = function() {
171+
var compile =
172+
Object.keys(this.partials).length > 0
173+
? CompilerContext.compileWithPartial
174+
: CompilerContext.compile;
175+
176+
var combinedRuntimeOptions = this._combineRuntimeOptions();
177+
178+
var template = compile(this.templateAsString, this.compileOptions);
179+
return template(this.input, combinedRuntimeOptions);
180+
};
181+
182+
HandlebarsTestBench.prototype._combineRuntimeOptions = function() {
183+
var self = this;
184+
var combinedRuntimeOptions = {};
185+
Object.keys(this.runtimeOptions).forEach(function(key) {
186+
combinedRuntimeOptions[key] = self.runtimeOptions[key];
187+
});
188+
combinedRuntimeOptions.helpers = this.helpers;
189+
combinedRuntimeOptions.partials = this.partials;
190+
combinedRuntimeOptions.decorators = this.decorators;
191+
return combinedRuntimeOptions;
192+
};

spec/env/node.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
require('./common');
22

3+
var chai = require('chai');
4+
var dirtyChai = require('dirty-chai');
5+
6+
chai.use(dirtyChai);
7+
global.expect = chai.expect;
8+
39
global.Handlebars = require('../../lib');
410

511
global.CompilerContext = {

spec/security.js

Lines changed: 67 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,26 @@
11
describe('security issues', function() {
22
describe('GH-1495: Prevent Remote Code Execution via constructor', function() {
3-
it('should not allow constructors to be accessed', function() {
4-
shouldCompileTo('{{constructor.name}}', {}, '');
5-
shouldCompileTo('{{lookup (lookup this "constructor") "name"}}', {}, '');
6-
});
3+
checkPropertyAccess({});
4+
5+
describe('in strict-mode', function() {
6+
checkPropertyAccess({ strict: true });
7+
});
78

9+
10+
function checkPropertyAccess(compileOptions) {
811
it('should allow the "constructor" property to be accessed if it is enumerable', function() {
9-
shouldCompileTo('{{constructor.name}}', {'constructor': {
10-
'name': 'here we go'
11-
}}, 'here we go');
12-
shouldCompileTo('{{lookup (lookup this "constructor") "name"}}', {'constructor': {
13-
'name': 'here we go'
14-
}}, 'here we go');
12+
expectTemplate('{{constructor.name}}')
13+
.withCompileOptions(compileOptions)
14+
.withInput({'constructor': {
15+
'name': 'here we go'
16+
}})
17+
.toCompileTo('here we go');
18+
expectTemplate('{{lookup (lookup this "constructor") "name"}}')
19+
.withCompileOptions(compileOptions)
20+
.withInput({'constructor': {
21+
'name': 'here we go'
22+
}})
23+
.toCompileTo('here we go');
1524
});
1625

1726
it('should allow prototype properties that are not constructors', function() {
@@ -24,11 +33,55 @@ describe('security issues', function() {
2433
}
2534
});
2635

27-
shouldCompileTo('{{#with this}}{{this.abc}}{{/with}}',
28-
new TestClass(), 'xyz');
29-
shouldCompileTo('{{#with this}}{{lookup this "abc"}}{{/with}}',
30-
new TestClass(), 'xyz');
36+
37+
expectTemplate('{{#with this}}{{this.abc}}{{/with}}')
38+
.withCompileOptions(compileOptions)
39+
.withInput(new TestClass())
40+
.toCompileTo('xyz');
41+
42+
expectTemplate('{{#with this}}{{lookup this "abc"}}{{/with}}')
43+
.withCompileOptions(compileOptions)
44+
.withInput(new TestClass())
45+
.toCompileTo('xyz');
3146
});
47+
48+
it('should not allow constructors to be accessed', function() {
49+
expectTemplate('{{lookup (lookup this "constructor") "name"}}')
50+
.withCompileOptions(compileOptions)
51+
.withInput({})
52+
.toCompileTo('');
53+
if (compileOptions.strict) {
54+
expectTemplate('{{constructor.name}}')
55+
.withCompileOptions(compileOptions)
56+
.withInput({})
57+
.toThrow(TypeError);
58+
} else {
59+
expectTemplate('{{constructor.name}}')
60+
.withCompileOptions(compileOptions)
61+
.withInput({})
62+
.toCompileTo('');
63+
}
64+
});
65+
66+
it('should not allow __proto__ to be accessed', function() {
67+
expectTemplate('{{lookup (lookup this "__proto__") "name"}}')
68+
.withCompileOptions(compileOptions)
69+
.withInput({})
70+
.toCompileTo('');
71+
if (compileOptions.strict) {
72+
expectTemplate('{{__proto__.name}}')
73+
.withCompileOptions(compileOptions)
74+
.withInput({})
75+
.toThrow(TypeError);
76+
} else {
77+
expectTemplate('{{__proto__.name}}')
78+
.withCompileOptions(compileOptions)
79+
.withInput({})
80+
.toCompileTo('');
81+
}
82+
});
83+
84+
}
3285
});
3386

3487
describe('GH-1595', function() {

0 commit comments

Comments
 (0)