@@ -4508,8 +4508,11 @@ var MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER ||
45084508// Max safe segment length for coercion.
45094509var MAX_SAFE_COMPONENT_LENGTH = 16
45104510
4511+ var MAX_SAFE_BUILD_LENGTH = MAX_LENGTH - 6
4512+
45114513// The actual regexps go on exports.re
45124514var re = exports.re = []
4515+ var safeRe = exports.safeRe = []
45134516var src = exports.src = []
45144517var t = exports.tokens = {}
45154518var R = 0
@@ -4518,6 +4521,31 @@ function tok (n) {
45184521 t[n] = R++
45194522}
45204523
4524+ var LETTERDASHNUMBER = '[a-zA-Z0-9-]'
4525+
4526+ // Replace some greedy regex tokens to prevent regex dos issues. These regex are
4527+ // used internally via the safeRe object since all inputs in this library get
4528+ // normalized first to trim and collapse all extra whitespace. The original
4529+ // regexes are exported for userland consumption and lower level usage. A
4530+ // future breaking change could export the safer regex only with a note that
4531+ // all input should have extra whitespace removed.
4532+ var safeRegexReplacements = [
4533+ ['\\s', 1],
4534+ ['\\d', MAX_LENGTH],
4535+ [LETTERDASHNUMBER, MAX_SAFE_BUILD_LENGTH],
4536+ ]
4537+
4538+ function makeSafeRe (value) {
4539+ for (var i = 0; i < safeRegexReplacements.length; i++) {
4540+ var token = safeRegexReplacements[i][0]
4541+ var max = safeRegexReplacements[i][1]
4542+ value = value
4543+ .split(token + '*').join(token + '{0,' + max + '}')
4544+ .split(token + '+').join(token + '{1,' + max + '}')
4545+ }
4546+ return value
4547+ }
4548+
45214549// The following Regular Expressions can be used for tokenizing,
45224550// validating, and parsing SemVer version strings.
45234551
@@ -4527,14 +4555,14 @@ function tok (n) {
45274555tok('NUMERICIDENTIFIER')
45284556src[t.NUMERICIDENTIFIER] = '0|[1-9]\\d*'
45294557tok('NUMERICIDENTIFIERLOOSE')
4530- src[t.NUMERICIDENTIFIERLOOSE] = '[0-9] +'
4558+ src[t.NUMERICIDENTIFIERLOOSE] = '\\d +'
45314559
45324560// ## Non-numeric Identifier
45334561// Zero or more digits, followed by a letter or hyphen, and then zero or
45344562// more letters, digits, or hyphens.
45354563
45364564tok('NONNUMERICIDENTIFIER')
4537- src[t.NONNUMERICIDENTIFIER] = '\\d*[a-zA-Z-][a-zA-Z0-9-] *'
4565+ src[t.NONNUMERICIDENTIFIER] = '\\d*[a-zA-Z-]' + LETTERDASHNUMBER + ' *'
45384566
45394567// ## Main Version
45404568// Three dot-separated numeric identifiers.
@@ -4576,7 +4604,7 @@ src[t.PRERELEASELOOSE] = '(?:-?(' + src[t.PRERELEASEIDENTIFIERLOOSE] +
45764604// Any combination of digits, letters, or hyphens.
45774605
45784606tok('BUILDIDENTIFIER')
4579- src[t.BUILDIDENTIFIER] = '[0-9A-Za-z-] +'
4607+ src[t.BUILDIDENTIFIER] = LETTERDASHNUMBER + ' +'
45804608
45814609// ## Build Metadata
45824610// Plus sign, followed by one or more period-separated build metadata
@@ -4656,6 +4684,7 @@ src[t.COERCE] = '(^|[^\\d])' +
46564684 '(?:$|[^\\d])'
46574685tok('COERCERTL')
46584686re[t.COERCERTL] = new RegExp(src[t.COERCE], 'g')
4687+ safeRe[t.COERCERTL] = new RegExp(makeSafeRe(src[t.COERCE]), 'g')
46594688
46604689// Tilde ranges.
46614690// Meaning is "reasonably at or greater than"
@@ -4665,6 +4694,7 @@ src[t.LONETILDE] = '(?:~>?)'
46654694tok('TILDETRIM')
46664695src[t.TILDETRIM] = '(\\s*)' + src[t.LONETILDE] + '\\s+'
46674696re[t.TILDETRIM] = new RegExp(src[t.TILDETRIM], 'g')
4697+ safeRe[t.TILDETRIM] = new RegExp(makeSafeRe(src[t.TILDETRIM]), 'g')
46684698var tildeTrimReplace = '$1~'
46694699
46704700tok('TILDE')
@@ -4680,6 +4710,7 @@ src[t.LONECARET] = '(?:\\^)'
46804710tok('CARETTRIM')
46814711src[t.CARETTRIM] = '(\\s*)' + src[t.LONECARET] + '\\s+'
46824712re[t.CARETTRIM] = new RegExp(src[t.CARETTRIM], 'g')
4713+ safeRe[t.CARETTRIM] = new RegExp(makeSafeRe(src[t.CARETTRIM]), 'g')
46834714var caretTrimReplace = '$1^'
46844715
46854716tok('CARET')
@@ -4701,6 +4732,7 @@ src[t.COMPARATORTRIM] = '(\\s*)' + src[t.GTLT] +
47014732
47024733// this one has to use the /g flag
47034734re[t.COMPARATORTRIM] = new RegExp(src[t.COMPARATORTRIM], 'g')
4735+ safeRe[t.COMPARATORTRIM] = new RegExp(makeSafeRe(src[t.COMPARATORTRIM]), 'g')
47044736var comparatorTrimReplace = '$1$2$3'
47054737
47064738// Something like `1.2.3 - 1.2.4`
@@ -4729,6 +4761,14 @@ for (var i = 0; i < R; i++) {
47294761 debug(i, src[i])
47304762 if (!re[i]) {
47314763 re[i] = new RegExp(src[i])
4764+
4765+ // Replace all greedy whitespace to prevent regex dos issues. These regex are
4766+ // used internally via the safeRe object since all inputs in this library get
4767+ // normalized first to trim and collapse all extra whitespace. The original
4768+ // regexes are exported for userland consumption and lower level usage. A
4769+ // future breaking change could export the safer regex only with a note that
4770+ // all input should have extra whitespace removed.
4771+ safeRe[i] = new RegExp(makeSafeRe(src[i]))
47324772 }
47334773}
47344774
@@ -4753,7 +4793,7 @@ function parse (version, options) {
47534793 return null
47544794 }
47554795
4756- var r = options.loose ? re [t.LOOSE] : re [t.FULL]
4796+ var r = options.loose ? safeRe [t.LOOSE] : safeRe [t.FULL]
47574797 if (!r.test(version)) {
47584798 return null
47594799 }
@@ -4808,7 +4848,7 @@ function SemVer (version, options) {
48084848 this.options = options
48094849 this.loose = !!options.loose
48104850
4811- var m = version.trim().match(options.loose ? re [t.LOOSE] : re [t.FULL])
4851+ var m = version.trim().match(options.loose ? safeRe [t.LOOSE] : safeRe [t.FULL])
48124852
48134853 if (!m) {
48144854 throw new TypeError('Invalid Version: ' + version)
@@ -5253,6 +5293,7 @@ function Comparator (comp, options) {
52535293 return new Comparator(comp, options)
52545294 }
52555295
5296+ comp = comp.trim().split(/\s+/).join(' ')
52565297 debug('comparator', comp, options)
52575298 this.options = options
52585299 this.loose = !!options.loose
@@ -5269,7 +5310,7 @@ function Comparator (comp, options) {
52695310
52705311var ANY = {}
52715312Comparator.prototype.parse = function (comp) {
5272- var r = this.options.loose ? re [t.COMPARATORLOOSE] : re [t.COMPARATOR]
5313+ var r = this.options.loose ? safeRe [t.COMPARATORLOOSE] : safeRe [t.COMPARATOR]
52735314 var m = comp.match(r)
52745315
52755316 if (!m) {
@@ -5393,17 +5434,24 @@ function Range (range, options) {
53935434 this.loose = !!options.loose
53945435 this.includePrerelease = !!options.includePrerelease
53955436
5396- // First, split based on boolean or ||
5437+ // First reduce all whitespace as much as possible so we do not have to rely
5438+ // on potentially slow regexes like \s*. This is then stored and used for
5439+ // future error messages as well.
53975440 this.raw = range
5398- this.set = range.split(/\s*\|\|\s*/).map(function (range) {
5441+ .trim()
5442+ .split(/\s+/)
5443+ .join(' ')
5444+
5445+ // First, split based on boolean or ||
5446+ this.set = this.raw.split('||').map(function (range) {
53995447 return this.parseRange(range.trim())
54005448 }, this).filter(function (c) {
54015449 // throw out any that are not relevant for whatever reason
54025450 return c.length
54035451 })
54045452
54055453 if (!this.set.length) {
5406- throw new TypeError('Invalid SemVer Range: ' + range )
5454+ throw new TypeError('Invalid SemVer Range: ' + this.raw )
54075455 }
54085456
54095457 this.format()
@@ -5422,28 +5470,27 @@ Range.prototype.toString = function () {
54225470
54235471Range.prototype.parseRange = function (range) {
54245472 var loose = this.options.loose
5425- range = range.trim()
54265473 // `1.2.3 - 1.2.4` => `>=1.2.3 <=1.2.4`
5427- var hr = loose ? re [t.HYPHENRANGELOOSE] : re [t.HYPHENRANGE]
5474+ var hr = loose ? safeRe [t.HYPHENRANGELOOSE] : safeRe [t.HYPHENRANGE]
54285475 range = range.replace(hr, hyphenReplace)
54295476 debug('hyphen replace', range)
54305477 // `> 1.2.3 < 1.2.5` => `>1.2.3 <1.2.5`
5431- range = range.replace(re [t.COMPARATORTRIM], comparatorTrimReplace)
5432- debug('comparator trim', range, re [t.COMPARATORTRIM])
5478+ range = range.replace(safeRe [t.COMPARATORTRIM], comparatorTrimReplace)
5479+ debug('comparator trim', range, safeRe [t.COMPARATORTRIM])
54335480
54345481 // `~ 1.2.3` => `~1.2.3`
5435- range = range.replace(re [t.TILDETRIM], tildeTrimReplace)
5482+ range = range.replace(safeRe [t.TILDETRIM], tildeTrimReplace)
54365483
54375484 // `^ 1.2.3` => `^1.2.3`
5438- range = range.replace(re [t.CARETTRIM], caretTrimReplace)
5485+ range = range.replace(safeRe [t.CARETTRIM], caretTrimReplace)
54395486
54405487 // normalize spaces
54415488 range = range.split(/\s+/).join(' ')
54425489
54435490 // At this point, the range is completely trimmed and
54445491 // ready to be split into comparators.
54455492
5446- var compRe = loose ? re [t.COMPARATORLOOSE] : re [t.COMPARATOR]
5493+ var compRe = loose ? safeRe [t.COMPARATORLOOSE] : safeRe [t.COMPARATOR]
54475494 var set = range.split(' ').map(function (comp) {
54485495 return parseComparator(comp, this.options)
54495496 }, this).join(' ').split(/\s+/)
@@ -5543,7 +5590,7 @@ function replaceTildes (comp, options) {
55435590}
55445591
55455592function replaceTilde (comp, options) {
5546- var r = options.loose ? re [t.TILDELOOSE] : re [t.TILDE]
5593+ var r = options.loose ? safeRe [t.TILDELOOSE] : safeRe [t.TILDE]
55475594 return comp.replace(r, function (_, M, m, p, pr) {
55485595 debug('tilde', comp, _, M, m, p, pr)
55495596 var ret
@@ -5584,7 +5631,7 @@ function replaceCarets (comp, options) {
55845631
55855632function replaceCaret (comp, options) {
55865633 debug('caret', comp, options)
5587- var r = options.loose ? re [t.CARETLOOSE] : re [t.CARET]
5634+ var r = options.loose ? safeRe [t.CARETLOOSE] : safeRe [t.CARET]
55885635 return comp.replace(r, function (_, M, m, p, pr) {
55895636 debug('caret', comp, _, M, m, p, pr)
55905637 var ret
@@ -5643,7 +5690,7 @@ function replaceXRanges (comp, options) {
56435690
56445691function replaceXRange (comp, options) {
56455692 comp = comp.trim()
5646- var r = options.loose ? re [t.XRANGELOOSE] : re [t.XRANGE]
5693+ var r = options.loose ? safeRe [t.XRANGELOOSE] : safeRe [t.XRANGE]
56475694 return comp.replace(r, function (ret, gtlt, M, m, p, pr) {
56485695 debug('xRange', comp, ret, gtlt, M, m, p, pr)
56495696 var xM = isX(M)
@@ -5718,7 +5765,7 @@ function replaceXRange (comp, options) {
57185765function replaceStars (comp, options) {
57195766 debug('replaceStars', comp, options)
57205767 // Looseness is ignored here. star is always as loose as it gets!
5721- return comp.trim().replace(re [t.STAR], '')
5768+ return comp.trim().replace(safeRe [t.STAR], '')
57225769}
57235770
57245771// This function is passed to string.replace(re[t.HYPHENRANGE])
@@ -6044,7 +6091,7 @@ function coerce (version, options) {
60446091
60456092 var match = null
60466093 if (!options.rtl) {
6047- match = version.match(re [t.COERCE])
6094+ match = version.match(safeRe [t.COERCE])
60486095 } else {
60496096 // Find the right-most coercible string that does not share
60506097 // a terminus with a more left-ward coercible string.
@@ -6055,17 +6102,17 @@ function coerce (version, options) {
60556102 // Stop when we get a match that ends at the string end, since no
60566103 // coercible string can be more right-ward without the same terminus.
60576104 var next
6058- while ((next = re [t.COERCERTL].exec(version)) &&
6105+ while ((next = safeRe [t.COERCERTL].exec(version)) &&
60596106 (!match || match.index + match[0].length !== version.length)
60606107 ) {
60616108 if (!match ||
60626109 next.index + next[0].length !== match.index + match[0].length) {
60636110 match = next
60646111 }
6065- re [t.COERCERTL].lastIndex = next.index + next[1].length + next[2].length
6112+ safeRe [t.COERCERTL].lastIndex = next.index + next[1].length + next[2].length
60666113 }
60676114 // leave it in a clean state
6068- re [t.COERCERTL].lastIndex = -1
6115+ safeRe [t.COERCERTL].lastIndex = -1
60696116 }
60706117
60716118 if (match === null) {
0 commit comments