diff --git a/.eslintrc.js b/.eslintrc.js index 3ba423055..5c55b10f9 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -57,6 +57,7 @@ module.exports = { "no-with": "error", "radix": "error", "wrap-iife": "error", + "no-prototype-builtins": "error", // Variables // @@ -114,4 +115,4 @@ module.exports = { "ecmaVersion": 6, "ecmaFeatures": {} } -} +}; diff --git a/bench/util/benchwarmer.js b/bench/util/benchwarmer.js index 49f66f737..58f138a0e 100644 --- a/bench/util/benchwarmer.js +++ b/bench/util/benchwarmer.js @@ -29,7 +29,7 @@ BenchWarmer.prototype = { }); }, push: function(name, fn) { - if (this.names.indexOf(name) == -1) { + if (this.names.indexOf(name) === -1) { this.names.push(name); } @@ -77,7 +77,7 @@ BenchWarmer.prototype = { var errors = false, prop, bench; for (prop in self.errors) { - if (self.errors.hasOwnProperty(prop) + if (Object.prototype.hasOwnProperty.call(self, prop) && self.errors[prop].error.message !== 'EWOT') { errors = true; break; @@ -86,9 +86,8 @@ BenchWarmer.prototype = { if (errors) { print('\n\nErrors:\n'); - for (prop in self.errors) { - if (self.errors.hasOwnProperty(prop) - && self.errors[prop].error.message !== 'EWOT') { + Object.keys(self.errors).forEach(function(prop) { + if (self.errors[prop].error.message !== 'EWOT') { bench = self.errors[prop]; print('\n' + bench.name + ':\n'); print(bench.error.message); @@ -97,7 +96,7 @@ BenchWarmer.prototype = { } print('\n'); } - } + }); } callback(); diff --git a/components/bower.json b/components/bower.json index 53453c96d..07e2baa96 100644 --- a/components/bower.json +++ b/components/bower.json @@ -1,6 +1,6 @@ { "name": "handlebars", - "version": "4.5.2", + "version": "4.5.3", "main": "handlebars.js", "license": "MIT", "dependencies": {} diff --git a/components/handlebars.js.nuspec b/components/handlebars.js.nuspec index a4ec49b01..e8d52d4ca 100644 --- a/components/handlebars.js.nuspec +++ b/components/handlebars.js.nuspec @@ -2,7 +2,7 @@ handlebars.js - 4.5.2 + 4.5.3 handlebars.js Authors https://github.com/wycats/handlebars.js/blob/master/LICENSE https://github.com/wycats/handlebars.js/ diff --git a/components/package.json b/components/package.json index 1fcee3eed..9623086b4 100644 --- a/components/package.json +++ b/components/package.json @@ -1,6 +1,6 @@ { "name": "handlebars", - "version": "4.5.2", + "version": "4.5.3", "license": "MIT", "jspm": { "main": "handlebars", diff --git a/lib/handlebars/base.js b/lib/handlebars/base.js index adb1547a5..ad3541b53 100644 --- a/lib/handlebars/base.js +++ b/lib/handlebars/base.js @@ -4,7 +4,7 @@ import {registerDefaultHelpers} from './helpers'; import {registerDefaultDecorators} from './decorators'; import logger from './logger'; -export const VERSION = '4.5.2'; +export const VERSION = '4.5.3'; export const COMPILER_REVISION = 8; export const LAST_COMPATIBLE_COMPILER_REVISION = 7; diff --git a/lib/handlebars/compiler/code-gen.js b/lib/handlebars/compiler/code-gen.js index 43c0481bc..30c5af740 100644 --- a/lib/handlebars/compiler/code-gen.js +++ b/lib/handlebars/compiler/code-gen.js @@ -125,14 +125,12 @@ CodeGen.prototype = { objectLiteral: function(obj) { let pairs = []; - for (let key in obj) { - if (obj.hasOwnProperty(key)) { - let value = castChunk(obj[key], this); - if (value !== 'undefined') { - pairs.push([this.quotedString(key), ':', value]); - } + Object.keys(obj).forEach(key => { + let value = castChunk(obj[key], this); + if (value !== 'undefined') { + pairs.push([this.quotedString(key), ':', value]); } - } + }); let ret = this.generateList(pairs); ret.prepend('{'); diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js index 894f7a7c0..b56ca5c26 100644 --- a/lib/handlebars/compiler/compiler.js +++ b/lib/handlebars/compiler/compiler.js @@ -54,9 +54,7 @@ Compiler.prototype = { options.blockParams = options.blockParams || []; - // These changes will propagate to the other compiler components - let knownHelpers = options.knownHelpers; - options.knownHelpers = { + options.knownHelpers = extend(Object.create(null), { 'helperMissing': true, 'blockHelperMissing': true, 'each': true, @@ -65,15 +63,7 @@ Compiler.prototype = { 'with': true, 'log': true, 'lookup': true - }; - if (knownHelpers) { - // the next line should use "Object.keys", but the code has been like this a long time and changing it, might - // cause backwards-compatibility issues... It's an old library... - // eslint-disable-next-line guard-for-in - for (let name in knownHelpers) { - this.options.knownHelpers[name] = knownHelpers[name]; - } - } + }, options.knownHelpers); return this.accept(program); }, @@ -369,7 +359,6 @@ Compiler.prototype = { if (isEligible && !isHelper) { let name = sexpr.path.parts[0], options = this.options; - if (options.knownHelpers[name]) { isHelper = true; } else if (options.knownHelpersOnly) { diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js index b21df1d8a..9a4c02daf 100644 --- a/lib/handlebars/compiler/javascript-compiler.js +++ b/lib/handlebars/compiler/javascript-compiler.js @@ -2,6 +2,7 @@ import { COMPILER_REVISION, REVISION_CHANGES } from '../base'; import Exception from '../exception'; import {isArray} from '../utils'; import CodeGen from './code-gen'; +import {dangerousPropertyRegex} from '../helpers/lookup'; function Literal(value) { this.value = value; @@ -13,9 +14,8 @@ JavaScriptCompiler.prototype = { // PUBLIC API: You can override these methods in a subclass to provide // alternative compiled forms for name lookup and buffering semantics nameLookup: function(parent, name/* , type*/) { - const isEnumerable = [ this.aliasable('container.propertyIsEnumerable'), '.call(', parent, ',"constructor")']; - - if (name === 'constructor') { + if (dangerousPropertyRegex.test(name)) { + const isEnumerable = [ this.aliasable('container.propertyIsEnumerable'), '.call(', parent, ',', JSON.stringify(name), ')']; return ['(', isEnumerable, '?', _actualLookup(), ' : undefined)']; } return _actualLookup(); @@ -218,13 +218,13 @@ JavaScriptCompiler.prototype = { // aliases will not be used, but this case is already being run on the client and // we aren't concern about minimizing the template size. let aliasCount = 0; - for (let alias in this.aliases) { // eslint-disable-line guard-for-in + Object.keys(this.aliases).forEach(alias => { let node = this.aliases[alias]; - if (this.aliases.hasOwnProperty(alias) && node.children && node.referenceCount > 1) { + if (node.children && node.referenceCount > 1) { varDeclarations += ', alias' + (++aliasCount) + '=' + alias; node.children[0] = 'alias' + aliasCount; } - } + }); let params = ['container', 'depth0', 'helpers', 'partials', 'data']; diff --git a/lib/handlebars/helpers/each.js b/lib/handlebars/helpers/each.js index ce549b5c7..8e57b7782 100644 --- a/lib/handlebars/helpers/each.js +++ b/lib/handlebars/helpers/each.js @@ -62,18 +62,16 @@ export default function(instance) { } else { let priorKey; - for (let key in context) { - if (context.hasOwnProperty(key)) { - // We're running the iterations one step out of sync so we can detect - // the last iteration without have to scan the object twice and create - // an itermediate keys array. - if (priorKey !== undefined) { - execIteration(priorKey, i - 1); - } - priorKey = key; - i++; + Object.keys(context).forEach(key => { + // We're running the iterations one step out of sync so we can detect + // the last iteration without have to scan the object twice and create + // an itermediate keys array. + if (priorKey !== undefined) { + execIteration(priorKey, i - 1); } - } + priorKey = key; + i++; + }); if (priorKey !== undefined) { execIteration(priorKey, i - 1, true); } diff --git a/lib/handlebars/helpers/lookup.js b/lib/handlebars/helpers/lookup.js index 0654cc393..8c1b604be 100644 --- a/lib/handlebars/helpers/lookup.js +++ b/lib/handlebars/helpers/lookup.js @@ -1,9 +1,11 @@ +export const dangerousPropertyRegex = /^(constructor|__defineGetter__|__defineSetter__|__lookupGetter__|__proto__)$/; + export default function(instance) { instance.registerHelper('lookup', function(obj, field) { if (!obj) { return obj; } - if (String(field) === 'constructor' && !obj.propertyIsEnumerable(field)) { + if (dangerousPropertyRegex.test(String(field)) && !Object.prototype.propertyIsEnumerable.call(obj, field)) { return undefined; } return obj[field]; diff --git a/package-lock.json b/package-lock.json index 332d8fb1b..6ed12f6bb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "handlebars", - "version": "4.5.0", + "version": "4.5.2", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -83,6 +83,50 @@ } } }, + "@sinonjs/commons": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.6.0.tgz", + "integrity": "sha512-w4/WHG7C4WWFyE5geCieFJF6MZkbW4VAriol5KlmQXpAQdxvV0p26sqNZOW6Qyw6Y0l9K4g+cHvvczR2sEEpqg==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "@sinonjs/formatio": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-3.2.2.tgz", + "integrity": "sha512-B8SEsgd8gArBLMD6zpRw3juQ2FVSsmdd7qlevyDqzS9WTCtvF55/gAL+h6gue8ZvPYcdiPdvueM/qm//9XzyTQ==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1", + "@sinonjs/samsam": "^3.1.0" + } + }, + "@sinonjs/samsam": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.3.3.tgz", + "integrity": "sha512-bKCMKZvWIjYD0BLGnNrxVuw4dkWCYsLqFOUWw8VgKF/+5Y+mE7LfHWPIYoDXowH+3a9LsWDMo0uAP8YDosPvHQ==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.3.0", + "array-from": "^2.1.1", + "lodash": "^4.17.15" + }, + "dependencies": { + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true + } + } + }, + "@sinonjs/text-encoding": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz", + "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", + "dev": true + }, "@types/parsimmon": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/@types/parsimmon/-/parsimmon-1.10.0.tgz", @@ -271,6 +315,12 @@ "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", "dev": true }, + "array-from": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz", + "integrity": "sha1-z+nYwmYoudxa7MYqn12PHzUsEZU=", + "dev": true + }, "array-slice": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", @@ -324,6 +374,12 @@ "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", "dev": true }, + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true + }, "assign-symbols": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", @@ -985,6 +1041,20 @@ "lazy-cache": "^1.0.3" } }, + "chai": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz", + "integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==", + "dev": true, + "requires": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "pathval": "^1.1.0", + "type-detect": "^4.0.5" + } + }, "chalk": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", @@ -1004,6 +1074,12 @@ "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=", "dev": true }, + "check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "dev": true + }, "chokidar": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.5.2.tgz", @@ -1467,6 +1543,15 @@ "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", "dev": true }, + "deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "dev": true, + "requires": { + "type-detect": "^4.0.0" + } + }, "deep-is": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", @@ -1619,6 +1704,12 @@ "integrity": "sha1-gAwN0eCov7yVg1wgKtIg/jF+WhI=", "dev": true }, + "dirty-chai": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/dirty-chai/-/dirty-chai-2.0.1.tgz", + "integrity": "sha512-ys79pWKvDMowIDEPC6Fig8d5THiC0DJ2gmTeGzVAoEH18J8OzLud0Jh7I9IWg3NSk8x2UocznUuFmfHCXYZx9w==", + "dev": true + }, "doctrine": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", @@ -3287,6 +3378,12 @@ "integrity": "sha1-+Xj6TJDR3+f/LWvtoqUV5xO9z0o=", "dev": true }, + "get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "dev": true + }, "get-stdin": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", @@ -4774,6 +4871,12 @@ "verror": "1.10.0" } }, + "just-extend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.0.2.tgz", + "integrity": "sha512-FrLwOgm+iXrPV+5zDU6Jqu4gCRXbWEQg2O3SKONsWE4w7AXFRkryS53bpWdaL9cNol+AmR3AEYz6kn+o0fCPnw==", + "dev": true + }, "kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", @@ -5208,6 +5311,12 @@ "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=", "dev": true }, + "lolex": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-4.2.0.tgz", + "integrity": "sha512-gKO5uExCXvSm6zbF562EvM+rd1kQDnB9AZBbiQVzf1ZmdDpxUSvpnAaVOP83N/31mRK8Ml8/VE8DMvsAZQ+7wg==", + "dev": true + }, "longest": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", @@ -5661,6 +5770,36 @@ "integrity": "sha1-ozeKdpbOfSI+iPybdkvX7xCJ42Y=", "dev": true }, + "nise": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/nise/-/nise-1.5.2.tgz", + "integrity": "sha512-/6RhOUlicRCbE9s+94qCUsyE+pKlVJ5AhIv+jEE7ESKwnbXqulKZ1FYU+XAtHHWE9TinYvAxDUJAb912PwPoWA==", + "dev": true, + "requires": { + "@sinonjs/formatio": "^3.2.1", + "@sinonjs/text-encoding": "^0.7.1", + "just-extend": "^4.0.2", + "lolex": "^4.1.0", + "path-to-regexp": "^1.7.0" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "dev": true, + "requires": { + "isarray": "0.0.1" + } + } + } + }, "node-libs-browser": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-0.7.0.tgz", @@ -6234,6 +6373,12 @@ "pinkie-promise": "^2.0.0" } }, + "pathval": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", + "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", + "dev": true + }, "pbkdf2-compat": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/pbkdf2-compat/-/pbkdf2-compat-2.0.1.tgz", @@ -7331,6 +7476,32 @@ "integrity": "sha1-Krt1qt453rXMgVzhDmGRFkhQuvA=", "dev": true }, + "sinon": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-7.5.0.tgz", + "integrity": "sha512-AoD0oJWerp0/rY9czP/D6hDTTUYGpObhZjMpd7Cl/A6+j0xBE+ayL/ldfggkBXUs0IkvIiM1ljM8+WkOc5k78Q==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.4.0", + "@sinonjs/formatio": "^3.2.1", + "@sinonjs/samsam": "^3.3.3", + "diff": "^3.5.0", + "lolex": "^4.2.0", + "nise": "^1.5.2", + "supports-color": "^5.5.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, "slash": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", @@ -8162,6 +8333,12 @@ "prelude-ls": "~1.1.2" } }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, "type-is": { "version": "1.6.16", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", diff --git a/package.json b/package.json index d753c9b2a..74749d86b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "handlebars", "barename": "handlebars", - "version": "4.5.2", + "version": "4.5.3", "description": "Handlebars provides the power necessary to let you build semantic templates effectively with no frustration", "homepage": "http://www.handlebarsjs.com/", "keywords": [ @@ -34,6 +34,8 @@ "babel-loader": "^5.0.0", "babel-runtime": "^5.1.10", "benchmark": "~1.0", + "chai": "^4.2.0", + "dirty-chai": "^2.0.1", "dtslint": "^0.5.5", "dustjs-linkedin": "^2.0.2", "eco": "~1.1.0-rc-3", @@ -58,6 +60,7 @@ "mock-stdin": "^0.3.0", "mustache": "^2.1.3", "semver": "^5.0.1", + "sinon": "^7.5.0", "typescript": "^3.4.3", "underscore": "^1.5.1", "webpack": "^1.12.6", diff --git a/release-notes.md b/release-notes.md index 3eefabe89..199c39bf5 100644 --- a/release-notes.md +++ b/release-notes.md @@ -2,7 +2,49 @@ ## Development -[Commits](https://github.com/wycats/handlebars.js/compare/v4.5.2...master) +[Commits](https://github.com/wycats/handlebars.js/compare/v4.5.3...master) + +## v4.5.3 - November 18th, 2019 +Bugfixes: + +- fix: add "no-prototype-builtins" eslint-rule and fix all occurences - f7f05d7 +- fix: add more properties required to be enumerable - 1988878 + +Chores / Build: +- fix: use !== 0 instead of != 0 - c02b05f +- add chai and dirty-chai and sinon, for cleaner test-assertions and spies, + deprecate old assertion-methods - 93e284e, 886ba86, 0817dad, 93516a0 + +Security: + +- The properties `__proto__`, `__defineGetter__`, `__defineSetter__` and `__lookupGetter__` + have been added to the list of "properties that must be enumerable". + If a property by that name is found and not enumerable on its parent, + it will silently evaluate to `undefined`. This is done in both the compiled template and the "lookup"-helper. + This will prevent new Remote-Code-Execution exploits that have been + published recently. + +Compatibility notes: + +- Due to the security-fixes. The semantics of the templates using + `__proto__`, `__defineGetter__`, `__defineSetter__` and `__lookupGetter__` in the respect that those expression now return + `undefined` rather than their actual value from the proto. +- The semantics have not changed in cases where the properties are enumerable, as in: + +```js +{ + __proto__: 'some string' +} +``` + +- The change may be breaking in that respect, but we still only + increase the patch-version, because the incompatible use-cases + are not intended, undocumented and far less important than fixing + Remote-Code-Execution exploits on existing systems. + + + +[Commits](https://github.com/wycats/handlebars.js/compare/v4.5.2...v4.5.3) ## v4.5.2 - November 13th, 2019 # Bugfixes diff --git a/spec/.eslintrc b/spec/.eslintrc index 34319f8c1..4bec97f44 100644 --- a/spec/.eslintrc +++ b/spec/.eslintrc @@ -27,16 +27,20 @@ "start": true, "stop": true, "ok": true, + "sinon": true, "strictEqual": true, - "define": true + "define": true, + "expect": true, + "chai": true }, "env": { "mocha": true }, "rules": { // Disabling for tests, for now. - "no-path-concat": 0, + "no-path-concat": "off", - "no-var": 0 + "no-var": "off", + "dot-notation": "off" } } \ No newline at end of file diff --git a/spec/amd-runtime.html b/spec/amd-runtime.html index 7cc64f190..fc413bd67 100644 --- a/spec/amd-runtime.html +++ b/spec/amd-runtime.html @@ -18,11 +18,14 @@ document.documentElement.className = 'headless'; } + + + - diff --git a/spec/amd.html b/spec/amd.html index 1149dc706..9ec44e686 100644 --- a/spec/amd.html +++ b/spec/amd.html @@ -19,11 +19,14 @@ document.documentElement.className = 'headless'; } + + + - diff --git a/spec/compiler.js b/spec/compiler.js index 02dad6751..3d37b5940 100644 --- a/spec/compiler.js +++ b/spec/compiler.js @@ -59,7 +59,7 @@ describe('compiler', function() { Handlebars.compile(' \n {{#if}}\n{{/def}}')(); equal(true, false, 'Statement must throw exception. This line should not be executed.'); } catch (err) { - equal(err.propertyIsEnumerable('column'), true, 'Checking error column'); + equal(Object.prototype.propertyIsEnumerable.call(err, 'column'), true, 'Checking error column'); } }); diff --git a/spec/env/browser.js b/spec/env/browser.js index 9e54a3977..d1dab0d5e 100644 --- a/spec/env/browser.js +++ b/spec/env/browser.js @@ -3,6 +3,15 @@ require('./common'); var fs = require('fs'), vm = require('vm'); +var chai = require('chai'); +var dirtyChai = require('dirty-chai'); + + +chai.use(dirtyChai); +global.expect = chai.expect; + +global.sinon = require('sinon'); + global.Handlebars = 'no-conflict'; var filename = 'dist/handlebars.js'; diff --git a/spec/env/common.js b/spec/env/common.js index 9523eaeb2..ac9421845 100644 --- a/spec/env/common.js +++ b/spec/env/common.js @@ -63,12 +63,20 @@ global.compileWithPartials = function(string, hashOrArray, partials) { }; +/** + * @deprecated Use chai's expect-style API instead (`expect(actualValue).to.equal(expectedValue)`) + * @see https://www.chaijs.com/api/bdd/ + */ global.equals = global.equal = function equals(a, b, msg) { if (a !== b) { throw new AssertError("'" + a + "' should === '" + b + "'" + (msg ? ': ' + msg : ''), equals); } }; +/** + * @deprecated Use chai's expect-style API instead (`expect(actualValue).to.equal(expectedValue)`) + * @see https://www.chaijs.com/api/bdd/#method_throw + */ global.shouldThrow = function(callback, type, msg) { var failed; try { @@ -134,24 +142,35 @@ HandlebarsTestBench.prototype.withMessage = function(message) { }; HandlebarsTestBench.prototype.toCompileTo = function(expectedOutputAsString) { + expect(this._compileAndExeute()).to.equal(expectedOutputAsString); +}; + +// see chai "to.throw" (https://www.chaijs.com/api/bdd/#method_throw) +HandlebarsTestBench.prototype.toThrow = function(errorLike, errMsgMatcher, msg) { var self = this; + expect(function() { + self._compileAndExeute(); + }).to.throw(errorLike, errMsgMatcher, msg); +}; + +HandlebarsTestBench.prototype._compileAndExeute = function() { var compile = Object.keys(this.partials).length > 0 ? CompilerContext.compileWithPartial : CompilerContext.compile; + var combinedRuntimeOptions = this._combineRuntimeOptions(); + + var template = compile(this.templateAsString, this.compileOptions); + return template(this.input, combinedRuntimeOptions); +}; + +HandlebarsTestBench.prototype._combineRuntimeOptions = function() { + var self = this; var combinedRuntimeOptions = {}; Object.keys(this.runtimeOptions).forEach(function(key) { combinedRuntimeOptions[key] = self.runtimeOptions[key]; }); combinedRuntimeOptions.helpers = this.helpers; combinedRuntimeOptions.partials = this.partials; - - var template = compile(this.templateAsString, this.compileOptions); - var output = template(this.input, combinedRuntimeOptions); - - if (output !== expectedOutputAsString) { - // Error message formatted so that IntelliJ-Idea shows "diff"-button - // https://stackoverflow.com/a/10945655/4251384 - throw new AssertError(this.message + '\nexpected:' + expectedOutputAsString + 'but was:' + output); - } + return combinedRuntimeOptions; }; diff --git a/spec/env/node.js b/spec/env/node.js index 881609d2e..0695468ba 100644 --- a/spec/env/node.js +++ b/spec/env/node.js @@ -1,5 +1,14 @@ require('./common'); +var chai = require('chai'); +var dirtyChai = require('dirty-chai'); + + +chai.use(dirtyChai); +global.expect = chai.expect; + +global.sinon = require('sinon'); + global.Handlebars = require('../../lib'); global.CompilerContext = { diff --git a/spec/env/runtime.js b/spec/env/runtime.js index 642acd348..4f6d28534 100644 --- a/spec/env/runtime.js +++ b/spec/env/runtime.js @@ -3,6 +3,15 @@ require('./common'); var fs = require('fs'), vm = require('vm'); +var chai = require('chai'); +var dirtyChai = require('dirty-chai'); + + +chai.use(dirtyChai); +global.expect = chai.expect; + +global.sinon = require('sinon'); + global.Handlebars = 'no-conflict'; var filename = 'dist/handlebars.runtime.js'; diff --git a/spec/index.html b/spec/index.html index 5db2146ad..b768dff76 100644 --- a/spec/index.html +++ b/spec/index.html @@ -19,12 +19,15 @@ document.documentElement.className = 'headless'; } + + + - + + + - diff --git a/spec/umd.html b/spec/umd.html index 5f3863fc8..53d6b2716 100644 --- a/spec/umd.html +++ b/spec/umd.html @@ -18,11 +18,15 @@ document.documentElement.className = 'headless'; } + + + + - diff --git a/tasks/test.js b/tasks/test.js index 6bee3920a..a17fc2a22 100644 --- a/tasks/test.js +++ b/tasks/test.js @@ -33,7 +33,7 @@ module.exports = function(grunt) { var runner = childProcess.fork('./spec/env/runner', [], {stdio: 'inherit'}); runner.on('close', function(code) { - if (code != 0) { + if (code !== 0) { grunt.fatal(code + ' tests failed'); } done(); @@ -55,7 +55,7 @@ module.exports = function(grunt) { var runner = childProcess.fork('./spec/env/runner', ['--min'], {stdio: 'inherit'}); runner.on('close', function(code) { - if (code != 0) { + if (code !== 0) { grunt.fatal(code + ' tests failed'); } done();