diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 0be3c6ce..cc729eee 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.5.2" + ".": "7.5.3" } diff --git a/CHANGELOG.md b/CHANGELOG.md index c08c960c..292ed24a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## [7.5.3](https://github.com/npm/node-semver/compare/v7.5.2...v7.5.3) (2023-06-22) + +### Bug Fixes + +* [`abdd93d`](https://github.com/npm/node-semver/commit/abdd93d55496d22e3c15a454a5cf13f101e48bce) [#571](https://github.com/npm/node-semver/pull/571) set max lengths in regex for numeric and build identifiers (#571) (@lukekarrys) + +### Documentation + +* [`bf53dd8`](https://github.com/npm/node-semver/commit/bf53dd8da15a17eb6b8111115d0d8ef341fea5db) [#569](https://github.com/npm/node-semver/pull/569) add example for `>` comparator (#569) (@mbtools) + ## [7.5.2](https://github.com/npm/node-semver/compare/v7.5.1...v7.5.2) (2023-06-15) ### Bug Fixes diff --git a/README.md b/README.md index b52a5eb1..53ea9b52 100644 --- a/README.md +++ b/README.md @@ -159,7 +159,9 @@ of primitive `operators` is: For example, the comparator `>=1.2.7` would match the versions `1.2.7`, `1.2.8`, `2.5.3`, and `1.3.9`, but not the versions `1.2.6` -or `1.1.0`. +or `1.1.0`. The comparator `>1` is equivalent to `>=2.0.0` and +would match the versions `2.0.0` and `3.1.0`, but not the versions +`1.0.1` or `1.1.0`. Comparators can be joined by whitespace to form a `comparator set`, which is satisfied by the **intersection** of all of the comparators diff --git a/classes/range.js b/classes/range.js index 53c2540f..a7d37203 100644 --- a/classes/range.js +++ b/classes/range.js @@ -98,15 +98,18 @@ class Range { const hr = loose ? re[t.HYPHENRANGELOOSE] : re[t.HYPHENRANGE] range = range.replace(hr, hyphenReplace(this.options.includePrerelease)) 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) // `~ 1.2.3` => `~1.2.3` range = range.replace(re[t.TILDETRIM], tildeTrimReplace) + debug('tilde trim', range) // `^ 1.2.3` => `^1.2.3` range = range.replace(re[t.CARETTRIM], caretTrimReplace) + debug('caret trim', range) // At this point, the range is completely trimmed and // ready to be split into comparators. diff --git a/internal/constants.js b/internal/constants.js index 25fab1ea..94be1c57 100644 --- a/internal/constants.js +++ b/internal/constants.js @@ -9,6 +9,10 @@ const MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER || // Max safe segment length for coercion. const MAX_SAFE_COMPONENT_LENGTH = 16 +// Max safe length for a build identifier. The max length minus 6 characters for +// the shortest version with a build 0.0.0+BUILD. +const MAX_SAFE_BUILD_LENGTH = MAX_LENGTH - 6 + const RELEASE_TYPES = [ 'major', 'premajor', @@ -22,6 +26,7 @@ const RELEASE_TYPES = [ module.exports = { MAX_LENGTH, MAX_SAFE_COMPONENT_LENGTH, + MAX_SAFE_BUILD_LENGTH, MAX_SAFE_INTEGER, RELEASE_TYPES, SEMVER_SPEC_VERSION, diff --git a/internal/re.js b/internal/re.js index f73ef1aa..9f5e36d5 100644 --- a/internal/re.js +++ b/internal/re.js @@ -1,4 +1,4 @@ -const { MAX_SAFE_COMPONENT_LENGTH } = require('./constants') +const { MAX_SAFE_COMPONENT_LENGTH, MAX_SAFE_BUILD_LENGTH } = require('./constants') const debug = require('./debug') exports = module.exports = {} @@ -9,16 +9,31 @@ const src = exports.src = [] const t = exports.t = {} let R = 0 +const LETTERDASHNUMBER = '[a-zA-Z0-9-]' + +// Replace some greedy regex tokens to prevent regex dos issues. These regex are +// used internally via the safeRe object since all inputs in this library get +// normalized first to trim and collapse all extra whitespace. The original +// regexes are exported for userland consumption and lower level usage. A +// future breaking change could export the safer regex only with a note that +// all input should have extra whitespace removed. +const safeRegexReplacements = [ + ['\\s', 1], + ['\\d', MAX_SAFE_COMPONENT_LENGTH], + [LETTERDASHNUMBER, MAX_SAFE_BUILD_LENGTH], +] + +const makeSafeRegex = (value) => { + for (const [token, max] of safeRegexReplacements) { + value = value + .split(`${token}*`).join(`${token}{0,${max}}`) + .split(`${token}+`).join(`${token}{1,${max}}`) + } + return value +} + const createToken = (name, value, isGlobal) => { - // Replace all greedy whitespace to prevent regex dos issues. These regex are - // used internally via the safeRe object since all inputs in this library get - // normalized first to trim and collapse all extra whitespace. The original - // regexes are exported for userland consumption and lower level usage. A - // future breaking change could export the safer regex only with a note that - // all input should have extra whitespace removed. - const safe = value - .split('\\s*').join('\\s{0,1}') - .split('\\s+').join('\\s') + const safe = makeSafeRegex(value) const index = R++ debug(name, index, value) t[name] = index @@ -34,13 +49,13 @@ const createToken = (name, value, isGlobal) => { // A single `0`, or a non-zero digit followed by zero or more digits. createToken('NUMERICIDENTIFIER', '0|[1-9]\\d*') -createToken('NUMERICIDENTIFIERLOOSE', '[0-9]+') +createToken('NUMERICIDENTIFIERLOOSE', '\\d+') // ## Non-numeric Identifier // Zero or more digits, followed by a letter or hyphen, and then zero or // more letters, digits, or hyphens. -createToken('NONNUMERICIDENTIFIER', '\\d*[a-zA-Z-][a-zA-Z0-9-]*') +createToken('NONNUMERICIDENTIFIER', `\\d*[a-zA-Z-]${LETTERDASHNUMBER}*`) // ## Main Version // Three dot-separated numeric identifiers. @@ -75,7 +90,7 @@ createToken('PRERELEASELOOSE', `(?:-?(${src[t.PRERELEASEIDENTIFIERLOOSE] // ## Build Metadata Identifier // Any combination of digits, letters, or hyphens. -createToken('BUILDIDENTIFIER', '[0-9A-Za-z-]+') +createToken('BUILDIDENTIFIER', `${LETTERDASHNUMBER}+`) // ## Build Metadata // Plus sign, followed by one or more period-separated build metadata diff --git a/package.json b/package.json index 7d0aff3c..378164a7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "semver", - "version": "7.5.2", + "version": "7.5.3", "description": "The semantic version parser used by npm.", "main": "index.js", "scripts": { diff --git a/test/integration/whitespace.js b/test/integration/whitespace.js index eb9744d9..ae1451b1 100644 --- a/test/integration/whitespace.js +++ b/test/integration/whitespace.js @@ -7,33 +7,43 @@ const minVersion = require('../../ranges/min-version') const minSatisfying = require('../../ranges/min-satisfying') const maxSatisfying = require('../../ranges/max-satisfying') -const s = (n = 500000) => ' '.repeat(n) +const wsMedium = ' '.repeat(125) +const wsLarge = ' '.repeat(500000) +const zeroLarge = '0'.repeat(500000) -test('regex dos via range whitespace', (t) => { - // a range with this much whitespace would take a few minutes to process if +test('range with whitespace', (t) => { + // a range with these extra characters would take a few minutes to process if // any redos susceptible regexes were used. there is a global tap timeout per // file set in the package.json that will error if this test takes too long. - const r = `1.2.3 ${s()} <1.3.0` - + const r = `1.2.3 ${wsLarge} <1.3.0` t.equal(new Range(r).range, '1.2.3 <1.3.0') t.equal(validRange(r), '1.2.3 <1.3.0') t.equal(minVersion(r).version, '1.2.3') t.equal(minSatisfying(['1.2.3'], r), '1.2.3') t.equal(maxSatisfying(['1.2.3'], r), '1.2.3') + t.end() +}) +test('range with 0', (t) => { + const r = `1.2.3 ${zeroLarge} <1.3.0` + t.throws(() => new Range(r).range) + t.equal(validRange(r), null) + t.throws(() => minVersion(r).version) + t.equal(minSatisfying(['1.2.3']), null) + t.equal(maxSatisfying(['1.2.3']), null) t.end() }) test('semver version', (t) => { - const v = `${s(125)}1.2.3${s(125)}` - const tooLong = `${s()}1.2.3${s()}` + const v = `${wsMedium}1.2.3${wsMedium}` + const tooLong = `${wsLarge}1.2.3${wsLarge}` t.equal(new SemVer(v).version, '1.2.3') t.throws(() => new SemVer(tooLong)) t.end() }) test('comparator', (t) => { - const c = `${s()}<${s()}1.2.3${s()}` - t.equal(new Comparator(c).value, '<1.2.3') + const comparator = `${wsLarge}<${wsLarge}1.2.3${wsLarge}` + t.equal(new Comparator(comparator).value, '<1.2.3') t.end() })