From 11494f1446a907c8fa5d9cfbc9fab04d553311f5 Mon Sep 17 00:00:00 2001 From: Luke Karrys Date: Fri, 25 Mar 2022 11:30:08 -0700 Subject: [PATCH] fix: properly escape dots in `GTE0` regexes (#432) Previously the dots were not properly escaped causing them to match any character. This caused ranges like `>=09090` to evaulate to `*` after they were incorrectly matched by the `GTE0` regex. This only happened in strict mode since in loose mode the leading 0 is allowed and parsed into `>=9090.0.0` before the `GTE0` check. After this fix, this range now will throw an error. This also affected prerelease versions in both strict and loose mode. --- classes/range.js | 19 ++++++++++++------- internal/re.js | 6 +++--- test/classes/range.js | 4 ++-- test/fixtures/range-parse.js | 4 ++++ test/ranges/valid.js | 2 +- 5 files changed, 22 insertions(+), 13 deletions(-) diff --git a/classes/range.js b/classes/range.js index 9e04555c..35db5072 100644 --- a/classes/range.js +++ b/classes/range.js @@ -94,7 +94,7 @@ class Range { debug('hyphen replace', range) // `> 1.2.3 < 1.2.5` => `>1.2.3 <1.2.5` range = range.replace(re[t.COMPARATORTRIM], comparatorTrimReplace) - debug('comparator trim', range, re[t.COMPARATORTRIM]) + debug('comparator trim', range) // `~ 1.2.3` => `~1.2.3` range = range.replace(re[t.TILDETRIM], tildeTrimReplace) @@ -108,24 +108,29 @@ class Range { // At this point, the range is completely trimmed and // ready to be split into comparators. - const compRe = loose ? re[t.COMPARATORLOOSE] : re[t.COMPARATOR] - const rangeList = range + let rangeList = range .split(' ') .map(comp => parseComparator(comp, this.options)) .join(' ') .split(/\s+/) // >=0.0.0 is equivalent to * .map(comp => replaceGTE0(comp, this.options)) + + if (loose) { // in loose mode, throw out any that are not valid comparators - .filter(this.options.loose ? comp => !!comp.match(compRe) : () => true) - .map(comp => new Comparator(comp, this.options)) + rangeList = rangeList.filter(comp => { + debug('loose invalid filter', comp, this.options) + return !!comp.match(re[t.COMPARATORLOOSE]) + }) + } + debug('range list', rangeList) // if any comparators are the null set, then replace with JUST null set // if more than one comparator, remove any * comparators // also, don't include the same comparator more than once - const l = rangeList.length const rangeMap = new Map() - for (const comp of rangeList) { + const comparators = rangeList.map(comp => new Comparator(comp, this.options)) + for (const comp of comparators) { if (isNullSet(comp)) return [comp] rangeMap.set(comp.value, comp) diff --git a/internal/re.js b/internal/re.js index 54d4176d..ed88398a 100644 --- a/internal/re.js +++ b/internal/re.js @@ -10,7 +10,7 @@ let R = 0 const createToken = (name, value, isGlobal) => { const index = R++ - debug(index, value) + debug(name, index, value) t[name] = index src[index] = value re[index] = new RegExp(value, isGlobal ? 'g' : undefined) @@ -178,5 +178,5 @@ createToken('HYPHENRANGELOOSE', `^\\s*(${src[t.XRANGEPLAINLOOSE]})` + // Star ranges basically just allow anything at all. createToken('STAR', '(<|>)?=?\\s*\\*') // >=0.0.0 is like a star -createToken('GTE0', '^\\s*>=\\s*0\.0\.0\\s*$') -createToken('GTE0PRE', '^\\s*>=\\s*0\.0\.0-0\\s*$') +createToken('GTE0', '^\\s*>=\\s*0\\.0\\.0\\s*$') +createToken('GTE0PRE', '^\\s*>=\\s*0\\.0\\.0-0\\s*$') diff --git a/test/classes/range.js b/test/classes/range.js index e3867a9a..d6cdb24d 100644 --- a/test/classes/range.js +++ b/test/classes/range.js @@ -17,9 +17,9 @@ test('range tests', t => { test('range parsing', t => { t.plan(rangeParse.length) - rangeParse.forEach(([range, expect, options]) => t.test(`${range} ${expect}`, t => { + rangeParse.forEach(([range, expect, options]) => t.test(`${range} ${expect} ${JSON.stringify(options)}`, t => { if (expect === null) - t.throws(() => new Range(range), TypeError, `invalid range: ${range}`) + t.throws(() => new Range(range, options), TypeError, `invalid range: ${range} ${JSON.stringify(options)}`) else { t.equal(new Range(range, options).range || '*', expect, `${range} => ${expect}`) diff --git a/test/fixtures/range-parse.js b/test/fixtures/range-parse.js index 7ae23f14..8779f9a7 100644 --- a/test/fixtures/range-parse.js +++ b/test/fixtures/range-parse.js @@ -89,4 +89,8 @@ module.exports = [ ['* 2.x', '<0.0.0-0'], ['>x 2.x || * || =09090', null], + ['>=09090', '>=9090.0.0', true], + ['>=09090-0', null, { includePrerelease: true }], + ['>=09090-0', null, { loose: true, includePrerelease: true }], ] diff --git a/test/ranges/valid.js b/test/ranges/valid.js index beee98d4..f1fe3960 100644 --- a/test/ranges/valid.js +++ b/test/ranges/valid.js @@ -8,5 +8,5 @@ test('valid range test', (t) => { t.plan(rangeParse.length) rangeParse.forEach(([pre, wanted, options]) => t.equal(validRange(pre, options), wanted, - `validRange(${pre}) === ${wanted}`)) + `validRange(${pre}) === ${wanted} ${JSON.stringify(options)}`)) })