From 4d768fd23677ccfbf4d5b5c67a6395644e6890be Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Tue, 25 May 2021 14:53:06 +0100 Subject: [PATCH 1/2] ensure context get/set key is a string --- .../@node-red/runtime/lib/nodes/context/index.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/node_modules/@node-red/runtime/lib/nodes/context/index.js b/packages/node_modules/@node-red/runtime/lib/nodes/context/index.js index fda3852ff..56b0c7e6f 100644 --- a/packages/node_modules/@node-red/runtime/lib/nodes/context/index.js +++ b/packages/node_modules/@node-red/runtime/lib/nodes/context/index.js @@ -256,6 +256,9 @@ function createContext(id,seed,parent) { value: function(key, storage, callback) { var context; + if (typeof key !== "string" || !key.length) { + throw new Error("context get() requires 'key' to be a valid string"); + } if (!callback && typeof storage === 'function') { callback = storage; storage = undefined; @@ -338,6 +341,9 @@ function createContext(id,seed,parent) { value: function(key, value, storage, callback) { var context; + if (typeof key !== "string" || !key.length) { + throw new Error("context set() requires 'key' to be a valid string"); + } if (!callback && typeof storage === 'function') { callback = storage; storage = undefined; From a92f0c4c6e60daaed863187b6a9a3a8f18f1bcb0 Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Wed, 26 May 2021 13:04:09 +0100 Subject: [PATCH 2/2] fix context key validation + add tests - adds a helper function validateContextKey to keep it DRY - adds tests ensure key of null "" 1 {} [] [""] [1] [{}] all throw error --- .../runtime/lib/nodes/context/index.js | 43 +++-- .../runtime/lib/nodes/context/index_spec.js | 148 ++++++++++++++++++ 2 files changed, 181 insertions(+), 10 deletions(-) diff --git a/packages/node_modules/@node-red/runtime/lib/nodes/context/index.js b/packages/node_modules/@node-red/runtime/lib/nodes/context/index.js index 56b0c7e6f..967fae295 100644 --- a/packages/node_modules/@node-red/runtime/lib/nodes/context/index.js +++ b/packages/node_modules/@node-red/runtime/lib/nodes/context/index.js @@ -215,6 +215,22 @@ function followParentContext(parent, key) { return null; } +function validateContextKey(key) { + try { + const keys = Array.isArray(key) ? key : [key]; + if(!keys.length) { return false }; //no key to get/set + for (let index = 0; index < keys.length; index++) { + const k = keys[index]; + if (typeof k !== "string" || !k.length) { + return false; //not string or zero-length + } + } + } catch (error) { + return false; + } + return true; +} + function createContext(id,seed,parent) { // Seed is only set for global context - sourced from functionGlobalContext var scope = id; @@ -251,14 +267,11 @@ function createContext(id,seed,parent) { } } } + Object.defineProperties(obj, { get: { value: function(key, storage, callback) { var context; - - if (typeof key !== "string" || !key.length) { - throw new Error("context get() requires 'key' to be a valid string"); - } if (!callback && typeof storage === 'function') { callback = storage; storage = undefined; @@ -266,7 +279,14 @@ function createContext(id,seed,parent) { if (callback && typeof callback !== 'function'){ throw new Error("Callback must be a function"); } - + if (!validateContextKey(key)) { + var err = Error("Invalid context key"); + if(callback) { + return callback(err); + } else { + throw err; + } + } if (!Array.isArray(key)) { var keyParts = util.parseContextStore(key); key = keyParts.key; @@ -340,10 +360,6 @@ function createContext(id,seed,parent) { set: { value: function(key, value, storage, callback) { var context; - - if (typeof key !== "string" || !key.length) { - throw new Error("context set() requires 'key' to be a valid string"); - } if (!callback && typeof storage === 'function') { callback = storage; storage = undefined; @@ -351,7 +367,14 @@ function createContext(id,seed,parent) { if (callback && typeof callback !== 'function'){ throw new Error("Callback must be a function"); } - + if (!validateContextKey(key)) { + var err = Error("Invalid context key"); + if(callback) { + return callback(err); + } else { + throw err; + } + } if (!Array.isArray(key)) { var keyParts = util.parseContextStore(key); key = keyParts.key; diff --git a/test/unit/@node-red/runtime/lib/nodes/context/index_spec.js b/test/unit/@node-red/runtime/lib/nodes/context/index_spec.js index 6fd76a421..ee0089008 100644 --- a/test/unit/@node-red/runtime/lib/nodes/context/index_spec.js +++ b/test/unit/@node-red/runtime/lib/nodes/context/index_spec.js @@ -1006,7 +1006,155 @@ describe('context', function() { done(); }).catch(done); }); + it('should throw an error in context.get if key is empty string', function (done) { + Context.init({ contextStorage: memoryStorage }); + Context.load().then(function () { + var context = Context.get("1", "flow"); + context.get(""); + done("should throw an error."); + }).catch(function () { + done(); + }); + }); + it('should throw an error in context.get if key is an object', function (done) { + Context.init({ contextStorage: memoryStorage }); + Context.load().then(function () { + var context = Context.get("1", "flow"); + context.get({}); + done("should throw an error."); + }).catch(function () { + done(); + }); + }); + it('should throw an error in context.get if key is a number', function (done) { + Context.init({ contextStorage: memoryStorage }); + Context.load().then(function () { + var context = Context.get("1", "flow"); + context.get(1); + done("should throw an error."); + }).catch(function () { + done(); + }); + }); + it('should throw an error in context.get if key array contains an empty string', function (done) { + Context.init({ contextStorage: memoryStorage }); + Context.load().then(function () { + var context = Context.get("1", "flow"); + context.get(["ok1", "", "ok2"]); + done("should throw an error."); + }).catch(function () { + done(); + }); + }); + it('should throw an error in context.get if key array contains an object', function (done) { + Context.init({ contextStorage: memoryStorage }); + Context.load().then(function () { + var context = Context.get("1", "flow"); + context.get(["ok1", {}, "ok2"]); + done("should throw an error."); + }).catch(function () { + done(); + }); + }); + it('should throw an error in context.get if key array contains a number', function (done) { + Context.init({ contextStorage: memoryStorage }); + Context.load().then(function () { + var context = Context.get("1", "flow"); + context.get(["ok1", 1, "ok2"]); + done("should throw an error."); + }).catch(function () { + done(); + }); + }); + it('should throw an error in context.set if key is empty string', function (done) { + Context.init({ contextStorage: memoryStorage }); + Context.load().then(function () { + var context = Context.get("1", "flow"); + context.set("", 1); + done("should throw an error."); + }).catch(function () { + done(); + }); + }); + it('should throw an error in context.set if key is an object', function (done) { + Context.init({ contextStorage: memoryStorage }); + Context.load().then(function () { + var context = Context.get("1", "flow"); + context.set({}, 1); + done("should throw an error."); + }).catch(function () { + done(); + }); + }); + it('should throw an error in context.set if key is a number', function (done) { + Context.init({ contextStorage: memoryStorage }); + Context.load().then(function () { + var context = Context.get("1", "flow"); + context.set(1, 1); + done("should throw an error."); + }).catch(function () { + done(); + }); + }); + it('should throw an error in context.set if key array contains an empty string', function (done) { + Context.init({ contextStorage: memoryStorage }); + Context.load().then(function () { + var context = Context.get("1", "flow"); + context.set(["ok1", "", "ok2"], 1); + done("should throw an error."); + }).catch(function () { + done(); + }); + }); + it('should throw an error in context.set if key array contains an object', function (done) { + Context.init({ contextStorage: memoryStorage }); + Context.load().then(function () { + var context = Context.get("1", "flow"); + context.set(["ok1", {}, "ok2"], 1); + done("should throw an error."); + }).catch(function () { + done(); + }); + }); + it('should throw an error in context.set if key array contains a number', function (done) { + Context.init({ contextStorage: memoryStorage }); + Context.load().then(function () { + var context = Context.get("1", "flow"); + context.set(["ok1", 1, "ok2"], 1); + done("should throw an error."); + }).catch(function () { + done(); + }); + }); + + it('should have an err set in callback for invalid key in context.get', function (done) { + Context.init({ contextStorage: memoryStorage }); + Context.load().then(function () { + var context = Context.get("1", "flow"); + context.get("", function(err) { + if(err) { + done(); + } else { + done("should throw an error."); + } + }); + }).catch(done); + }); + + it('should have an err set in callback for invalid key in context.set', function (done) { + Context.init({ contextStorage: memoryStorage }); + Context.load().then(function () { + var context = Context.get("1", "flow"); + context.set("", "value", function(err) { + if(err) { + done(); + } else { + done("should throw an error."); + } + }); + }).catch(done); + }); }); describe('listStores', function () {