fix: reject loose ranges that collapse to match everything
In loose mode the comparators that fail to parse are filtered out of a comparator list, and whatever is left becomes the range. When the only survivor is the any comparator, a typo such as `* (4.1.x 4.1.2)` drops `(4.1.x` and `4.1.2)` and collapses to the leftover `*`, so the range silently satisfies every version instead of being rejected. Without `loose` the same input throws `Invalid comparator: (4.1.x`. Treat that comparator list as invalid instead, the same way a list with no valid comparator at all is already treated: the list is dropped, so other `||` branches still stand on their own and a range left with no valid branch throws `Invalid SemVer Range`. Junk sitting next to a comparator that does parse is still tolerated, so `>=1.2.3 &&&` still yields `>=1.2.3` and `== 1.0.0` still yields `1.0.0`. Fixes #387
This commit is contained in:
1 parent
d4354cc38b
commit
2124e4b155
3 files changed
+37
-1
No files matched your search
+9
-1
@@ -143,10 +143,18 @@ class Range {
|
|||||||
|
|
||||||
if (loose) {
|
if (loose) {
|
||||||
// in loose mode, throw out any that are not valid comparators
|
// in loose mode, throw out any that are not valid comparators
|
||||||
rangeList = rangeList.filter(comp => {
|
const filtered = rangeList.filter(comp => {
|
||||||
debug('loose invalid filter', comp, this.options)
|
debug('loose invalid filter', comp, this.options)
|
||||||
return !!comp.match(re[t.COMPARATORLOOSE])
|
return !!comp.match(re[t.COMPARATORLOOSE])
|
||||||
})
|
})
|
||||||
|
// dropping invalid comparators must not leave a range that matches
|
||||||
|
// everything: `* (4.1.x 4.1.2)` would collapse to the leftover `*`.
|
||||||
|
// if only the any comparator survives, treat the list as invalid.
|
||||||
|
if (filtered.length !== rangeList.length &&
|
||||||
|
filtered.every(comp => comp === '')) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
rangeList = filtered
|
||||||
}
|
}
|
||||||
debug('range list', rangeList)
|
debug('range list', rangeList)
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
const { test } = require('tap')
|
const { test } = require('tap')
|
||||||
const Range = require('../../classes/range')
|
const Range = require('../../classes/range')
|
||||||
const Comparator = require('../../classes/comparator')
|
const Comparator = require('../../classes/comparator')
|
||||||
|
const satisfies = require('../../functions/satisfies')
|
||||||
const rangeIntersection = require('../fixtures/range-intersection.js')
|
const rangeIntersection = require('../fixtures/range-intersection.js')
|
||||||
|
|
||||||
const rangeInclude = require('../fixtures/range-include.js')
|
const rangeInclude = require('../fixtures/range-include.js')
|
||||||
@@ -38,6 +39,25 @@ test('throw for empty comparator set, even in loose mode', t => {
|
|||||||
t.end()
|
t.end()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('loose mode does not drop comparators down to a match-everything range', t => {
|
||||||
|
// `(4.1.x` and `4.1.2)` used to be filtered out, collapsing the range to the
|
||||||
|
// `*` that was left over, so a typo silently matched every version
|
||||||
|
t.throws(() => new Range('* (4.1.x 4.1.2)', { loose: true }),
|
||||||
|
TypeError('Invalid SemVer Range: * (4.1.x 4.1.2)'))
|
||||||
|
t.equal(satisfies('9.9.9', '* (4.1.x 4.1.2)', { loose: true }), false,
|
||||||
|
'typo range does not match an unrelated version')
|
||||||
|
t.equal(satisfies('4.1.2', '* (4.1.x 4.1.2)', { loose: true }), false,
|
||||||
|
'typo range does not match the version it looks like it means')
|
||||||
|
// only the collapse to `*` is rejected. loose mode still tolerates junk
|
||||||
|
// sitting next to a comparator that does parse, and still tolerates a `||`
|
||||||
|
// branch that is invalid as a whole
|
||||||
|
t.equal(new Range('>=1.2.3 &&&', { loose: true }).range, '>=1.2.3',
|
||||||
|
'junk beside a valid comparator is still dropped')
|
||||||
|
t.equal(new Range('1.2.3 || (foo)', { loose: true }).range, '1.2.3',
|
||||||
|
'wholly invalid union branch is still tolerated')
|
||||||
|
t.end()
|
||||||
|
})
|
||||||
|
|
||||||
test('convert comparator to range', t => {
|
test('convert comparator to range', t => {
|
||||||
const c = new Comparator('>=1.2.3')
|
const c = new Comparator('>=1.2.3')
|
||||||
const r = new Range(c)
|
const r = new Range(c)
|
||||||
|
|||||||
Vendored
+8
@@ -148,4 +148,12 @@ module.exports = [
|
|||||||
['1.2.3+sha256.' + 'a'.repeat(200) + '.' + 'b'.repeat(200), '1.2.3', { loose: true }],
|
['1.2.3+sha256.' + 'a'.repeat(200) + '.' + 'b'.repeat(200), '1.2.3', { loose: true }],
|
||||||
['1.2.3+' + 'a'.repeat(251) + ' || 2.0.0+' + 'b'.repeat(251),
|
['1.2.3+' + 'a'.repeat(251) + ' || 2.0.0+' + 'b'.repeat(251),
|
||||||
'1.2.3||2.0.0', { loose: true }],
|
'1.2.3||2.0.0', { loose: true }],
|
||||||
|
// dropping unparseable comparators must not leave a match-everything range
|
||||||
|
['* (4.1.x 4.1.2)', null],
|
||||||
|
['* (4.1.x 4.1.2)', null, { loose: true }],
|
||||||
|
['* (foo)', null, { loose: true }],
|
||||||
|
// but loose mode still tolerates junk next to a real comparator, and still
|
||||||
|
// tolerates a `||` branch that is invalid as a whole
|
||||||
|
['>=1.2.3 &&&', '>=1.2.3', { loose: true }],
|
||||||
|
['1.2.3 || (foo)', '1.2.3', { loose: true }],
|
||||||
]
|
]
|
||||||
Reference in new issue
Block a user