diff --git a/index.js b/index.js index 3f60f4c..d5ca1e6 100644 --- a/index.js +++ b/index.js @@ -10,6 +10,12 @@ class ArgError extends Error { } } +// Same acceptance as /^-?\d*(\.(?=\d))?\d*$/ without nested optional +// digit runs that backtrack on long non matching input (ReDoS). +function looksLikeNumber(str) { + return /^-?\d*(?:\.\d+)?$/.test(str); +} + function arg( opts, { @@ -153,7 +159,7 @@ function arg( (argv[i + 1].length > 1 && argv[i + 1][0] === '-' && !( - argv[i + 1].match(/^-?\d*(\.(?=\d))?\d*$/) && + looksLikeNumber(argv[i + 1]) && (type === Number || // eslint-disable-next-line no-undef (typeof BigInt !== 'undefined' && type === BigInt)) diff --git a/test.js b/test.js index 49e638f..d076cc4 100644 --- a/test.js +++ b/test.js @@ -551,3 +551,24 @@ test('should error if negative numeric argument is passed to non-negative argume ) ).to.throw(arg.ArgError, 'option requires argument: --str'); }); + +test('should parse negative decimal numbers as option values', () => { + const argv = ['--num', '-1.5']; + + expect(arg({ '--num': Number }, { argv })).to.deep.equal({ + _: [], + '--num': -1.5 + }); +}); + +test('should reject long non numeric option values without hanging', () => { + const argv = ['--foo', `-${'0'.repeat(20000)}-`]; + const start = Date.now(); + + expect(() => arg({ '--foo': Number }, { argv })).to.throw( + arg.ArgError, + 'option requires argument: --foo' + ); + + expect(Date.now() - start).to.be.below(1000); +});