From 3e7e1cebfd237ed7a41f5f24cabd84b6db8fd625 Mon Sep 17 00:00:00 2001 From: jos Date: Wed, 20 Mar 2019 17:34:39 +0100 Subject: [PATCH] Fixed #676: JSON Paths containing array properties with a `]` not parsed correctly --- HISTORY.md | 2 + src/js/polyfills.js | 7 ++++ src/js/util.js | 92 ++++++++++++++++++++++++++++++--------------- test/util.test.js | 15 +++++--- 4 files changed, 80 insertions(+), 36 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index c82a457..228d2ad 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -9,6 +9,8 @@ https://github.com/josdejong/jsoneditor styling can be applied for default and non-default values. Thanks @AdamVig. - Fixed #667: resolving JSON Schema examples and descriptions did not always work for referenced schemas. Thanks @AdamVig. +- Fixed #676: JSON Paths containing array properties with a `]` not parsed + correctly. ## 2019-03-14, version 5.31.1 diff --git a/src/js/polyfills.js b/src/js/polyfills.js index b522cc4..db68ce5 100644 --- a/src/js/polyfills.js +++ b/src/js/polyfills.js @@ -43,3 +43,10 @@ if (!Array.prototype.find) { } } } + +// Polyfill for String.trim +if (!String.prototype.trim) { + String.prototype.trim = function () { + return this.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, ''); + }; +} diff --git a/src/js/util.js b/src/js/util.js index 493dd15..ea09ef6 100644 --- a/src/js/util.js +++ b/src/js/util.js @@ -742,43 +742,75 @@ exports.isChildOf = function (elem, parent) { * @return {Array} */ exports.parsePath = function parsePath(jsonPath) { - var prop, remainder; + var path = []; + var i = 0; - if (jsonPath.length === 0) { - return []; - } - - // find a match like '.prop' - var match = jsonPath.match(/^\.([\w$]+)/); - if (match) { - prop = match[1]; - remainder = jsonPath.substr(prop.length + 1); - } - else if (jsonPath[0] === '[') { - // find a match like - var end = jsonPath.indexOf(']'); - if (end === -1) { - throw new SyntaxError('Character ] expected in path'); - } - if (end === 1) { - throw new SyntaxError('Index expected after ['); + function parseProperty () { + var prop = '' + while (jsonPath[i] !== undefined && /[\w$]/.test(jsonPath[i])) { + prop += jsonPath[i]; + i++; } - var value = jsonPath.substring(1, end); - if (value[0] === '\'') { - // ajv produces string prop names with single quotes, so we need - // to reformat them into valid double-quoted JSON strings - value = '\"' + value.substring(1, value.length - 1) + '\"'; + if (prop === '') { + throw new Error('Invalid JSON path: property name expected at index ' + i); } - prop = value === '*' ? value : JSON.parse(value); // parse string and number - remainder = jsonPath.substr(end + 1); - } - else { - throw new SyntaxError('Failed to parse path'); + return prop; } - return [prop].concat(parsePath(remainder)) + function parseIndex (end) { + var name = '' + while (jsonPath[i] !== undefined && jsonPath[i] !== end) { + name += jsonPath[i]; + i++; + } + + if (jsonPath[i] !== end) { + throw new Error('Invalid JSON path: unexpected end, character ' + end + ' expected') + } + + return name; + } + + while (jsonPath[i] !== undefined) { + if (jsonPath[i] === '.') { + i++; + path.push(parseProperty()); + } + else if (i > 0 && jsonPath[i] === '[') { + i++; + + if (jsonPath[i] === '\'' || jsonPath[i] === '"') { + var end = jsonPath[i] + i++; + + path.push(parseIndex(end)); + + if (jsonPath[i] !== end) { + throw new Error('Invalid JSON path: closing quote \' expected at index ' + i) + } + i++; + } + else { + var index = parseIndex(']').trim() + if (index.length === 0) { + throw new Error('Invalid JSON path: array value expected at index ' + i) + } + path.push(index); + } + + if (jsonPath[i] !== ']') { + throw new Error('Invalid JSON path: closing bracket ] expected at index ' + i) + } + i++; + } + else { + throw new Error('Invalid JSON path: unexpected character "' + jsonPath[i] + '" at index ' + i); + } + } + + return path; }; /** diff --git a/test/util.test.js b/test/util.test.js index 6a62a7d..7bbfab4 100644 --- a/test/util.test.js +++ b/test/util.test.js @@ -105,16 +105,19 @@ describe('util', function () { assert.deepEqual(util.parsePath('.foo[2].bar'), ['foo', 2, 'bar']); assert.deepEqual(util.parsePath('.foo["prop with spaces"]'), ['foo', 'prop with spaces']); assert.deepEqual(util.parsePath('.foo[\'prop with single quotes as outputted by ajv library\']'), ['foo', 'prop with single quotes as outputted by ajv library']); + assert.deepEqual(util.parsePath('.foo["prop with . dot"]'), ['foo', 'prop with . dot']); + assert.deepEqual(util.parsePath('.foo["prop with ] character"]'), ['foo', 'prop with ] character']); assert.deepEqual(util.parsePath('.foo[*].bar'), ['foo', '*', 'bar']); }); it ('should throw an exception in case of an invalid path', function () { - assert.throws(function () {util.parsePath('.')}, /Error/); - assert.throws(function () {util.parsePath('[')}, /Error/); - assert.throws(function () {util.parsePath('[]')}, /Error/); - assert.throws(function () {util.parsePath('.[]')}, /Error/); - assert.throws(function () {util.parsePath('["23]')}, /Error/); - assert.throws(function () {util.parsePath('.foo bar')}, /Error/); + assert.throws(function () {util.parsePath('.')}, /Invalid JSON path: property name expected at index 1/); + assert.throws(function () {util.parsePath('[')}, /Invalid JSON path: unexpected character "\[" at index 0/); + assert.throws(function () {util.parsePath('[]')}, /Invalid JSON path: unexpected character "\[" at index 0/); + assert.throws(function () {util.parsePath('.foo[ ]')}, /Invalid JSON path: array value expected at index 7/); + assert.throws(function () {util.parsePath('.[]')}, /Invalid JSON path: property name expected at index 1/); + assert.throws(function () {util.parsePath('["23]')}, /Invalid JSON path: unexpected character "\[" at index 0/); + assert.throws(function () {util.parsePath('.foo bar')}, /Invalid JSON path: unexpected character " " at index 4/); }); });