minimatch 3.1.3 → 3.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +37 -0
  2. package/minimatch.js +157 -102
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -10,6 +10,43 @@ This is the matching library used internally by npm.
10
10
  It works by converting glob expressions into JavaScript `RegExp`
11
11
  objects.
12
12
 
13
+ ## Important Security Consideration!
14
+
15
+ > [!WARNING]
16
+ > This library uses JavaScript regular expressions. Please read
17
+ > the following warning carefully, and be thoughtful about what
18
+ > you provide to this library in production systems.
19
+
20
+ _Any_ library in JavaScript that deals with matching string
21
+ patterns using regular expressions will be subject to
22
+ [ReDoS](https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS)
23
+ if the pattern is generated using untrusted input.
24
+
25
+ Efforts have been made to mitigate risk as much as is feasible in
26
+ such a library, providing maximum recursion depths and so forth,
27
+ but these measures can only ultimately protect against accidents,
28
+ not malice. A dedicated attacker can _always_ find patterns that
29
+ cannot be defended against by a bash-compatible glob pattern
30
+ matching system that uses JavaScript regular expressions.
31
+
32
+ To be extremely clear:
33
+
34
+ > [!WARNING]
35
+ > **If you create a system where you take user input, and use
36
+ > that input as the source of a Regular Expression pattern, in
37
+ > this or any extant glob matcher in JavaScript, you will be
38
+ > pwned.**
39
+
40
+ A future version of this library _may_ use a different matching
41
+ algorithm which does not exhibit backtracking problems. If and
42
+ when that happens, it will likely be a sweeping change, and those
43
+ improvements will **not** be backported to legacy versions.
44
+
45
+ In the near term, it is not reasonable to continue to play
46
+ whack-a-mole with security advisories, and so any future ReDoS
47
+ reports will be considered "working as intended", and resolved
48
+ entirely by this warning.
49
+
13
50
  ## Usage
14
51
 
15
52
  ```javascript
package/minimatch.js CHANGED
@@ -142,6 +142,8 @@ function Minimatch (pattern, options) {
142
142
  }
143
143
 
144
144
  this.options = options
145
+ this.maxGlobstarRecursion = options.maxGlobstarRecursion !== undefined
146
+ ? options.maxGlobstarRecursion : 200
145
147
  this.set = []
146
148
  this.pattern = pattern
147
149
  this.regexp = null
@@ -787,109 +789,173 @@ Minimatch.prototype.match = function match (f, partial) {
787
789
  // out of pattern, then that's fine, as long as all
788
790
  // the parts match.
789
791
  Minimatch.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
+ }
791
797
 
792
- this.debug('matchOne',
793
- { 'this': this, file: file, pattern: pattern })
798
+ Minimatch.prototype._matchGlobstar = function (file, pattern, partial, fileIndex, patternIndex) {
799
+ var i
794
800
 
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
+ }
796
806
 
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
+ }
806
812
 
807
- this.debug(pattern, p, f)
813
+ var head = pattern.slice(patternIndex, firstgs)
814
+ var body = partial ? pattern.slice(firstgs + 1) : pattern.slice(firstgs + 1, lastgs)
815
+ var tail = partial ? [] : pattern.slice(lastgs + 1)
808
816
 
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
854
843
  }
844
+ fileTailMatch = tail.length + 1
845
+ }
846
+ }
855
847
 
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
880
857
  }
858
+ }
859
+ return partial || 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
+ }
881
885
 
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
890
905
  }
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) === '.')) {
891
935
  return false
892
936
  }
937
+ fileIndex++
938
+ }
939
+ return partial || 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
893
959
 
894
960
  // something other than **
895
961
  // non-magic patterns just have to match exactly
@@ -906,17 +972,6 @@ Minimatch.prototype.matchOne = function (file, pattern, partial) {
906
972
  if (!hit) return false
907
973
  }
908
974
 
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
-
920
975
  // now either we fell off the end of the pattern, or we're done.
921
976
  if (fi === fl && pi === pl) {
922
977
  // ran out of pattern and filename at the same time.
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "author": "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me)",
3
3
  "name": "minimatch",
4
4
  "description": "a glob matcher in javascript",
5
- "version": "3.1.3",
5
+ "version": "3.1.5",
6
6
  "publishConfig": {
7
7
  "tag": "legacy-v3"
8
8
  },