@@ -168,6 +168,8 @@ class Minimatch {
168168 if ( ! options ) options = { }
169169
170170 this . options = options
171+ this . maxGlobstarRecursion = options . maxGlobstarRecursion !== undefined
172+ ? options . maxGlobstarRecursion : 200
171173 this . set = [ ]
172174 this . pattern = pattern
173175 this . windowsPathsNoEscape = ! ! options . windowsPathsNoEscape ||
@@ -255,114 +257,172 @@ class Minimatch {
255257 // out of pattern, then that's fine, as long as all
256258 // the parts match.
257259 matchOne ( file , pattern , partial ) {
258- var options = this . options
260+ if ( pattern . indexOf ( GLOBSTAR ) !== - 1 ) {
261+ return this . _matchGlobstar ( file , pattern , partial , 0 , 0 )
262+ }
263+ return this . _matchOne ( file , pattern , partial , 0 , 0 )
264+ }
259265
260- this . debug ( 'matchOne' ,
261- { 'this' : this , file : file , pattern : pattern } )
266+ _matchGlobstar ( file , pattern , partial , fileIndex , patternIndex ) {
267+ // find first globstar from patternIndex
268+ let firstgs = - 1
269+ for ( let i = patternIndex ; i < pattern . length ; i ++ ) {
270+ if ( pattern [ i ] === GLOBSTAR ) { firstgs = i ; break }
271+ }
262272
263- this . debug ( 'matchOne' , file . length , pattern . length )
273+ // find last globstar
274+ let lastgs = - 1
275+ for ( let i = pattern . length - 1 ; i >= 0 ; i -- ) {
276+ if ( pattern [ i ] === GLOBSTAR ) { lastgs = i ; break }
277+ }
264278
265- for ( var fi = 0 ,
266- pi = 0 ,
267- fl = file . length ,
268- pl = pattern . length
269- ; ( fi < fl ) && ( pi < pl )
270- ; fi ++ , pi ++ ) {
271- this . debug ( 'matchOne loop' )
272- var p = pattern [ pi ]
273- var f = file [ fi ]
279+ const head = pattern . slice ( patternIndex , firstgs )
280+ const body = pattern . slice ( firstgs + 1 , lastgs )
281+ const tail = pattern . slice ( lastgs + 1 )
274282
275- this . debug ( pattern , p , f )
283+ // check the head
284+ if ( head . length ) {
285+ const fileHead = file . slice ( fileIndex , fileIndex + head . length )
286+ if ( ! this . _matchOne ( fileHead , head , partial , 0 , 0 ) ) {
287+ return false
288+ }
289+ fileIndex += head . length
290+ }
276291
277- // should be impossible.
278- // some invalid regexp stuff in the set.
279- /* istanbul ignore if */
280- if ( p === false ) return false
281-
282- if ( p === GLOBSTAR ) {
283- this . debug ( 'GLOBSTAR' , [ pattern , p , f ] )
284-
285- // "**"
286- // a/**/b/**/c would match the following:
287- // a/b/x/y/z/c
288- // a/x/y/z/b/c
289- // a/b/x/b/x/c
290- // a/b/c
291- // To do this, take the rest of the pattern after
292- // the **, and see if it would match the file remainder.
293- // If so, return success.
294- // If not, the ** "swallows" a segment, and try again.
295- // This is recursively awful.
296- //
297- // a/**/b/**/c matching a/b/x/y/z/c
298- // - a matches a
299- // - doublestar
300- // - matchOne(b/x/y/z/c, b/**/c)
301- // - b matches b
302- // - doublestar
303- // - matchOne(x/y/z/c, c) -> no
304- // - matchOne(y/z/c, c) -> no
305- // - matchOne(z/c, c) -> no
306- // - matchOne(c, c) yes, hit
307- var fr = fi
308- var pr = pi + 1
309- if ( pr === pl ) {
310- this . debug ( '** at the end' )
311- // a ** at the end will just swallow the rest.
312- // We have found a match.
313- // however, it will not swallow /.x, unless
314- // options.dot is set.
315- // . and .. are *never* matched by **, for explosively
316- // exponential reasons.
317- for ( ; fi < fl ; fi ++ ) {
318- if ( file [ fi ] === '.' || file [ fi ] === '..' ||
319- ( ! options . dot && file [ fi ] . charAt ( 0 ) === '.' ) ) return false
320- }
321- return true
292+ // check the tail
293+ let fileTailMatch = 0
294+ if ( tail . length ) {
295+ if ( tail . length + fileIndex > file . length ) return false
296+
297+ const tailStart = file . length - tail . length
298+ if ( this . _matchOne ( file , tail , partial , tailStart , 0 ) ) {
299+ fileTailMatch = tail . length
300+ } else {
301+ // affordance for stuff like a/**/* matching a/b/
302+ if ( file [ file . length - 1 ] !== '' ||
303+ fileIndex + tail . length === file . length ) {
304+ return false
305+ }
306+ if ( ! this . _matchOne ( file , tail , partial , tailStart - 1 , 0 ) ) {
307+ return false
322308 }
309+ fileTailMatch = tail . length + 1
310+ }
311+ }
323312
324- // ok, let's see if we can swallow whatever we can.
325- while ( fr < fl ) {
326- var swallowee = file [ fr ]
313+ // if body is empty (single ** between head and tail)
314+ if ( ! body . length ) {
315+ let sawSome = ! ! fileTailMatch
316+ for ( let i = fileIndex ; i < file . length - fileTailMatch ; i ++ ) {
317+ const f = String ( file [ i ] )
318+ sawSome = true
319+ if ( f === '.' || f === '..' ||
320+ ( ! this . options . dot && f . charAt ( 0 ) === '.' ) ) {
321+ return false
322+ }
323+ }
324+ return sawSome
325+ }
327326
328- this . debug ( '\nglobstar while' , file , fr , pattern , pr , swallowee )
327+ // split body into segments at each GLOBSTAR
328+ const bodySegments = [ [ [ ] , 0 ] ]
329+ let currentBody = bodySegments [ 0 ]
330+ let nonGsParts = 0
331+ const nonGsPartsSums = [ 0 ]
332+ for ( const b of body ) {
333+ if ( b === GLOBSTAR ) {
334+ nonGsPartsSums . push ( nonGsParts )
335+ currentBody = [ [ ] , 0 ]
336+ bodySegments . push ( currentBody )
337+ } else {
338+ currentBody [ 0 ] . push ( b )
339+ nonGsParts ++
340+ }
341+ }
329342
330- // XXX remove this slice. Just pass the start index.
331- if ( this . matchOne ( file . slice ( fr ) , pattern . slice ( pr ) , partial ) ) {
332- this . debug ( 'globstar found match!' , fr , fl , swallowee )
333- // found a match.
334- return true
335- } else {
336- // can't swallow "." or ".." ever.
337- // can only swallow ".foo" when explicitly asked.
338- if ( swallowee === '.' || swallowee === '..' ||
339- ( ! options . dot && swallowee . charAt ( 0 ) === '.' ) ) {
340- this . debug ( 'dot detected!' , file , fr , pattern , pr )
341- break
342- }
343-
344- // ** swallows a segment, and continue.
345- this . debug ( 'globstar swallow a segment, and continue' )
346- fr ++
347- }
343+ let idx = bodySegments . length - 1
344+ const fileLength = file . length - fileTailMatch
345+ for ( const b of bodySegments ) {
346+ b [ 1 ] = fileLength - ( nonGsPartsSums [ idx -- ] + b [ 0 ] . length )
347+ }
348+
349+ return ! ! this . _matchGlobStarBodySections (
350+ file , bodySegments , fileIndex , 0 , partial , 0 , ! ! fileTailMatch
351+ )
352+ }
353+
354+ // return false for "nope, not matching"
355+ // return null for "not matching, cannot keep trying"
356+ _matchGlobStarBodySections (
357+ file , bodySegments , fileIndex , bodyIndex , partial , globStarDepth , sawTail
358+ ) {
359+ const bs = bodySegments [ bodyIndex ]
360+ if ( ! bs ) {
361+ // just make sure there are no bad dots
362+ for ( let i = fileIndex ; i < file . length ; i ++ ) {
363+ sawTail = true
364+ const f = file [ i ]
365+ if ( f === '.' || f === '..' ||
366+ ( ! this . options . dot && f . charAt ( 0 ) === '.' ) ) {
367+ return false
348368 }
369+ }
370+ return sawTail
371+ }
349372
350- // no match was found.
351- // However, in partial mode, we can't say this is necessarily over.
352- // If there's more *pattern* left, then
353- /* istanbul ignore if */
354- if ( partial ) {
355- // ran out of file
356- this . debug ( '\n>>> no match, partial?' , file , fr , pattern , pr )
357- if ( fr === fl ) return true
373+ const [ body , after ] = bs
374+ while ( fileIndex <= after ) {
375+ const m = this . _matchOne (
376+ file . slice ( 0 , fileIndex + body . length ) ,
377+ body ,
378+ partial ,
379+ fileIndex ,
380+ 0
381+ )
382+ // if limit exceeded, no match. intentional false negative,
383+ // acceptable break in correctness for security.
384+ if ( m && globStarDepth < this . maxGlobstarRecursion ) {
385+ const sub = this . _matchGlobStarBodySections (
386+ file , bodySegments ,
387+ fileIndex + body . length , bodyIndex + 1 ,
388+ partial , globStarDepth + 1 , sawTail
389+ )
390+ if ( sub !== false ) {
391+ return sub
358392 }
393+ }
394+ const f = file [ fileIndex ]
395+ if ( f === '.' || f === '..' ||
396+ ( ! this . options . dot && f . charAt ( 0 ) === '.' ) ) {
359397 return false
360398 }
399+ fileIndex ++
400+ }
401+ return null
402+ }
403+
404+ _matchOne ( file , pattern , partial , fileIndex , patternIndex ) {
405+ let fi , pi , fl , pl
406+ for (
407+ fi = fileIndex , pi = patternIndex , fl = file . length , pl = pattern . length
408+ ; ( fi < fl ) && ( pi < pl )
409+ ; fi ++ , pi ++
410+ ) {
411+ this . debug ( 'matchOne loop' )
412+ const p = pattern [ pi ]
413+ const f = file [ fi ]
414+
415+ this . debug ( pattern , p , f )
416+
417+ // should be impossible.
418+ // some invalid regexp stuff in the set.
419+ /* istanbul ignore if */
420+ if ( p === false || p === GLOBSTAR ) return false
361421
362422 // something other than **
363423 // non-magic patterns just have to match exactly
364424 // patterns with magic have been turned into regexps.
365- var hit
425+ let hit
366426 if ( typeof p === 'string' ) {
367427 hit = f === p
368428 this . debug ( 'string match' , p , f , hit )
@@ -374,17 +434,6 @@ class Minimatch {
374434 if ( ! hit ) return false
375435 }
376436
377- // Note: ending in / means that we'll get a final ""
378- // at the end of the pattern. This can only match a
379- // corresponding "" at the end of the file.
380- // If the file ends in /, then it can only match a
381- // a pattern that ends in /, unless the pattern just
382- // doesn't have any more for it. But, a/b/ should *not*
383- // match "a/b/*", even though "" matches against the
384- // [^/]*? pattern, except in partial mode, where it might
385- // simply not be reached yet.
386- // However, a/b/ should still satisfy a/*
387-
388437 // now either we fell off the end of the pattern, or we're done.
389438 if ( fi === fl && pi === pl ) {
390439 // ran out of pattern and filename at the same time.
0 commit comments