util: prevent tampering with internals in inspect()

This makes sure user options passed to `util.inspect()` will not
override any internal properties (besides `stylize`).

PR-URL: https://github.com/nodejs/node/pull/26577
Fixes: https://github.com/nodejs/node/issues/24765
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Rich Trott <rtrott@gmail.com>
This commit is contained in:
Ruben Bridgewater 2019-03-10 23:59:49 +01:00
parent 32853c0a13
commit 5672ab7668
No known key found for this signature in database
GPG Key ID: F07496B3EB3C1762
2 changed files with 31 additions and 9 deletions

View File

@ -150,6 +150,16 @@ const meta = [
'', '', '', '', '', '', '', '\\\\'
];
function getUserOptions(ctx) {
const obj = { stylize: ctx.stylize };
for (const key of Object.keys(inspectDefaultOptions)) {
obj[key] = ctx[key];
}
if (ctx.userOptions === undefined)
return obj;
return { ...obj, ...ctx.userOptions };
}
/**
* Echos the value of any input. Tries to print the value out
* in the best way possible given the different types.
@ -192,8 +202,16 @@ function inspect(value, opts) {
ctx.showHidden = opts;
} else if (opts) {
const optKeys = Object.keys(opts);
for (var i = 0; i < optKeys.length; i++) {
ctx[optKeys[i]] = opts[optKeys[i]];
for (const key of optKeys) {
// TODO(BridgeAR): Find a solution what to do about stylize. Either make
// this function public or add a new API with a similar or better
// functionality.
if (hasOwnProperty(inspectDefaultOptions, key) || key === 'stylize') {
ctx[key] = opts[key];
} else if (ctx.userOptions === undefined) {
// This is required to pass through the actual user input.
ctx.userOptions = opts;
}
}
}
}
@ -522,14 +540,10 @@ function formatValue(ctx, value, recurseTimes, typedArray) {
maybeCustom !== inspect &&
// Also filter out any prototype objects using the circular check.
!(value.constructor && value.constructor.prototype === value)) {
// Remove some internal properties from the options before passing it
// through to the user function. This also prevents option manipulation.
// eslint-disable-next-line no-unused-vars
const { budget, seen, indentationLvl, ...plainCtx } = ctx;
// This makes sure the recurseTimes are reported as before while using
// a counter internally.
const depth = ctx.depth === null ? null : ctx.depth - recurseTimes;
const ret = maybeCustom.call(context, depth, plainCtx);
const ret = maybeCustom.call(context, depth, getUserOptions(ctx));
// If the custom inspection method returned `this`, don't go into
// infinite recursion.
if (ret !== context) {

View File

@ -791,7 +791,7 @@ util.inspect({ hasOwnProperty: null });
}) };
});
util.inspect(subject, { customInspectOptions: true });
util.inspect(subject);
// util.inspect.custom is a shared symbol which can be accessed as
// Symbol.for("nodejs.util.inspect.custom").
@ -803,9 +803,11 @@ util.inspect({ hasOwnProperty: null });
subject[inspect] = (depth, opts) => {
assert.strictEqual(opts.customInspectOptions, true);
assert.strictEqual(opts.seen, null);
return {};
};
util.inspect(subject, { customInspectOptions: true });
util.inspect(subject, { customInspectOptions: true, seen: null });
}
{
@ -816,6 +818,12 @@ util.inspect({ hasOwnProperty: null });
`{ a: 123,\n [Symbol(${UIC})]: [Function: [${UIC}]] }`);
}
// Verify that it's possible to use the stylize function to manipulate input.
assert.strictEqual(
util.inspect([1, 2, 3], { stylize() { return 'x'; } }),
'[ x, x, x ]'
);
// Using `util.inspect` with "colors" option should produce as many lines as
// without it.
{