Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 43 additions & 37 deletions modules/glob/glob.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"fmt"
"regexp"
"slices"
"strings"

"code.gitea.io/gitea/modules/util"
)
Expand All @@ -29,50 +30,52 @@ type globCompiler struct {
globPattern []rune
regexpPattern string
regexp *regexp.Regexp
builder *strings.Builder
pos int
negativeFlip bool
}

// compileChars compiles character class patterns like [abc] or [!abc]
func (g *globCompiler) compileChars() (string, error) {
result := ""
func (g *globCompiler) compileChars() error {
g.builder.WriteByte('[')
if g.pos < len(g.globPattern) && g.globPattern[g.pos] == '!' {
g.pos++
result += "^"
g.builder.WriteByte('^')
}

for g.pos < len(g.globPattern) {
c := g.globPattern[g.pos]
g.pos++

if c == ']' {
return "[" + result + "]", nil
g.builder.WriteByte(']')
return nil
}

if c == '\\' {
if g.pos >= len(g.globPattern) {
return "", errors.New("unterminated character class escape")
return errors.New("unterminated character class escape")
}
result += "\\" + string(g.globPattern[g.pos])
g.builder.WriteByte('\\')
g.builder.WriteRune(g.globPattern[g.pos])
g.pos++
} else {
result += string(c)
g.builder.WriteRune(c)
}
}

return "", errors.New("unterminated character class")
return errors.New("unterminated character class")
}

// compile compiles the glob pattern into a regular expression
func (g *globCompiler) compile(subPattern bool) (string, error) {
result := ""

func (g *globCompiler) compile(subPattern bool) error {
for g.pos < len(g.globPattern) {
c := g.globPattern[g.pos]
g.pos++

if subPattern && c == '}' {
return "(" + result + ")", nil
g.builder.WriteByte(')')
return nil
}

switch c {
Expand All @@ -95,59 +98,61 @@ func (g *globCompiler) compile(subPattern bool) (string, error) {
} else {
g.pos++
}
result += ".*" // match any sequence of characters
g.builder.WriteString(".*") // match any sequence of characters
} else {
result += g.nonSeparatorChars + "*" // match any sequence of non-separator characters
g.builder.WriteString(g.nonSeparatorChars)
g.builder.WriteByte('*') // match any sequence of non-separator characters
}
case '?':
if g.regexpQuestion {
result += "?"
g.builder.WriteByte('?')
} else {
result += g.nonSeparatorChars // match any single non-separator character
g.builder.WriteString(g.nonSeparatorChars) // match any single non-separator character
}
case '+':
if g.regexpPlus {
result += "+"
g.builder.WriteByte('+')
} else {
result += "\\" + string(c)
g.builder.WriteByte('\\')
g.builder.WriteRune(c)
}
case '[':
chars, err := g.compileChars()
if err != nil {
return "", err
if err := g.compileChars(); err != nil {
return err
}
result += chars
case '{':
subResult, err := g.compile(true)
if err != nil {
return "", err
g.builder.WriteByte('(')
if err := g.compile(true); err != nil {
return err
}
result += subResult
case ',':
if subPattern {
result += "|"
g.builder.WriteByte('|')
} else {
result += ","
g.builder.WriteByte(',')
}
case '\\':
if g.pos >= len(g.globPattern) {
return "", errors.New("no character to escape")
return errors.New("no character to escape")
}
result += "\\" + string(g.globPattern[g.pos])
g.builder.WriteByte('\\')
g.builder.WriteRune(g.globPattern[g.pos])
g.pos++
case '.', '^', '$', '(', ')', '|':
result += "\\" + string(c) // escape regexp special characters
g.builder.WriteByte('\\')
g.builder.WriteRune(c) // escape regexp special characters
default:
result += string(c)
g.builder.WriteRune(c)
}
}

return result, nil
return nil
}

func initGlobCompiler(g *globCompiler, pattern string, separators []rune) (Glob, error) {
g.globPattern = []rune(pattern)
g.separators = separators
g.builder = new(strings.Builder)

// Escape separators for use in character class
escapedSeparators := regexp.QuoteMeta(string(separators))
Expand All @@ -162,12 +167,13 @@ func initGlobCompiler(g *globCompiler, pattern string, separators []rune) (Glob,
g.pos++
}

compiled, err := g.compile(false)
if err != nil {
g.builder.WriteByte('^')
if err := g.compile(false); err != nil {
return nil, err
}

g.regexpPattern = "^" + compiled + "$"
g.builder.WriteByte('$')
g.regexpPattern = g.builder.String()
g.builder = nil

regex, err := regexp.Compile(g.regexpPattern)
if err != nil {
Expand Down