87 lines
1.8 KiB
JavaScript
87 lines
1.8 KiB
JavaScript
'use strict';
|
|
|
|
|
|
const internals = {
|
|
suspectRx: /"(?:_|\\u005f)(?:_|\\u005f)(?:p|\\u0070)(?:r|\\u0072)(?:o|\\u006f)(?:t|\\u0074)(?:o|\\u006f)(?:_|\\u005f)(?:_|\\u005f)"\s*\:/
|
|
};
|
|
|
|
|
|
exports.parse = function (text, reviver, options) {
|
|
|
|
// Normalize arguments
|
|
|
|
if (!options) {
|
|
if (reviver &&
|
|
typeof reviver === 'object') {
|
|
|
|
options = reviver;
|
|
reviver = undefined;
|
|
}
|
|
else {
|
|
options = {};
|
|
}
|
|
}
|
|
|
|
// Parse normally, allowing exceptions
|
|
|
|
const obj = JSON.parse(text, reviver);
|
|
|
|
// options.protoAction: 'error' (default) / 'remove' / 'ignore'
|
|
|
|
if (options.protoAction === 'ignore') {
|
|
return obj;
|
|
}
|
|
|
|
// Ignore null and non-objects
|
|
|
|
if (!obj ||
|
|
typeof obj !== 'object') {
|
|
|
|
return obj;
|
|
}
|
|
|
|
// Check original string for potential exploit
|
|
|
|
if (!text.match(internals.suspectRx)) {
|
|
return obj;
|
|
}
|
|
|
|
// Scan result for proto keys
|
|
|
|
exports.scan(obj, options);
|
|
|
|
return obj;
|
|
};
|
|
|
|
|
|
exports.scan = function (obj, options) {
|
|
|
|
options = options || {};
|
|
|
|
let next = [obj];
|
|
|
|
while (next.length) {
|
|
const nodes = next;
|
|
next = [];
|
|
|
|
for (const node of nodes) {
|
|
if (Object.prototype.hasOwnProperty.call(node, '__proto__')) { // Avoid calling node.hasOwnProperty directly
|
|
if (options.protoAction !== 'remove') {
|
|
throw new SyntaxError('Object contains forbidden prototype property');
|
|
}
|
|
|
|
delete node.__proto__;
|
|
}
|
|
|
|
for (const key in node) {
|
|
const value = node[key];
|
|
if (value &&
|
|
typeof value === 'object') {
|
|
|
|
next.push(node[key]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|