@@ -4508,8 +4508,11 @@ var MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER ||
4508
4508
// Max safe segment length for coercion.
4509
4509
var MAX_SAFE_COMPONENT_LENGTH = 16
4510
4510
4511
+ var MAX_SAFE_BUILD_LENGTH = MAX_LENGTH - 6
4512
+
4511
4513
// The actual regexps go on exports.re
4512
4514
var re = exports.re = []
4515
+ var safeRe = exports.safeRe = []
4513
4516
var src = exports.src = []
4514
4517
var t = exports.tokens = {}
4515
4518
var R = 0
@@ -4518,6 +4521,31 @@ function tok (n) {
4518
4521
t[n] = R++
4519
4522
}
4520
4523
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
+
4521
4549
// The following Regular Expressions can be used for tokenizing,
4522
4550
// validating, and parsing SemVer version strings.
4523
4551
@@ -4527,14 +4555,14 @@ function tok (n) {
4527
4555
tok('NUMERICIDENTIFIER')
4528
4556
src[t.NUMERICIDENTIFIER] = '0|[1-9]\\d*'
4529
4557
tok('NUMERICIDENTIFIERLOOSE')
4530
- src[t.NUMERICIDENTIFIERLOOSE] = '[0-9] +'
4558
+ src[t.NUMERICIDENTIFIERLOOSE] = '\\d +'
4531
4559
4532
4560
// ## Non-numeric Identifier
4533
4561
// Zero or more digits, followed by a letter or hyphen, and then zero or
4534
4562
// more letters, digits, or hyphens.
4535
4563
4536
4564
tok('NONNUMERICIDENTIFIER')
4537
- src[t.NONNUMERICIDENTIFIER] = '\\d*[a-zA-Z-][a-zA-Z0-9-] *'
4565
+ src[t.NONNUMERICIDENTIFIER] = '\\d*[a-zA-Z-]' + LETTERDASHNUMBER + ' *'
4538
4566
4539
4567
// ## Main Version
4540
4568
// Three dot-separated numeric identifiers.
@@ -4576,7 +4604,7 @@ src[t.PRERELEASELOOSE] = '(?:-?(' + src[t.PRERELEASEIDENTIFIERLOOSE] +
4576
4604
// Any combination of digits, letters, or hyphens.
4577
4605
4578
4606
tok('BUILDIDENTIFIER')
4579
- src[t.BUILDIDENTIFIER] = '[0-9A-Za-z-] +'
4607
+ src[t.BUILDIDENTIFIER] = LETTERDASHNUMBER + ' +'
4580
4608
4581
4609
// ## Build Metadata
4582
4610
// Plus sign, followed by one or more period-separated build metadata
@@ -4656,6 +4684,7 @@ src[t.COERCE] = '(^|[^\\d])' +
4656
4684
'(?:$|[^\\d])'
4657
4685
tok('COERCERTL')
4658
4686
re[t.COERCERTL] = new RegExp(src[t.COERCE], 'g')
4687
+ safeRe[t.COERCERTL] = new RegExp(makeSafeRe(src[t.COERCE]), 'g')
4659
4688
4660
4689
// Tilde ranges.
4661
4690
// Meaning is "reasonably at or greater than"
@@ -4665,6 +4694,7 @@ src[t.LONETILDE] = '(?:~>?)'
4665
4694
tok('TILDETRIM')
4666
4695
src[t.TILDETRIM] = '(\\s*)' + src[t.LONETILDE] + '\\s+'
4667
4696
re[t.TILDETRIM] = new RegExp(src[t.TILDETRIM], 'g')
4697
+ safeRe[t.TILDETRIM] = new RegExp(makeSafeRe(src[t.TILDETRIM]), 'g')
4668
4698
var tildeTrimReplace = '$1~'
4669
4699
4670
4700
tok('TILDE')
@@ -4680,6 +4710,7 @@ src[t.LONECARET] = '(?:\\^)'
4680
4710
tok('CARETTRIM')
4681
4711
src[t.CARETTRIM] = '(\\s*)' + src[t.LONECARET] + '\\s+'
4682
4712
re[t.CARETTRIM] = new RegExp(src[t.CARETTRIM], 'g')
4713
+ safeRe[t.CARETTRIM] = new RegExp(makeSafeRe(src[t.CARETTRIM]), 'g')
4683
4714
var caretTrimReplace = '$1^'
4684
4715
4685
4716
tok('CARET')
@@ -4701,6 +4732,7 @@ src[t.COMPARATORTRIM] = '(\\s*)' + src[t.GTLT] +
4701
4732
4702
4733
// this one has to use the /g flag
4703
4734
re[t.COMPARATORTRIM] = new RegExp(src[t.COMPARATORTRIM], 'g')
4735
+ safeRe[t.COMPARATORTRIM] = new RegExp(makeSafeRe(src[t.COMPARATORTRIM]), 'g')
4704
4736
var comparatorTrimReplace = '$1$2$3'
4705
4737
4706
4738
// Something like `1.2.3 - 1.2.4`
@@ -4729,6 +4761,14 @@ for (var i = 0; i < R; i++) {
4729
4761
debug(i, src[i])
4730
4762
if (!re[i]) {
4731
4763
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]))
4732
4772
}
4733
4773
}
4734
4774
@@ -4753,7 +4793,7 @@ function parse (version, options) {
4753
4793
return null
4754
4794
}
4755
4795
4756
- var r = options.loose ? re [t.LOOSE] : re [t.FULL]
4796
+ var r = options.loose ? safeRe [t.LOOSE] : safeRe [t.FULL]
4757
4797
if (!r.test(version)) {
4758
4798
return null
4759
4799
}
@@ -4808,7 +4848,7 @@ function SemVer (version, options) {
4808
4848
this.options = options
4809
4849
this.loose = !!options.loose
4810
4850
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])
4812
4852
4813
4853
if (!m) {
4814
4854
throw new TypeError('Invalid Version: ' + version)
@@ -5253,6 +5293,7 @@ function Comparator (comp, options) {
5253
5293
return new Comparator(comp, options)
5254
5294
}
5255
5295
5296
+ comp = comp.trim().split(/\s+/).join(' ')
5256
5297
debug('comparator', comp, options)
5257
5298
this.options = options
5258
5299
this.loose = !!options.loose
@@ -5269,7 +5310,7 @@ function Comparator (comp, options) {
5269
5310
5270
5311
var ANY = {}
5271
5312
Comparator.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]
5273
5314
var m = comp.match(r)
5274
5315
5275
5316
if (!m) {
@@ -5393,17 +5434,24 @@ function Range (range, options) {
5393
5434
this.loose = !!options.loose
5394
5435
this.includePrerelease = !!options.includePrerelease
5395
5436
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.
5397
5440
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) {
5399
5447
return this.parseRange(range.trim())
5400
5448
}, this).filter(function (c) {
5401
5449
// throw out any that are not relevant for whatever reason
5402
5450
return c.length
5403
5451
})
5404
5452
5405
5453
if (!this.set.length) {
5406
- throw new TypeError('Invalid SemVer Range: ' + range )
5454
+ throw new TypeError('Invalid SemVer Range: ' + this.raw )
5407
5455
}
5408
5456
5409
5457
this.format()
@@ -5422,28 +5470,27 @@ Range.prototype.toString = function () {
5422
5470
5423
5471
Range.prototype.parseRange = function (range) {
5424
5472
var loose = this.options.loose
5425
- range = range.trim()
5426
5473
// `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]
5428
5475
range = range.replace(hr, hyphenReplace)
5429
5476
debug('hyphen replace', range)
5430
5477
// `> 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])
5433
5480
5434
5481
// `~ 1.2.3` => `~1.2.3`
5435
- range = range.replace(re [t.TILDETRIM], tildeTrimReplace)
5482
+ range = range.replace(safeRe [t.TILDETRIM], tildeTrimReplace)
5436
5483
5437
5484
// `^ 1.2.3` => `^1.2.3`
5438
- range = range.replace(re [t.CARETTRIM], caretTrimReplace)
5485
+ range = range.replace(safeRe [t.CARETTRIM], caretTrimReplace)
5439
5486
5440
5487
// normalize spaces
5441
5488
range = range.split(/\s+/).join(' ')
5442
5489
5443
5490
// At this point, the range is completely trimmed and
5444
5491
// ready to be split into comparators.
5445
5492
5446
- var compRe = loose ? re [t.COMPARATORLOOSE] : re [t.COMPARATOR]
5493
+ var compRe = loose ? safeRe [t.COMPARATORLOOSE] : safeRe [t.COMPARATOR]
5447
5494
var set = range.split(' ').map(function (comp) {
5448
5495
return parseComparator(comp, this.options)
5449
5496
}, this).join(' ').split(/\s+/)
@@ -5543,7 +5590,7 @@ function replaceTildes (comp, options) {
5543
5590
}
5544
5591
5545
5592
function 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]
5547
5594
return comp.replace(r, function (_, M, m, p, pr) {
5548
5595
debug('tilde', comp, _, M, m, p, pr)
5549
5596
var ret
@@ -5584,7 +5631,7 @@ function replaceCarets (comp, options) {
5584
5631
5585
5632
function replaceCaret (comp, options) {
5586
5633
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]
5588
5635
return comp.replace(r, function (_, M, m, p, pr) {
5589
5636
debug('caret', comp, _, M, m, p, pr)
5590
5637
var ret
@@ -5643,7 +5690,7 @@ function replaceXRanges (comp, options) {
5643
5690
5644
5691
function replaceXRange (comp, options) {
5645
5692
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]
5647
5694
return comp.replace(r, function (ret, gtlt, M, m, p, pr) {
5648
5695
debug('xRange', comp, ret, gtlt, M, m, p, pr)
5649
5696
var xM = isX(M)
@@ -5718,7 +5765,7 @@ function replaceXRange (comp, options) {
5718
5765
function replaceStars (comp, options) {
5719
5766
debug('replaceStars', comp, options)
5720
5767
// 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], '')
5722
5769
}
5723
5770
5724
5771
// This function is passed to string.replace(re[t.HYPHENRANGE])
@@ -6044,7 +6091,7 @@ function coerce (version, options) {
6044
6091
6045
6092
var match = null
6046
6093
if (!options.rtl) {
6047
- match = version.match(re [t.COERCE])
6094
+ match = version.match(safeRe [t.COERCE])
6048
6095
} else {
6049
6096
// Find the right-most coercible string that does not share
6050
6097
// a terminus with a more left-ward coercible string.
@@ -6055,17 +6102,17 @@ function coerce (version, options) {
6055
6102
// Stop when we get a match that ends at the string end, since no
6056
6103
// coercible string can be more right-ward without the same terminus.
6057
6104
var next
6058
- while ((next = re [t.COERCERTL].exec(version)) &&
6105
+ while ((next = safeRe [t.COERCERTL].exec(version)) &&
6059
6106
(!match || match.index + match[0].length !== version.length)
6060
6107
) {
6061
6108
if (!match ||
6062
6109
next.index + next[0].length !== match.index + match[0].length) {
6063
6110
match = next
6064
6111
}
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
6066
6113
}
6067
6114
// leave it in a clean state
6068
- re [t.COERCERTL].lastIndex = -1
6115
+ safeRe [t.COERCERTL].lastIndex = -1
6069
6116
}
6070
6117
6071
6118
if (match === null) {
0 commit comments