gitea源码

glob.go 4.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. // Copyright 2025 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package glob
  4. import (
  5. "errors"
  6. "fmt"
  7. "regexp"
  8. "code.gitea.io/gitea/modules/util"
  9. )
  10. // Reference: https://github.com/gobwas/glob/blob/master/glob.go
  11. type Glob interface {
  12. Match(string) bool
  13. }
  14. type globCompiler struct {
  15. nonSeparatorChars string
  16. globPattern []rune
  17. regexpPattern string
  18. regexp *regexp.Regexp
  19. pos int
  20. }
  21. // compileChars compiles character class patterns like [abc] or [!abc]
  22. func (g *globCompiler) compileChars() (string, error) {
  23. result := ""
  24. if g.pos < len(g.globPattern) && g.globPattern[g.pos] == '!' {
  25. g.pos++
  26. result += "^"
  27. }
  28. for g.pos < len(g.globPattern) {
  29. c := g.globPattern[g.pos]
  30. g.pos++
  31. if c == ']' {
  32. return "[" + result + "]", nil
  33. }
  34. if c == '\\' {
  35. if g.pos >= len(g.globPattern) {
  36. return "", errors.New("unterminated character class escape")
  37. }
  38. result += "\\" + string(g.globPattern[g.pos])
  39. g.pos++
  40. } else {
  41. result += string(c)
  42. }
  43. }
  44. return "", errors.New("unterminated character class")
  45. }
  46. // compile compiles the glob pattern into a regular expression
  47. func (g *globCompiler) compile(subPattern bool) (string, error) {
  48. result := ""
  49. for g.pos < len(g.globPattern) {
  50. c := g.globPattern[g.pos]
  51. g.pos++
  52. if subPattern && c == '}' {
  53. return "(" + result + ")", nil
  54. }
  55. switch c {
  56. case '*':
  57. if g.pos < len(g.globPattern) && g.globPattern[g.pos] == '*' {
  58. g.pos++
  59. result += ".*" // match any sequence of characters
  60. } else {
  61. result += g.nonSeparatorChars + "*" // match any sequence of non-separator characters
  62. }
  63. case '?':
  64. result += g.nonSeparatorChars // match any single non-separator character
  65. case '[':
  66. chars, err := g.compileChars()
  67. if err != nil {
  68. return "", err
  69. }
  70. result += chars
  71. case '{':
  72. subResult, err := g.compile(true)
  73. if err != nil {
  74. return "", err
  75. }
  76. result += subResult
  77. case ',':
  78. if subPattern {
  79. result += "|"
  80. } else {
  81. result += ","
  82. }
  83. case '\\':
  84. if g.pos >= len(g.globPattern) {
  85. return "", errors.New("no character to escape")
  86. }
  87. result += "\\" + string(g.globPattern[g.pos])
  88. g.pos++
  89. case '.', '+', '^', '$', '(', ')', '|':
  90. result += "\\" + string(c) // escape regexp special characters
  91. default:
  92. result += string(c)
  93. }
  94. }
  95. return result, nil
  96. }
  97. func newGlobCompiler(pattern string, separators ...rune) (Glob, error) {
  98. g := &globCompiler{globPattern: []rune(pattern)}
  99. // Escape separators for use in character class
  100. escapedSeparators := regexp.QuoteMeta(string(separators))
  101. if escapedSeparators != "" {
  102. g.nonSeparatorChars = "[^" + escapedSeparators + "]"
  103. } else {
  104. g.nonSeparatorChars = "."
  105. }
  106. compiled, err := g.compile(false)
  107. if err != nil {
  108. return nil, err
  109. }
  110. g.regexpPattern = "^" + compiled + "$"
  111. regex, err := regexp.Compile(g.regexpPattern)
  112. if err != nil {
  113. return nil, fmt.Errorf("failed to compile regexp: %w", err)
  114. }
  115. g.regexp = regex
  116. return g, nil
  117. }
  118. func (g *globCompiler) Match(s string) bool {
  119. return g.regexp.MatchString(s)
  120. }
  121. func Compile(pattern string, separators ...rune) (Glob, error) {
  122. return newGlobCompiler(pattern, separators...)
  123. }
  124. func MustCompile(pattern string, separators ...rune) Glob {
  125. g, err := Compile(pattern, separators...)
  126. if err != nil {
  127. panic(err)
  128. }
  129. return g
  130. }
  131. func IsSpecialByte(c byte) bool {
  132. return c == '*' || c == '?' || c == '\\' || c == '[' || c == ']' || c == '{' || c == '}'
  133. }
  134. // QuoteMeta returns a string that quotes all glob pattern meta characters
  135. // inside the argument text; For example, QuoteMeta(`{foo*}`) returns `\[foo\*\]`.
  136. // Reference: https://github.com/gobwas/glob/blob/master/glob.go
  137. func QuoteMeta(s string) string {
  138. pos := 0
  139. for pos < len(s) && !IsSpecialByte(s[pos]) {
  140. pos++
  141. }
  142. if pos == len(s) {
  143. return s
  144. }
  145. b := make([]byte, pos+2*(len(s)-pos))
  146. copy(b, s[0:pos])
  147. to := pos
  148. for ; pos < len(s); pos++ {
  149. if IsSpecialByte(s[pos]) {
  150. b[to] = '\\'
  151. to++
  152. }
  153. b[to] = s[pos]
  154. to++
  155. }
  156. return util.UnsafeBytesToString(b[0:to])
  157. }