@@ -142,6 +142,8 @@ function Minimatch (pattern, options) {
142142 }
143143
144144 this . options = options
145+ this . maxGlobstarRecursion = options . maxGlobstarRecursion !== undefined
146+ ? options . maxGlobstarRecursion : 200
145147 this . set = [ ]
146148 this . pattern = pattern
147149 this . regexp = null
@@ -787,109 +789,173 @@ Minimatch.prototype.match = function match (f, partial) {
787789// out of pattern, then that's fine, as long as all
788790// the parts match.
789791Minimatch . prototype . matchOne = function ( file , pattern , partial ) {
790- var options = this . options
792+ if ( pattern . indexOf ( GLOBSTAR ) !== - 1 ) {
793+ return this . _matchGlobstar ( file , pattern , partial , 0 , 0 )
794+ }
795+ return this . _matchOne ( file , pattern , partial , 0 , 0 )
796+ }
791797
792- this . debug ( 'matchOne' ,
793- { 'this' : this , file : file , pattern : pattern } )
798+ Minimatch . prototype . _matchGlobstar = function ( file , pattern , partial , fileIndex , patternIndex ) {
799+ var i
794800
795- this . debug ( 'matchOne' , file . length , pattern . length )
801+ // find first globstar from patternIndex
802+ var firstgs = - 1
803+ for ( i = patternIndex ; i < pattern . length ; i ++ ) {
804+ if ( pattern [ i ] === GLOBSTAR ) { firstgs = i ; break }
805+ }
796806
797- for ( var fi = 0 ,
798- pi = 0 ,
799- fl = file . length ,
800- pl = pattern . length
801- ; ( fi < fl ) && ( pi < pl )
802- ; fi ++ , pi ++ ) {
803- this . debug ( 'matchOne loop' )
804- var p = pattern [ pi ]
805- var f = file [ fi ]
807+ // find last globstar
808+ var lastgs = - 1
809+ for ( i = pattern . length - 1 ; i >= 0 ; i -- ) {
810+ if ( pattern [ i ] === GLOBSTAR ) { lastgs = i ; break }
811+ }
806812
807- this . debug ( pattern , p , f )
813+ var head = pattern . slice ( patternIndex , firstgs )
814+ var body = pattern . slice ( firstgs + 1 , lastgs )
815+ var tail = pattern . slice ( lastgs + 1 )
808816
809- // should be impossible.
810- // some invalid regexp stuff in the set.
811- /* istanbul ignore if */
812- if ( p === false ) return false
813-
814- if ( p === GLOBSTAR ) {
815- this . debug ( 'GLOBSTAR' , [ pattern , p , f ] )
816-
817- // "**"
818- // a/**/b/**/c would match the following:
819- // a/b/x/y/z/c
820- // a/x/y/z/b/c
821- // a/b/x/b/x/c
822- // a/b/c
823- // To do this, take the rest of the pattern after
824- // the **, and see if it would match the file remainder.
825- // If so, return success.
826- // If not, the ** "swallows" a segment, and try again.
827- // This is recursively awful.
828- //
829- // a/**/b/**/c matching a/b/x/y/z/c
830- // - a matches a
831- // - doublestar
832- // - matchOne(b/x/y/z/c, b/**/c)
833- // - b matches b
834- // - doublestar
835- // - matchOne(x/y/z/c, c) -> no
836- // - matchOne(y/z/c, c) -> no
837- // - matchOne(z/c, c) -> no
838- // - matchOne(c, c) yes, hit
839- var fr = fi
840- var pr = pi + 1
841- if ( pr === pl ) {
842- this . debug ( '** at the end' )
843- // a ** at the end will just swallow the rest.
844- // We have found a match.
845- // however, it will not swallow /.x, unless
846- // options.dot is set.
847- // . and .. are *never* matched by **, for explosively
848- // exponential reasons.
849- for ( ; fi < fl ; fi ++ ) {
850- if ( file [ fi ] === '.' || file [ fi ] === '..' ||
851- ( ! options . dot && file [ fi ] . charAt ( 0 ) === '.' ) ) return false
852- }
853- return true
817+ // check the head
818+ if ( head . length ) {
819+ var fileHead = file . slice ( fileIndex , fileIndex + head . length )
820+ if ( ! this . _matchOne ( fileHead , head , partial , 0 , 0 ) ) {
821+ return false
822+ }
823+ fileIndex += head . length
824+ }
825+
826+ // check the tail
827+ var fileTailMatch = 0
828+ if ( tail . length ) {
829+ if ( tail . length + fileIndex > file . length ) return false
830+
831+ var tailStart = file . length - tail . length
832+ if ( this . _matchOne ( file , tail , partial , tailStart , 0 ) ) {
833+ fileTailMatch = tail . length
834+ } else {
835+ // affordance for stuff like a/**/* matching a/b/
836+ if ( file [ file . length - 1 ] !== '' ||
837+ fileIndex + tail . length === file . length ) {
838+ return false
839+ }
840+ tailStart --
841+ if ( ! this . _matchOne ( file , tail , partial , tailStart , 0 ) ) {
842+ return false
854843 }
844+ fileTailMatch = tail . length + 1
845+ }
846+ }
855847
856- // ok, let's see if we can swallow whatever we can.
857- while ( fr < fl ) {
858- var swallowee = file [ fr ]
859-
860- this . debug ( '\nglobstar while' , file , fr , pattern , pr , swallowee )
861-
862- // XXX remove this slice. Just pass the start index.
863- if ( this . matchOne ( file . slice ( fr ) , pattern . slice ( pr ) , partial ) ) {
864- this . debug ( 'globstar found match!' , fr , fl , swallowee )
865- // found a match.
866- return true
867- } else {
868- // can't swallow "." or ".." ever.
869- // can only swallow ".foo" when explicitly asked.
870- if ( swallowee === '.' || swallowee === '..' ||
871- ( ! options . dot && swallowee . charAt ( 0 ) === '.' ) ) {
872- this . debug ( 'dot detected!' , file , fr , pattern , pr )
873- break
874- }
875-
876- // ** swallows a segment, and continue.
877- this . debug ( 'globstar swallow a segment, and continue' )
878- fr ++
879- }
848+ // if body is empty (single ** between head and tail)
849+ if ( ! body . length ) {
850+ var sawSome = ! ! fileTailMatch
851+ for ( i = fileIndex ; i < file . length - fileTailMatch ; i ++ ) {
852+ var f = String ( file [ i ] )
853+ sawSome = true
854+ if ( f === '.' || f === '..' ||
855+ ( ! this . options . dot && f . charAt ( 0 ) === '.' ) ) {
856+ return false
880857 }
858+ }
859+ return sawSome
860+ }
861+
862+ // split body into segments at each GLOBSTAR
863+ var bodySegments = [ [ [ ] , 0 ] ]
864+ var currentBody = bodySegments [ 0 ]
865+ var nonGsParts = 0
866+ var nonGsPartsSums = [ 0 ]
867+ for ( var bi = 0 ; bi < body . length ; bi ++ ) {
868+ var b = body [ bi ]
869+ if ( b === GLOBSTAR ) {
870+ nonGsPartsSums . push ( nonGsParts )
871+ currentBody = [ [ ] , 0 ]
872+ bodySegments . push ( currentBody )
873+ } else {
874+ currentBody [ 0 ] . push ( b )
875+ nonGsParts ++
876+ }
877+ }
878+
879+ var idx = bodySegments . length - 1
880+ var fileLength = file . length - fileTailMatch
881+ for ( var si = 0 ; si < bodySegments . length ; si ++ ) {
882+ bodySegments [ si ] [ 1 ] = fileLength -
883+ ( nonGsPartsSums [ idx -- ] + bodySegments [ si ] [ 0 ] . length )
884+ }
881885
882- // no match was found.
883- // However, in partial mode, we can't say this is necessarily over.
884- // If there's more *pattern* left, then
885- /* istanbul ignore if */
886- if ( partial ) {
887- // ran out of file
888- this . debug ( '\n>>> no match, partial?' , file , fr , pattern , pr )
889- if ( fr === fl ) return true
886+ return ! ! this . _matchGlobStarBodySections (
887+ file , bodySegments , fileIndex , 0 , partial , 0 , ! ! fileTailMatch
888+ )
889+ }
890+
891+ // return false for "nope, not matching"
892+ // return null for "not matching, cannot keep trying"
893+ Minimatch . prototype . _matchGlobStarBodySections = function (
894+ file , bodySegments , fileIndex , bodyIndex , partial , globStarDepth , sawTail
895+ ) {
896+ var bs = bodySegments [ bodyIndex ]
897+ if ( ! bs ) {
898+ // just make sure there are no bad dots
899+ for ( var i = fileIndex ; i < file . length ; i ++ ) {
900+ sawTail = true
901+ var f = file [ i ]
902+ if ( f === '.' || f === '..' ||
903+ ( ! this . options . dot && f . charAt ( 0 ) === '.' ) ) {
904+ return false
890905 }
906+ }
907+ return sawTail
908+ }
909+
910+ var body = bs [ 0 ]
911+ var after = bs [ 1 ]
912+ while ( fileIndex <= after ) {
913+ var m = this . _matchOne (
914+ file . slice ( 0 , fileIndex + body . length ) ,
915+ body ,
916+ partial ,
917+ fileIndex ,
918+ 0
919+ )
920+ // if limit exceeded, no match. intentional false negative,
921+ // acceptable break in correctness for security.
922+ if ( m && globStarDepth < this . maxGlobstarRecursion ) {
923+ var sub = this . _matchGlobStarBodySections (
924+ file , bodySegments ,
925+ fileIndex + body . length , bodyIndex + 1 ,
926+ partial , globStarDepth + 1 , sawTail
927+ )
928+ if ( sub !== false ) {
929+ return sub
930+ }
931+ }
932+ var f = file [ fileIndex ]
933+ if ( f === '.' || f === '..' ||
934+ ( ! this . options . dot && f . charAt ( 0 ) === '.' ) ) {
891935 return false
892936 }
937+ fileIndex ++
938+ }
939+ return null
940+ }
941+
942+ Minimatch . prototype . _matchOne = function ( file , pattern , partial , fileIndex , patternIndex ) {
943+ var fi , pi , fl , pl
944+ for (
945+ fi = fileIndex , pi = patternIndex , fl = file . length , pl = pattern . length
946+ ; ( fi < fl ) && ( pi < pl )
947+ ; fi ++ , pi ++
948+ ) {
949+ this . debug ( 'matchOne loop' )
950+ var p = pattern [ pi ]
951+ var f = file [ fi ]
952+
953+ this . debug ( pattern , p , f )
954+
955+ // should be impossible.
956+ // some invalid regexp stuff in the set.
957+ /* istanbul ignore if */
958+ if ( p === false || p === GLOBSTAR ) return false
893959
894960 // something other than **
895961 // non-magic patterns just have to match exactly
@@ -906,17 +972,6 @@ Minimatch.prototype.matchOne = function (file, pattern, partial) {
906972 if ( ! hit ) return false
907973 }
908974
909- // Note: ending in / means that we'll get a final ""
910- // at the end of the pattern. This can only match a
911- // corresponding "" at the end of the file.
912- // If the file ends in /, then it can only match a
913- // a pattern that ends in /, unless the pattern just
914- // doesn't have any more for it. But, a/b/ should *not*
915- // match "a/b/*", even though "" matches against the
916- // [^/]*? pattern, except in partial mode, where it might
917- // simply not be reached yet.
918- // However, a/b/ should still satisfy a/*
919-
920975 // now either we fell off the end of the pattern, or we're done.
921976 if ( fi === fl && pi === pl ) {
922977 // ran out of pattern and filename at the same time.
0 commit comments