From c0d76dd24ecca0d5af05a0b5fd2307f9e8602b46 Mon Sep 17 00:00:00 2001 From: Damien Lebrun Date: Sat, 7 Oct 2017 12:24:28 +0200 Subject: [PATCH] fix: validate path Validate path navigation rules or node and when creating/updating data nodes. Fixes #131 --- lib/database/ruleset.js | 5 ++++ lib/database/store.js | 26 +++++++++++++----- lib/paths.js | 12 +++++++++ test/spec/lib/database/ruleset.js | 20 +++++++++++++- test/spec/lib/database/store.js | 45 +++++++++++++++++++++++++++++++ 5 files changed, 100 insertions(+), 8 deletions(-) diff --git a/lib/database/ruleset.js b/lib/database/ruleset.js index 0a5032f..13a48f3 100644 --- a/lib/database/ruleset.js +++ b/lib/database/ruleset.js @@ -336,6 +336,8 @@ class RulesetNode { * @return {{child: RulesetNode, wildchildren: object}|void} */ $child(name, wildchildren) { + paths.mustBeValid(name); + wildchildren = wildchildren || {}; const parts = paths.split(name); @@ -370,6 +372,9 @@ class RulesetNode { * @param {function(path: string, rules: RulesetNode, wildchildren: object): boolean} cb Receive each node traversed. */ $traverse(path, wildchildren, cb) { + + paths.mustBeValid(path); + let currentPath = ''; let currentRules = this; diff --git a/lib/database/store.js b/lib/database/store.js index 4abbac5..abb06d9 100644 --- a/lib/database/store.js +++ b/lib/database/store.js @@ -110,15 +110,21 @@ class DataNode { const keys = value['.value'] === undefined ? Object.keys(value) : []; - keys.filter(isValidKey).forEach(key => { - const childNode = DataNode.from(value[key], undefined, now); + keys + .filter(key => key !== '.value' && key !== '.priority') + .forEach(key => { + if (!isValidKey(key)) { + throw new Error(`Invalid key "${key}"; it must not include [${invalidChar.join(',')}]`); + } - if (childNode.$isNull()) { - return; - } + const childNode = DataNode.from(value[key], undefined, now); - this[key] = childNode; - }); + if (childNode.$isNull()) { + return; + } + + this[key] = childNode; + }); if (this[valueKey] === undefined && Object.keys(this).length === 0) { this[valueKey] = null; @@ -259,6 +265,8 @@ class DataNode { * @return {DataNode} */ $child(path) { + paths.mustBeValid(path); + return paths.split(path).reduce( (parent, key) => parent[key] || DataNode.null(), this @@ -276,6 +284,8 @@ class DataNode { */ $set(path, value, priority, now) { + paths.mustBeValid(path); + path = paths.trim(path); now = now || Date.now(); @@ -343,6 +353,8 @@ class DataNode { */ $remove(path, now) { + paths.mustBeValid(path); + path = paths.trim(path); if (!path) { diff --git a/lib/paths.js b/lib/paths.js index c19c0f9..f885c8c 100644 --- a/lib/paths.js +++ b/lib/paths.js @@ -4,6 +4,18 @@ 'use strict'; +const invalidChar = ['.', '#', '$', '[', ']']; + +exports.isValid = function(path) { + return invalidChar.some(char => path.includes(char)) === false; +}; + +exports.mustBeValid = function(path) { + if (!exports.isValid(path)) { + throw new Error(`Invalid location "${path}" contains one of [${invalidChar.join(', ')}]`); + } +}; + exports.trimLeft = function(path) { path = path || ''; diff --git a/test/spec/lib/database/ruleset.js b/test/spec/lib/database/ruleset.js index 7717a13..3c75453 100644 --- a/test/spec/lib/database/ruleset.js +++ b/test/spec/lib/database/ruleset.js @@ -23,7 +23,7 @@ const invalidRulesets = { }, otherStuff: true }, - 'include invalid index': { + 'includes an invalid index': { rules: { '.read': true, '.indexOn': true @@ -267,6 +267,15 @@ describe('Ruleset', function() { expect(child.wildchildren).to.eql({$b: 'foo'}); }); + describe('should throw when the name includes an invalid character:', function() { + const rules = ruleset.create({rules: {'.read': true}}); + + ['.', '#', '$', '[', ']'].forEach(char => { + it(`e.g. using "${char}"`, () => expect(() => rules.root.$child(`ab/c${char}d`)).to.throw()); + }); + + }); + }); describe('#$traverse', function() { @@ -400,6 +409,15 @@ describe('Ruleset', function() { expect(cb).to.have.callCount(2); }); + describe('should throw when the path includes an invalid character:', function() { + const rules = ruleset.create({rules: {'.read': true}}); + + ['.', '#', '$', '[', ']'].forEach(char => { + it(`e.g. using "${char}"`, () => expect(() => rules.root.$traverse(`a/b${char}c`, () => {})).to.throw()); + }); + + }); + }); }); diff --git a/test/spec/lib/database/store.js b/test/spec/lib/database/store.js index eda2589..1d0a97b 100644 --- a/test/spec/lib/database/store.js +++ b/test/spec/lib/database/store.js @@ -69,6 +69,16 @@ describe('store', function() { }); }); + describe('should throw when creating a node with an invalid name:', function() { + + ['.', '#', '$', '[', ']'].forEach(char => { + const data = {[`a${char}c`]: 1}; + + it(`e.g. using "${char}"`, () => expect(() => store.create(data)).to.throw()); + }); + + }); + describe('server value replacement', function() { it('should handle time stamps', function() { @@ -222,6 +232,14 @@ describe('store', function() { expect(newRoot).not.to.have.property('b'); }); + describe('should throw when the path includes an invalid character:', function() { + + ['.', '#', '$', '[', ']'].forEach(char => { + it(`e.g. using "${char}"`, () => expect(() => data.$set(`a${char}c`, 1)).to.throw()); + }); + + }); + }); describe('#$merge', function() { @@ -271,6 +289,17 @@ describe('store', function() { expect(data.$merge('b/c', {})).to.equal(data); }); + describe('should throw when the path includes an invalid character:', function() { + + ['.', '#', '$', '[', ']'].forEach(char => { + it(`e.g. using "${char}"`, () => { + expect(() => data.$merge('/', {a: 3, [`b/c/a${char}c`]: 4})).to.throw(); + expect(() => data.$merge(`/a${char}c`, {a: 3})).to.throw(); + }); + }); + + }); + }); describe('#$remove', function() { @@ -329,6 +358,14 @@ describe('store', function() { expect(newRoot.$value()).to.eql({a: {b: {d: true}, e: true}}); }); + describe('should throw when the path includes an invalid character:', function() { + + ['.', '#', '$', '[', ']'].forEach(char => { + it(`e.g. using "${char}"`, () => expect(() => data.$remove(`/a${char}c`)).to.throw()); + }); + + }); + }); describe('#$child', function() { @@ -346,6 +383,14 @@ describe('store', function() { expect(data.$child('foo/bar').$value()).to.equal(null); }); + describe('should throw when the path includes an invalid character:', function() { + + ['.', '#', '$', '[', ']'].forEach(char => { + it(`e.g. using "${char}"`, () => expect(() => data.$child(`a${char}c`)).to.throw()); + }); + + }); + }); });