@@ -28,9 +28,14 @@ var MAX_SAFE_COMPONENT_LENGTH = 16
2828
2929// The actual regexps go on exports.re
3030var re = exports . re = [ ]
31+ var safeRe = exports . safeRe = [ ]
3132var src = exports . src = [ ]
3233var R = 0
3334
35+ function makeSafeRe ( value ) {
36+ return value . split ( '\\s*' ) . join ( '\\s{0,1}' ) . split ( '\\s+' ) . join ( '\\s' )
37+ }
38+
3439// The following Regular Expressions can be used for tokenizing,
3540// validating, and parsing SemVer version strings.
3641
@@ -174,6 +179,7 @@ src[LONETILDE] = '(?:~>?)'
174179var TILDETRIM = R ++
175180src [ TILDETRIM ] = '(\\s*)' + src [ LONETILDE ] + '\\s+'
176181re [ TILDETRIM ] = new RegExp ( src [ TILDETRIM ] , 'g' )
182+ safeRe [ TILDETRIM ] = new RegExp ( makeSafeRe ( src [ TILDETRIM ] ) , 'g' )
177183var tildeTrimReplace = '$1~'
178184
179185var TILDE = R ++
@@ -189,6 +195,7 @@ src[LONECARET] = '(?:\\^)'
189195var CARETTRIM = R ++
190196src [ CARETTRIM ] = '(\\s*)' + src [ LONECARET ] + '\\s+'
191197re [ CARETTRIM ] = new RegExp ( src [ CARETTRIM ] , 'g' )
198+ safeRe [ CARETTRIM ] = new RegExp ( makeSafeRe ( src [ CARETTRIM ] ) , 'g' )
192199var caretTrimReplace = '$1^'
193200
194201var CARET = R ++
@@ -210,6 +217,7 @@ src[COMPARATORTRIM] = '(\\s*)' + src[GTLT] +
210217
211218// this one has to use the /g flag
212219re [ COMPARATORTRIM ] = new RegExp ( src [ COMPARATORTRIM ] , 'g' )
220+ safeRe [ COMPARATORTRIM ] = new RegExp ( makeSafeRe ( src [ COMPARATORTRIM ] ) , 'g' )
213221var comparatorTrimReplace = '$1$2$3'
214222
215223// Something like `1.2.3 - 1.2.4`
@@ -238,6 +246,14 @@ for (var i = 0; i < R; i++) {
238246 debug ( i , src [ i ] )
239247 if ( ! re [ i ] ) {
240248 re [ i ] = new RegExp ( src [ i ] )
249+
250+ // Replace all greedy whitespace to prevent regex dos issues. These regex are
251+ // used internally via the safeRe object since all inputs in this library get
252+ // normalized first to trim and collapse all extra whitespace. The original
253+ // regexes are exported for userland consumption and lower level usage. A
254+ // future breaking change could export the safer regex only with a note that
255+ // all input should have extra whitespace removed.
256+ safeRe [ i ] = new RegExp ( src [ i ] . split ( '\\s*' ) . join ( '\\s{0,1}' ) . split ( '\\s+' ) . join ( '\\s' ) )
241257 }
242258}
243259
@@ -262,7 +278,7 @@ function parse (version, options) {
262278 return null
263279 }
264280
265- var r = options . loose ? re [ LOOSE ] : re [ FULL ]
281+ var r = options . loose ? safeRe [ LOOSE ] : safeRe [ FULL ]
266282 if ( ! r . test ( version ) ) {
267283 return null
268284 }
@@ -317,7 +333,7 @@ function SemVer (version, options) {
317333 this . options = options
318334 this . loose = ! ! options . loose
319335
320- var m = version . trim ( ) . match ( options . loose ? re [ LOOSE ] : re [ FULL ] )
336+ var m = version . trim ( ) . match ( options . loose ? safeRe [ LOOSE ] : safeRe [ FULL ] )
321337
322338 if ( ! m ) {
323339 throw new TypeError ( 'Invalid Version: ' + version )
@@ -731,6 +747,7 @@ function Comparator (comp, options) {
731747 return new Comparator ( comp , options )
732748 }
733749
750+ comp = comp . trim ( ) . split ( / \s + / ) . join ( ' ' )
734751 debug ( 'comparator' , comp , options )
735752 this . options = options
736753 this . loose = ! ! options . loose
@@ -747,7 +764,7 @@ function Comparator (comp, options) {
747764
748765var ANY = { }
749766Comparator . prototype . parse = function ( comp ) {
750- var r = this . options . loose ? re [ COMPARATORLOOSE ] : re [ COMPARATOR ]
767+ var r = this . options . loose ? safeRe [ COMPARATORLOOSE ] : safeRe [ COMPARATOR ]
751768 var m = comp . match ( r )
752769
753770 if ( ! m ) {
@@ -861,17 +878,24 @@ function Range (range, options) {
861878 this . loose = ! ! options . loose
862879 this . includePrerelease = ! ! options . includePrerelease
863880
864- // First, split based on boolean or ||
881+ // First reduce all whitespace as much as possible so we do not have to rely
882+ // on potentially slow regexes like \s*. This is then stored and used for
883+ // future error messages as well.
865884 this . raw = range
866- this . set = range . split ( / \s * \| \| \s * / ) . map ( function ( range ) {
885+ . trim ( )
886+ . split ( / \s + / )
887+ . join ( ' ' )
888+
889+ // First, split based on boolean or ||
890+ this . set = this . raw . split ( '||' ) . map ( function ( range ) {
867891 return this . parseRange ( range . trim ( ) )
868892 } , this ) . filter ( function ( c ) {
869893 // throw out any that are not relevant for whatever reason
870894 return c . length
871895 } )
872896
873897 if ( ! this . set . length ) {
874- throw new TypeError ( 'Invalid SemVer Range: ' + range )
898+ throw new TypeError ( 'Invalid SemVer Range: ' + this . raw )
875899 }
876900
877901 this . format ( )
@@ -890,28 +914,23 @@ Range.prototype.toString = function () {
890914
891915Range . prototype . parseRange = function ( range ) {
892916 var loose = this . options . loose
893- range = range . trim ( )
894917 // `1.2.3 - 1.2.4` => `>=1.2.3 <=1.2.4`
895- var hr = loose ? re [ HYPHENRANGELOOSE ] : re [ HYPHENRANGE ]
918+ var hr = loose ? safeRe [ HYPHENRANGELOOSE ] : safeRe [ HYPHENRANGE ]
896919 range = range . replace ( hr , hyphenReplace )
897920 debug ( 'hyphen replace' , range )
898921 // `> 1.2.3 < 1.2.5` => `>1.2.3 <1.2.5`
899- range = range . replace ( re [ COMPARATORTRIM ] , comparatorTrimReplace )
900- debug ( 'comparator trim' , range , re [ COMPARATORTRIM ] )
922+ range = range . replace ( safeRe [ COMPARATORTRIM ] , comparatorTrimReplace )
923+ debug ( 'comparator trim' , range , safeRe [ COMPARATORTRIM ] )
901924
902925 // `~ 1.2.3` => `~1.2.3`
903- range = range . replace ( re [ TILDETRIM ] , tildeTrimReplace )
926+ range = range . replace ( safeRe [ TILDETRIM ] , tildeTrimReplace )
904927
905928 // `^ 1.2.3` => `^1.2.3`
906- range = range . replace ( re [ CARETTRIM ] , caretTrimReplace )
907-
908- // normalize spaces
909- range = range . split ( / \s + / ) . join ( ' ' )
929+ range = range . replace ( safeRe [ CARETTRIM ] , caretTrimReplace )
910930
911931 // At this point, the range is completely trimmed and
912932 // ready to be split into comparators.
913-
914- var compRe = loose ? re [ COMPARATORLOOSE ] : re [ COMPARATOR ]
933+ var compRe = loose ? safeRe [ COMPARATORLOOSE ] : safeRe [ COMPARATOR ]
915934 var set = range . split ( ' ' ) . map ( function ( comp ) {
916935 return parseComparator ( comp , this . options )
917936 } , this ) . join ( ' ' ) . split ( / \s + / )
@@ -987,7 +1006,7 @@ function replaceTildes (comp, options) {
9871006}
9881007
9891008function replaceTilde ( comp , options ) {
990- var r = options . loose ? re [ TILDELOOSE ] : re [ TILDE ]
1009+ var r = options . loose ? safeRe [ TILDELOOSE ] : safeRe [ TILDE ]
9911010 return comp . replace ( r , function ( _ , M , m , p , pr ) {
9921011 debug ( 'tilde' , comp , _ , M , m , p , pr )
9931012 var ret
@@ -1028,7 +1047,7 @@ function replaceCarets (comp, options) {
10281047
10291048function replaceCaret ( comp , options ) {
10301049 debug ( 'caret' , comp , options )
1031- var r = options . loose ? re [ CARETLOOSE ] : re [ CARET ]
1050+ var r = options . loose ? safeRe [ CARETLOOSE ] : safeRe [ CARET ]
10321051 return comp . replace ( r , function ( _ , M , m , p , pr ) {
10331052 debug ( 'caret' , comp , _ , M , m , p , pr )
10341053 var ret
@@ -1087,7 +1106,7 @@ function replaceXRanges (comp, options) {
10871106
10881107function replaceXRange ( comp , options ) {
10891108 comp = comp . trim ( )
1090- var r = options . loose ? re [ XRANGELOOSE ] : re [ XRANGE ]
1109+ var r = options . loose ? safeRe [ XRANGELOOSE ] : safeRe [ XRANGE ]
10911110 return comp . replace ( r , function ( ret , gtlt , M , m , p , pr ) {
10921111 debug ( 'xRange' , comp , ret , gtlt , M , m , p , pr )
10931112 var xM = isX ( M )
@@ -1157,10 +1176,10 @@ function replaceXRange (comp, options) {
11571176function replaceStars ( comp , options ) {
11581177 debug ( 'replaceStars' , comp , options )
11591178 // Looseness is ignored here. star is always as loose as it gets!
1160- return comp . trim ( ) . replace ( re [ STAR ] , '' )
1179+ return comp . trim ( ) . replace ( safeRe [ STAR ] , '' )
11611180}
11621181
1163- // This function is passed to string.replace(re [HYPHENRANGE])
1182+ // This function is passed to string.replace(safeRe [HYPHENRANGE])
11641183// M, m, patch, prerelease, build
11651184// 1.2 - 3.4.5 => >=1.2.0 <=3.4.5
11661185// 1.2.3 - 3.4 => >=1.2.0 <3.5.0 Any 3.4.x will do
@@ -1471,7 +1490,7 @@ function coerce (version) {
14711490 return null
14721491 }
14731492
1474- var match = version . match ( re [ COERCE ] )
1493+ var match = version . match ( safeRe [ COERCE ] )
14751494
14761495 if ( match == null ) {
14771496 return null
0 commit comments