From 576283bb5db2f5b6d2c9f979163a84644f581697 Mon Sep 17 00:00:00 2001
From: mde <mde@fleegix.org>
Date: Mon, 31 May 2021 13:29:51 -0700
Subject: [PATCH] Move to utils, handle older runtimes, fix tests

---
 lib/ejs.js   | 27 ++++++++-------------------
 lib/utils.js | 26 ++++++++++++++++++++++++++
 test/ejs.js  | 10 ++++++----
 3 files changed, 40 insertions(+), 23 deletions(-)

diff --git a/lib/ejs.js b/lib/ejs.js
index bd7ca927..13dc7392 100755
--- a/lib/ejs.js
+++ b/lib/ejs.js
@@ -67,17 +67,6 @@ var _OPTS_PASSABLE_WITH_DATA_EXPRESS = _OPTS_PASSABLE_WITH_DATA.concat('cache');
 var _BOM = /^\uFEFF/;
 var _JS_IDENTIFIER = /^[a-zA-Z_$][0-9a-zA-Z_$]*$/;
 
-var createObj = function() {
-  if (typeof Object.create !== 'function') {
-    return function (o) {
-      function F() {}
-      F.prototype = o;
-      return new F();
-    };
-  }
-  return Object.create;
-}();
-
 /**
  * EJS template function cache. This can be a LRU object from lru-cache NPM
  * module. By default, it is {@link module:utils.cache}, a simple in-process
@@ -318,7 +307,7 @@ function fileLoader(filePath){
  */
 
 function includeFile(path, options) {
-  var opts = utils.shallowCopy(createObj(null), options);
+  var opts = utils.shallowCopy(utils.createNullProtoObjWherePossible(), options);
   opts.filename = getIncludePath(path, opts);
   if (typeof options.includer === 'function') {
     var includerResult = options.includer(path, opts.filename);
@@ -424,8 +413,8 @@ exports.compile = function compile(template, opts) {
  */
 
 exports.render = function (template, d, o) {
-  var data = d || createObj(null);
-  var opts = o || createObj(null);
+  var data = d || utils.createNullProtoObjWherePossible();
+  var opts = o || utils.createNullProtoObjWherePossible();
 
   // No options object -- if there are optiony names
   // in the data, copy them to options
@@ -496,7 +485,7 @@ exports.renderFile = function () {
     opts.filename = filename;
   }
   else {
-    data = createObj(null);
+    data = utils.createNullProtoObjWherePossible();
   }
 
   return tryHandleCache(opts, data, cb);
@@ -518,8 +507,8 @@ exports.clearCache = function () {
 };
 
 function Template(text, opts) {
-  opts = opts || createObj(null);
-  var options = createObj(null);
+  opts = opts || utils.createNullProtoObjWherePossible();
+  var options = utils.createNullProtoObjWherePossible();
   this.templateText = text;
   /** @type {string | null} */
   this.mode = null;
@@ -705,14 +694,14 @@ Template.prototype = {
     // Adds a local `include` function which allows full recursive include
     var returnedFn = opts.client ? fn : function anonymous(data) {
       var include = function (path, includeData) {
-        var d = utils.shallowCopy(createObj(null), data);
+        var d = utils.shallowCopy(utils.createNullProtoObjWherePossible(), data);
         if (includeData) {
           d = utils.shallowCopy(d, includeData);
         }
         return includeFile(path, opts)(d);
       };
       return fn.apply(opts.context,
-          [data || createObj(null), escapeFn, include, rethrow]);
+        [data || utils.createNullProtoObjWherePossible(), escapeFn, include, rethrow]);
     };
     if (opts.filename && typeof Object.defineProperty === 'function') {
       var filename = opts.filename;
diff --git a/lib/utils.js b/lib/utils.js
index b01e37bf..5aef7c82 100644
--- a/lib/utils.js
+++ b/lib/utils.js
@@ -183,3 +183,29 @@ exports.cache = {
 exports.hyphenToCamel = function (str) {
   return str.replace(/-[a-z]/g, function (match) { return match[1].toUpperCase(); });
 };
+
+/**
+ * Returns a null-prototype object in runtimes that support it
+ *
+ * @return {Object} Object, prototype will be set to null where possible
+ * @static
+ * @private
+ */
+exports.createNullProtoObjWherePossible = (function () {
+  if (typeof Object.create == 'function') {
+    return function () {
+      return Object.create(null);
+    };
+  }
+  if (!({__proto__: null} instanceof Object)) {
+    return function () {
+      return {__proto__: null};
+    };
+  }
+  // Not possible, just pass through
+  return function () {
+    return {};
+  };
+})();
+
+
diff --git a/test/ejs.js b/test/ejs.js
index a8d8a814..607b4a71 100755
--- a/test/ejs.js
+++ b/test/ejs.js
@@ -1183,24 +1183,26 @@ suite('identifier validation', function () {
   test('invalid outputFunctionName', function() {
     assert.throws(function() {
       ejs.compile('<p>yay</p>', {outputFunctionName: 'x;console.log(1);x'});
-    }, /outputFunctionName is not a valid JS identifier/)
+    }, /outputFunctionName is not a valid JS identifier/);
   });
 
   test('invalid localsName', function() {
     var locals = Object.create(null);
+    void(locals); // For linting;
     assert.throws(function() {
       ejs.compile('<p>yay</p>', {
         localsName: 'function(){console.log(1);return locals;}()'});
-    }, /localsName is not a valid JS identifier/)
+    }, /localsName is not a valid JS identifier/);
   });
 
   test('invalid destructuredLocals', function() {
     var locals = {};
+    void(locals); // For linting;
     assert.throws(function() {
       ejs.compile('<p>yay</p>', {
         destructuredLocals: [
           'console.log(1); //'
         ]});
-    }, /destructuredLocals\[0\] is not a valid JS identifier/)
+    }, /destructuredLocals\[0\] is not a valid JS identifier/);
   });
-});
\ No newline at end of file
+});