Skip to content

Commit

Permalink
refactor: tweaks for landing in Node.js (#89)
Browse files Browse the repository at this point in the history
  • Loading branch information
bcoe authored Apr 9, 2022
1 parent 835b17a commit 9549cf9
Show file tree
Hide file tree
Showing 5 changed files with 49 additions and 8 deletions.
8 changes: 8 additions & 0 deletions errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,16 @@ class ERR_INVALID_ARG_TYPE extends TypeError {
}
}

class ERR_INVALID_SHORT_OPTION extends TypeError {
constructor(longOption, shortOption) {
super(`options.${longOption}.short must be a single character, got '${shortOption}'`);
this.code = 'ERR_INVALID_SHORT_OPTION';
}
}

module.exports = {
codes: {
ERR_INVALID_ARG_TYPE,
ERR_INVALID_SHORT_OPTION
}
};
17 changes: 14 additions & 3 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const {
ArrayPrototypeShift,
ArrayPrototypeSlice,
ArrayPrototypePush,
ObjectHasOwn,
ObjectPrototypeHasOwnProperty: ObjectHasOwn,
ObjectEntries,
StringPrototypeCharAt,
StringPrototypeIncludes,
Expand All @@ -32,6 +32,12 @@ const {
isShortOptionGroup
} = require('./utils');

const {
codes: {
ERR_INVALID_SHORT_OPTION,
},
} = require('./errors');

function getMainArgs() {
// This function is a placeholder for proposed process.mainArgs.
// Work out where to slice process.argv for user supplied arguments.
Expand Down Expand Up @@ -66,12 +72,17 @@ function getMainArgs() {
return ArrayPrototypeSlice(process.argv, 2);
}

const protoKey = '__proto__';
function storeOptionValue(options, longOption, value, result) {
const optionConfig = options[longOption] || {};

// Flags
result.flags[longOption] = true;

if (longOption === protoKey) {
return;
}

// Values
if (optionConfig.multiple) {
// Always store value in array, including for flags.
Expand All @@ -97,7 +108,7 @@ const parseArgs = ({
validateObject(options, 'options');
ArrayPrototypeForEach(
ObjectEntries(options),
([longOption, optionConfig]) => {
({ 0: longOption, 1: optionConfig }) => {
validateObject(optionConfig, `options.${longOption}`);

if (ObjectHasOwn(optionConfig, 'type')) {
Expand All @@ -108,7 +119,7 @@ const parseArgs = ({
const shortOption = optionConfig.short;
validateString(shortOption, `options.${longOption}.short`);
if (shortOption.length !== 1) {
throw new Error(`options.${longOption}.short must be a single character, got '${shortOption}'`);
throw new ERR_INVALID_SHORT_OPTION(longOption, shortOption);
}
}

Expand Down
4 changes: 3 additions & 1 deletion test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,9 @@ test('invalid short option length', function(t) {
const passedArgs = [];
const passedOptions = { foo: { short: 'fo' } };

t.throws(function() { parseArgs({ args: passedArgs, options: passedOptions }); });
t.throws(function() { parseArgs({ args: passedArgs, options: passedOptions }); }, {
code: 'ERR_INVALID_SHORT_OPTION'
});

t.end();
});
15 changes: 15 additions & 0 deletions test/prototype-pollution.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
'use strict';
/* eslint max-len: 0 */

const test = require('tape');
const { parseArgs } = require('../index.js');

test('should not allow __proto__ key to be set on object', (t) => {
const passedArgs = ['--__proto__=hello'];
const expected = { flags: {}, values: {}, positionals: [] };

const result = parseArgs({ args: passedArgs });

t.deepEqual(result, expected);
t.end();
});
13 changes: 9 additions & 4 deletions utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ const {
StringPrototypeStartsWith,
} = require('./primordials');

const {
validateObject
} = require('./validators');

// These are internal utilities to make the parsing logic easier to read, and
// add lots of detail for the curious. They are in a separate file to allow
// unit testing, although that is not essential (this could be rolled into
Expand Down Expand Up @@ -116,7 +120,8 @@ function isShortOptionGroup(arg, options) {
* }) // returns true
*/
function isShortOptionAndValue(arg, options) {
if (!options) throw new Error('Internal error, missing options argument');
validateObject(options, 'options');

if (arg.length <= 2) return false;
if (StringPrototypeCharAt(arg, 0) !== '-') return false;
if (StringPrototypeCharAt(arg, 1) === '-') return false;
Expand All @@ -136,10 +141,10 @@ function isShortOptionAndValue(arg, options) {
* }) // returns 'bar'
*/
function findLongOptionForShort(shortOption, options) {
if (!options) throw new Error('Internal error, missing options argument');
const [longOption] = ArrayPrototypeFind(
validateObject(options, 'options');
const { 0: longOption } = ArrayPrototypeFind(
ObjectEntries(options),
([, optionConfig]) => optionConfig.short === shortOption
({ 1: optionConfig }) => optionConfig.short === shortOption
) || [];
return longOption || shortOption;
}
Expand Down

0 comments on commit 9549cf9

Please sign in to comment.