Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: back-porting prototype fixes for *really* old version #271

Merged
merged 3 commits into from May 22, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
68 changes: 31 additions & 37 deletions index.js
Expand Up @@ -2,14 +2,15 @@ var camelCase = require('camelcase')
var path = require('path')
var tokenizeArgString = require('./lib/tokenize-arg-string')
var util = require('util')
var assign = require('object.assign')

function parse (args, opts) {
if (!opts) opts = {}
if (!opts) opts = Object.create(null)
// allow a string argument to be passed in rather
// than an argv array.
args = tokenizeArgString(args)
// aliases might have transitive relationships, normalize this.
var aliases = combineAliases(opts.alias || {})
var aliases = combineAliases(opts.alias || Object.create(null))
var configuration = assign({
'short-option-groups': true,
'camel-case-expansion': true,
Expand All @@ -19,27 +20,27 @@ function parse (args, opts) {
'duplicate-arguments-array': true,
'flatten-duplicate-arrays': true
}, opts.configuration)
var defaults = opts.default || {}
var defaults = opts.default || Object.create(null)
var configObjects = opts.configObjects || []
var envPrefix = opts.envPrefix
var newAliases = {}
var newAliases = Object.create(null)
// allow a i18n handler to be passed in, default to a fake one (util.format).
var __ = opts.__ || function (str) {
return util.format.apply(util, Array.prototype.slice.call(arguments))
}
var error = null
var flags = {
aliases: {},
arrays: {},
bools: {},
strings: {},
numbers: {},
counts: {},
normalize: {},
configs: {},
defaulted: {},
nargs: {},
coercions: {}
aliases: Object.create(null),
arrays: Object.create(null),
bools: Object.create(null),
strings: Object.create(null),
numbers: Object.create(null),
counts: Object.create(null),
normalize: Object.create(null),
configs: Object.create(null),
defaulted: Object.create(null),
nargs: Object.create(null),
coercions: Object.create(null)
}
var negative = /^-[0-9]+(\.[0-9]+)?/

Expand Down Expand Up @@ -67,11 +68,11 @@ function parse (args, opts) {
flags.normalize[key] = true
})

Object.keys(opts.narg || {}).forEach(function (k) {
Object.keys(opts.narg || Object.create(null)).forEach(function (k) {
flags.nargs[k] = opts.narg[k]
})

Object.keys(opts.coerce || {}).forEach(function (k) {
Object.keys(opts.coerce || Object.create(null)).forEach(function (k) {
flags.coercions[k] = opts.coerce[k]
})

Expand All @@ -80,7 +81,7 @@ function parse (args, opts) {
flags.configs[key] = true
})
} else {
Object.keys(opts.config || {}).forEach(function (k) {
Object.keys(opts.config || Object.create(null)).forEach(function (k) {
flags.configs[k] = opts.config[k]
})
}
Expand Down Expand Up @@ -417,7 +418,7 @@ function parse (args, opts) {
// set args from config.json file, this should be
// applied last so that defaults can be applied.
function setConfig (argv) {
var configLookup = {}
var configLookup = Object.create(null)

// expand defaults/aliases, in-case any happen to reference
// the config.json file.
Expand Down Expand Up @@ -537,7 +538,7 @@ function parse (args, opts) {
if (!configuration['dot-notation']) keys = [keys.join('.')]

keys.slice(0, -1).forEach(function (key) {
o = (o[key] || {})
o = (o[key] || Object.create(null))
})

var key = keys[keys.length - 1]
Expand All @@ -551,8 +552,10 @@ function parse (args, opts) {

if (!configuration['dot-notation']) keys = [keys.join('.')]

keys = keys.map(sanitizeKey)

keys.slice(0, -1).forEach(function (key) {
if (o[key] === undefined) o[key] = {}
if (o[key] === undefined) o[key] = Object.create(null)
o = o[key]
})

Expand Down Expand Up @@ -584,7 +587,7 @@ function parse (args, opts) {
// extend the aliases list with inferred aliases.
function extendAliases () {
Array.prototype.slice.call(arguments).forEach(function (obj) {
Object.keys(obj || {}).forEach(function (key) {
Object.keys(obj || Object.create(null)).forEach(function (key) {
// short-circuit if we've already added a key
// to the aliases array, for example it might
// exist in both 'opts.default' and 'opts.key'.
Expand Down Expand Up @@ -681,7 +684,7 @@ function parse (args, opts) {
function combineAliases (aliases) {
var aliasArrays = []
var change = true
var combined = {}
var combined = Object.create(null)

// turn alias lookup hash {key: ['alias1', 'alias2']} into
// a simple array ['key', 'alias1', 'alias2']
Expand Down Expand Up @@ -723,27 +726,18 @@ function combineAliases (aliases) {
return combined
}

function assign (defaults, configuration) {
var o = {}
configuration = configuration || {}

Object.keys(defaults).forEach(function (k) {
o[k] = defaults[k]
})
Object.keys(configuration).forEach(function (k) {
o[k] = configuration[k]
})

return o
}

// this function should only be called when a count is given as an arg
// it is NOT called to set a default value
// thus we can start the count at 1 instead of 0
function increment (orig) {
return orig !== undefined ? orig + 1 : 1
}

function sanitizeKey (key) {
if (key === '__proto__') return '___proto___'
return key
}

function Parser (args, opts) {
var result = parse(args.slice(), opts)

Expand Down
3 changes: 2 additions & 1 deletion package.json
Expand Up @@ -34,7 +34,8 @@
"standard-version": "^4.0.0"
},
"dependencies": {
"camelcase": "^3.0.0"
"camelcase": "^3.0.0",
"object.assign": "^4.1.0"
},
"files": [
"lib",
Expand Down
11 changes: 10 additions & 1 deletion test/fixtures/config.json
Expand Up @@ -3,5 +3,14 @@
"z": 55,
"foo": "baz",
"version": "1.0.2",
"truthy": true
"truthy": true,
"toString": "method name",
"__proto__": {
"aaa": 99
},
"bar": {
"__proto__": {
"bbb": 100
}
}
}
28 changes: 28 additions & 0 deletions test/yargs-parser.js
Expand Up @@ -410,6 +410,26 @@ describe('yargs-parser', function () {
describe('config', function () {
var jsonPath = path.resolve(__dirname, './fixtures/config.json')

// Patching for https://snyk.io/vuln/SNYK-JS-YARGSPARSER-560381
it('should not pollute the prototype', function () {
const argv = parser(['--foo', 'bar'], {
alias: {
z: 'zoom'
},
default: {
settings: jsonPath
},
config: 'settings'
})

argv.should.have.property('herp', 'derp')
argv.should.have.property('zoom', 55)
argv.should.have.property('foo').and.deep.equal('bar')

expect({}.bbb).to.equal(undefined)
expect({}.aaa).to.equal(undefined)
})

// See: https://github.com/chevex/yargs/issues/12
it('should load options and values from default config if specified', function () {
var argv = parser([ '--foo', 'bar' ], {
Expand Down Expand Up @@ -2375,4 +2395,12 @@ describe('yargs-parser', function () {
})
argv.a.should.deep.equal(['a.txt', 'b.txt'])
})

// Patching for https://snyk.io/vuln/SNYK-JS-YARGSPARSER-560381
it('should not pollute the prototype', function () {
parser(['-f.__proto__.foo', '99', '-x.y.__proto__.bar', '100', '--__proto__', '200'])
Object.keys({}.__proto__).length.should.equal(0) // eslint-disable-line
expect({}.foo).to.equal(undefined)
expect({}.bar).to.equal(undefined)
})
})