diff --git a/index.js b/index.js index 7508655..bf55ca7 100755 --- a/index.js +++ b/index.js @@ -10,14 +10,9 @@ function parse (text, reviver, options) { if (reviver !== null && typeof reviver === 'object') { options = reviver reviver = undefined - } else { - options = {} } } - const protoAction = options.protoAction || 'error' - const constructorAction = options.constructorAction || 'error' - if (hasBuffer && Buffer.isBuffer(text)) { text = text.toString() } @@ -30,13 +25,16 @@ function parse (text, reviver, options) { // Parse normally, allowing exceptions const obj = JSON.parse(text, reviver) - // options: 'error' (default) / 'remove' / 'ignore' - if (protoAction === 'ignore' && constructorAction === 'ignore') { + // Ignore null and non-objects + if (obj === null || typeof obj !== 'object') { return obj } - // Ignore null and non-objects - if (obj === null || typeof obj !== 'object') { + const protoAction = (options && options.protoAction) || 'error' + const constructorAction = (options && options.constructorAction) || 'error' + + // options: 'error' (default) / 'remove' / 'ignore' + if (protoAction === 'ignore' && constructorAction === 'ignore') { return obj } @@ -55,12 +53,10 @@ function parse (text, reviver, options) { } // Scan result for proto keys - scan(obj, { protoAction, constructorAction }) - - return obj + return filter(obj, { protoAction, constructorAction, safe: options && options.safe }) } -function scan (obj, { protoAction = 'error', constructorAction = 'error' } = {}) { +function filter (obj, { protoAction = 'error', constructorAction = 'error', safe } = {}) { let next = [obj] while (next.length) { @@ -69,7 +65,9 @@ function scan (obj, { protoAction = 'error', constructorAction = 'error' } = {}) for (const node of nodes) { if (protoAction !== 'ignore' && Object.prototype.hasOwnProperty.call(node, '__proto__')) { // Avoid calling node.hasOwnProperty directly - if (protoAction === 'error') { + if (safe === true) { + return null + } else if (protoAction === 'error') { throw new SyntaxError('Object contains forbidden prototype property') } @@ -79,7 +77,9 @@ function scan (obj, { protoAction = 'error', constructorAction = 'error' } = {}) if (constructorAction !== 'ignore' && Object.prototype.hasOwnProperty.call(node, 'constructor') && Object.prototype.hasOwnProperty.call(node.constructor, 'prototype')) { // Avoid calling node.hasOwnProperty directly - if (constructorAction === 'error') { + if (safe === true) { + return null + } else if (constructorAction === 'error') { throw new SyntaxError('Object contains forbidden prototype property') } @@ -89,16 +89,17 @@ function scan (obj, { protoAction = 'error', constructorAction = 'error' } = {}) for (const key in node) { const value = node[key] if (value && typeof value === 'object') { - next.push(node[key]) + next.push(value) } } } } + return obj } function safeParse (text, reviver) { try { - return parse(text, reviver) + return parse(text, reviver, { safe: true }) } catch (ignoreError) { return null } @@ -106,6 +107,6 @@ function safeParse (text, reviver) { module.exports = { parse, - scan, + scan: filter, safeParse } diff --git a/types/index.d.ts b/types/index.d.ts index 0c15f9e..fee2eb9 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -5,30 +5,17 @@ export type ParseOptions = { * - `'remove'` - deletes any `__proto__` keys from the result object. * - `'ignore'` - skips all validation (same as calling `JSON.parse()` directly). */ - protoAction?: 'error' | 'remove' | 'ignore', + protoAction?: 'error' | 'remove' | 'ignore'; /** * What to do when a `constructor` key is found. * - `'error'` - throw a `SyntaxError` when a `constructor.prototype` key is found. This is the default value. * - `'remove'` - deletes any `constructor` keys from the result object. * - `'ignore'` - skips all validation (same as calling `JSON.parse()` directly). */ - constructorAction?: 'error' | 'remove' | 'ignore', + constructorAction?: 'error' | 'remove' | 'ignore'; } -export type ScanOptions = { - /** - * What to do when a `__proto__` key is found. - * - `'error'` - throw a `SyntaxError` when a `__proto__` key is found. This is the default value. - * - `'remove'` - deletes any `__proto__` keys from the input `obj`. - */ - protoAction?: 'error' | 'remove', - /** - * What to do when a `constructor` key is found. - * - `'error'` - throw a `SyntaxError` when a `constructor.prototype` key is found. This is the default value. - * - `'remove'` - deletes any `constructor` keys from the input `obj`. - */ - constructorAction?: 'error' | 'remove', -} +export type ScanOptions = ParseOptions type Reviver = (this: any, key: string, value: any) => any @@ -56,5 +43,6 @@ export function safeParse(text: string | Buffer, reviver?: Reviver | null): any * * @param obj The object being scanned. * @param options Optional configuration object. + * @returns The object, or `null` if onError is set to `nullify` */ -export function scan(obj: any, options?: ScanOptions): void +export function scan(obj: {[key: string | number]: any }, options?: ParseOptions): any diff --git a/types/index.test-d.ts b/types/index.test-d.ts index cfccbc9..200ad9c 100644 --- a/types/index.test-d.ts +++ b/types/index.test-d.ts @@ -13,10 +13,11 @@ sjson.safeParse('"test"', null) sjson.safeParse('"test"') expectError(sjson.safeParse(null)) -sjson.scan('"test"', { protoAction: 'remove' }) -expectError(sjson.scan('"test"', { protoAction: 'ignore' })) -sjson.scan('"test"', { constructorAction: 'error' }) -expectError(sjson.scan('"test"', { constructorAction: 'ignore' })) +sjson.scan({}, { protoAction: 'remove' }) +sjson.scan({}, { protoAction: 'ignore' }) +sjson.scan({}, { constructorAction: 'error' }) +sjson.scan({}, { constructorAction: 'ignore' }) +sjson.scan(new Array(), {}) declare const input: Buffer sjson.parse(input)