diff --git a/index.d.ts b/index.d.ts index 72981c5..843f8f9 100644 --- a/index.d.ts +++ b/index.d.ts @@ -108,3 +108,26 @@ console.log(object); ``` */ export function deleteProperty(object: Record, path: string): boolean; + +/** +Escape special characters in a path. Useful for sanitizing user input. + +@param path - The dot path to sanitize. + +@example +``` +import {getProperty, escapePath} from 'dot-prop'; + +const object = { + foo: { + bar: 'πŸ‘ΈπŸ» You found me Mario!', + }, + 'foo.bar' : 'πŸ„ The princess is in another castle!', +}; +const escapedPath = escapePath('foo.bar'); + +console.log(getProperty(object, escapedPath)); +//=> 'πŸ„ The princess is in another castle!' +``` +*/ +export function escapePath(path: string): string; diff --git a/index.js b/index.js index 0f4f053..ab670f5 100644 --- a/index.js +++ b/index.js @@ -281,3 +281,11 @@ export function hasProperty(object, path) { return true; } + +export function escapePath(path) { + if (typeof path !== 'string') { + throw new TypeError('Expected a string'); + } + + return path.replace(/[\\.[]/g, '\\$&'); +} diff --git a/readme.md b/readme.md index 8eb58e8..828d09c 100644 --- a/readme.md +++ b/readme.md @@ -89,6 +89,25 @@ Delete the property at the given path. Returns a boolean of whether the property existed before being deleted. +### escapePath(path) + +Escape special characters in a path. Useful for sanitizing user input. + +```js +import {getProperty, escapePath} from 'dot-prop'; + +const object = { + foo: { + bar: 'πŸ‘ΈπŸ» You found me Mario!', + }, + 'foo.bar' : 'πŸ„ The princess is in another castle!', +}; +const escapedPath = escapePath('foo.bar'); + +console.log(getProperty(object, escapedPath)); +//=> 'πŸ„ The princess is in another castle!' +``` + #### object Type: `object | array` diff --git a/test.js b/test.js index 56e8e7e..0430e58 100644 --- a/test.js +++ b/test.js @@ -1,5 +1,5 @@ import test from 'ava'; -import {getProperty, setProperty, hasProperty, deleteProperty} from './index.js'; +import {getProperty, setProperty, hasProperty, deleteProperty, escapePath} from './index.js'; test('getProperty', t => { const fixture1 = {foo: {bar: 1}}; @@ -383,6 +383,25 @@ test('hasProperty', t => { }, 'foo[0].bar.1')); }); +test('escapePath', t => { + t.is(escapePath('foo.bar[0]'), 'foo\\.bar\\[0]'); + t.is(escapePath('foo\\.bar[0]'), 'foo\\\\\\.bar\\[0]'); + t.is(escapePath('foo\\\.bar[0]'), 'foo\\\\\\.bar\\[0]'); // eslint-disable-line no-useless-escape + t.is(escapePath('foo\\\\.bar[0]'), 'foo\\\\\\\\\\.bar\\[0]'); + t.is(escapePath('foo\\\\.bar\\\\[0]'), 'foo\\\\\\\\\\.bar\\\\\\\\\\[0]'); + t.is(escapePath('foo[0].bar'), 'foo\\[0]\\.bar'); + t.is(escapePath('foo.bar[0].baz'), 'foo\\.bar\\[0]\\.baz'); + t.is(escapePath('[0].foo'), '\\[0]\\.foo'); + t.is(escapePath(''), ''); + + t.throws(() => { + escapePath(0); + }, { + instanceOf: TypeError, + message: 'Expected a string', + }); +}); + test('prevent setting/getting `__proto__`', t => { setProperty({}, '__proto__.unicorn', 'πŸ¦„'); t.not({}.unicorn, 'πŸ¦„'); // eslint-disable-line no-use-extend-native/no-use-extend-native