mirror of
https://github.com/go-gitea/gitea.git
synced 2025-09-22 10:10:03 +00:00
Replace gobwas/glob package (#35478)
https://github.com/gobwas/glob is unmaintained and has bugs.
This commit is contained in:
@@ -10,11 +10,11 @@ import (
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/glob"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
webhook_module "code.gitea.io/gitea/modules/webhook"
|
||||
|
||||
"github.com/gobwas/glob"
|
||||
"github.com/nektos/act/pkg/jobparser"
|
||||
"github.com/nektos/act/pkg/model"
|
||||
"github.com/nektos/act/pkg/workflowpattern"
|
||||
|
184
modules/glob/glob.go
Normal file
184
modules/glob/glob.go
Normal file
@@ -0,0 +1,184 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package glob
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
)
|
||||
|
||||
// Reference: https://github.com/gobwas/glob/blob/master/glob.go
|
||||
|
||||
type Glob interface {
|
||||
Match(string) bool
|
||||
}
|
||||
|
||||
type globCompiler struct {
|
||||
nonSeparatorChars string
|
||||
globPattern []rune
|
||||
regexpPattern string
|
||||
regexp *regexp.Regexp
|
||||
pos int
|
||||
}
|
||||
|
||||
// compileChars compiles character class patterns like [abc] or [!abc]
|
||||
func (g *globCompiler) compileChars() (string, error) {
|
||||
result := ""
|
||||
if g.pos < len(g.globPattern) && g.globPattern[g.pos] == '!' {
|
||||
g.pos++
|
||||
result += "^"
|
||||
}
|
||||
|
||||
for g.pos < len(g.globPattern) {
|
||||
c := g.globPattern[g.pos]
|
||||
g.pos++
|
||||
|
||||
if c == ']' {
|
||||
return "[" + result + "]", nil
|
||||
}
|
||||
|
||||
if c == '\\' {
|
||||
if g.pos >= len(g.globPattern) {
|
||||
return "", errors.New("unterminated character class escape")
|
||||
}
|
||||
result += "\\" + string(g.globPattern[g.pos])
|
||||
g.pos++
|
||||
} else {
|
||||
result += string(c)
|
||||
}
|
||||
}
|
||||
|
||||
return "", errors.New("unterminated character class")
|
||||
}
|
||||
|
||||
// compile compiles the glob pattern into a regular expression
|
||||
func (g *globCompiler) compile(subPattern bool) (string, error) {
|
||||
result := ""
|
||||
|
||||
for g.pos < len(g.globPattern) {
|
||||
c := g.globPattern[g.pos]
|
||||
g.pos++
|
||||
|
||||
if subPattern && c == '}' {
|
||||
return "(" + result + ")", nil
|
||||
}
|
||||
|
||||
switch c {
|
||||
case '*':
|
||||
if g.pos < len(g.globPattern) && g.globPattern[g.pos] == '*' {
|
||||
g.pos++
|
||||
result += ".*" // match any sequence of characters
|
||||
} else {
|
||||
result += g.nonSeparatorChars + "*" // match any sequence of non-separator characters
|
||||
}
|
||||
case '?':
|
||||
result += g.nonSeparatorChars // match any single non-separator character
|
||||
case '[':
|
||||
chars, err := g.compileChars()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
result += chars
|
||||
case '{':
|
||||
subResult, err := g.compile(true)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
result += subResult
|
||||
case ',':
|
||||
if subPattern {
|
||||
result += "|"
|
||||
} else {
|
||||
result += ","
|
||||
}
|
||||
case '\\':
|
||||
if g.pos >= len(g.globPattern) {
|
||||
return "", errors.New("no character to escape")
|
||||
}
|
||||
result += "\\" + string(g.globPattern[g.pos])
|
||||
g.pos++
|
||||
case '.', '+', '^', '$', '(', ')', '|':
|
||||
result += "\\" + string(c) // escape regexp special characters
|
||||
default:
|
||||
result += string(c)
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func newGlobCompiler(pattern string, separators ...rune) (Glob, error) {
|
||||
g := &globCompiler{globPattern: []rune(pattern)}
|
||||
|
||||
// Escape separators for use in character class
|
||||
escapedSeparators := regexp.QuoteMeta(string(separators))
|
||||
if escapedSeparators != "" {
|
||||
g.nonSeparatorChars = "[^" + escapedSeparators + "]"
|
||||
} else {
|
||||
g.nonSeparatorChars = "."
|
||||
}
|
||||
|
||||
compiled, err := g.compile(false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
g.regexpPattern = "^" + compiled + "$"
|
||||
|
||||
regex, err := regexp.Compile(g.regexpPattern)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to compile regexp: %w", err)
|
||||
}
|
||||
|
||||
g.regexp = regex
|
||||
return g, nil
|
||||
}
|
||||
|
||||
func (g *globCompiler) Match(s string) bool {
|
||||
return g.regexp.MatchString(s)
|
||||
}
|
||||
|
||||
func Compile(pattern string, separators ...rune) (Glob, error) {
|
||||
return newGlobCompiler(pattern, separators...)
|
||||
}
|
||||
|
||||
func MustCompile(pattern string, separators ...rune) Glob {
|
||||
g, err := Compile(pattern, separators...)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return g
|
||||
}
|
||||
|
||||
func IsSpecialByte(c byte) bool {
|
||||
return c == '*' || c == '?' || c == '\\' || c == '[' || c == ']' || c == '{' || c == '}'
|
||||
}
|
||||
|
||||
// QuoteMeta returns a string that quotes all glob pattern meta characters
|
||||
// inside the argument text; For example, QuoteMeta(`{foo*}`) returns `\[foo\*\]`.
|
||||
// Reference: https://github.com/gobwas/glob/blob/master/glob.go
|
||||
func QuoteMeta(s string) string {
|
||||
pos := 0
|
||||
for pos < len(s) && !IsSpecialByte(s[pos]) {
|
||||
pos++
|
||||
}
|
||||
if pos == len(s) {
|
||||
return s
|
||||
}
|
||||
b := make([]byte, pos+2*(len(s)-pos))
|
||||
copy(b, s[0:pos])
|
||||
to := pos
|
||||
for ; pos < len(s); pos++ {
|
||||
if IsSpecialByte(s[pos]) {
|
||||
b[to] = '\\'
|
||||
to++
|
||||
}
|
||||
b[to] = s[pos]
|
||||
to++
|
||||
}
|
||||
return util.UnsafeBytesToString(b[0:to])
|
||||
}
|
208
modules/glob/glob_test.go
Normal file
208
modules/glob/glob_test.go
Normal file
@@ -0,0 +1,208 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// Copyright (c) 2016 Sergey Kamardin
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
//nolint:revive // the code is from gobwas/glob
|
||||
package glob
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// Reference: https://github.com/gobwas/glob/blob/master/glob_test.go
|
||||
|
||||
const (
|
||||
pattern_all = "[a-z][!a-x]*cat*[h][!b]*eyes*"
|
||||
regexp_all = `^[a-z][^a-x].*cat.*[h][^b].*eyes.*$`
|
||||
fixture_all_match = "my cat has very bright eyes"
|
||||
fixture_all_mismatch = "my dog has very bright eyes"
|
||||
|
||||
pattern_plain = "google.com"
|
||||
regexp_plain = `^google\.com$`
|
||||
fixture_plain_match = "google.com"
|
||||
fixture_plain_mismatch = "gobwas.com"
|
||||
|
||||
pattern_multiple = "https://*.google.*"
|
||||
regexp_multiple = `^https:\/\/.*\.google\..*$`
|
||||
fixture_multiple_match = "https://account.google.com"
|
||||
fixture_multiple_mismatch = "https://google.com"
|
||||
|
||||
pattern_alternatives = "{https://*.google.*,*yandex.*,*yahoo.*,*mail.ru}"
|
||||
regexp_alternatives = `^(https:\/\/.*\.google\..*|.*yandex\..*|.*yahoo\..*|.*mail\.ru)$`
|
||||
fixture_alternatives_match = "http://yahoo.com"
|
||||
fixture_alternatives_mismatch = "http://google.com"
|
||||
|
||||
pattern_alternatives_suffix = "{https://*gobwas.com,http://exclude.gobwas.com}"
|
||||
regexp_alternatives_suffix = `^(https:\/\/.*gobwas\.com|http://exclude.gobwas.com)$`
|
||||
fixture_alternatives_suffix_first_match = "https://safe.gobwas.com"
|
||||
fixture_alternatives_suffix_first_mismatch = "http://safe.gobwas.com"
|
||||
fixture_alternatives_suffix_second = "http://exclude.gobwas.com"
|
||||
|
||||
pattern_prefix = "abc*"
|
||||
regexp_prefix = `^abc.*$`
|
||||
pattern_suffix = "*def"
|
||||
regexp_suffix = `^.*def$`
|
||||
pattern_prefix_suffix = "ab*ef"
|
||||
regexp_prefix_suffix = `^ab.*ef$`
|
||||
fixture_prefix_suffix_match = "abcdef"
|
||||
fixture_prefix_suffix_mismatch = "af"
|
||||
|
||||
pattern_alternatives_combine_lite = "{abc*def,abc?def,abc[zte]def}"
|
||||
regexp_alternatives_combine_lite = `^(abc.*def|abc.def|abc[zte]def)$`
|
||||
fixture_alternatives_combine_lite = "abczdef"
|
||||
|
||||
pattern_alternatives_combine_hard = "{abc*[a-c]def,abc?[d-g]def,abc[zte]?def}"
|
||||
regexp_alternatives_combine_hard = `^(abc.*[a-c]def|abc.[d-g]def|abc[zte].def)$`
|
||||
fixture_alternatives_combine_hard = "abczqdef"
|
||||
)
|
||||
|
||||
type test struct {
|
||||
pattern, match string
|
||||
should bool
|
||||
delimiters []rune
|
||||
}
|
||||
|
||||
func glob(s bool, p, m string, d ...rune) test {
|
||||
return test{p, m, s, d}
|
||||
}
|
||||
|
||||
func TestGlob(t *testing.T) {
|
||||
for _, test := range []test{
|
||||
glob(true, "* ?at * eyes", "my cat has very bright eyes"),
|
||||
|
||||
glob(true, "", ""),
|
||||
glob(false, "", "b"),
|
||||
|
||||
glob(true, "*ä", "åä"),
|
||||
glob(true, "abc", "abc"),
|
||||
glob(true, "a*c", "abc"),
|
||||
glob(true, "a*c", "a12345c"),
|
||||
glob(true, "a?c", "a1c"),
|
||||
glob(true, "a.b", "a.b", '.'),
|
||||
glob(true, "a.*", "a.b", '.'),
|
||||
glob(true, "a.**", "a.b.c", '.'),
|
||||
glob(true, "a.?.c", "a.b.c", '.'),
|
||||
glob(true, "a.?.?", "a.b.c", '.'),
|
||||
glob(true, "?at", "cat"),
|
||||
glob(true, "?at", "fat"),
|
||||
glob(true, "*", "abc"),
|
||||
glob(true, `\*`, "*"),
|
||||
glob(true, "**", "a.b.c", '.'),
|
||||
|
||||
glob(false, "?at", "at"),
|
||||
glob(false, "?at", "fat", 'f'),
|
||||
glob(false, "a.*", "a.b.c", '.'),
|
||||
glob(false, "a.?.c", "a.bb.c", '.'),
|
||||
glob(false, "*", "a.b.c", '.'),
|
||||
|
||||
glob(true, "*test", "this is a test"),
|
||||
glob(true, "this*", "this is a test"),
|
||||
glob(true, "*is *", "this is a test"),
|
||||
glob(true, "*is*a*", "this is a test"),
|
||||
glob(true, "**test**", "this is a test"),
|
||||
glob(true, "**is**a***test*", "this is a test"),
|
||||
|
||||
glob(false, "*is", "this is a test"),
|
||||
glob(false, "*no*", "this is a test"),
|
||||
glob(true, "[!a]*", "this is a test3"),
|
||||
|
||||
glob(true, "*abc", "abcabc"),
|
||||
glob(true, "**abc", "abcabc"),
|
||||
glob(true, "???", "abc"),
|
||||
glob(true, "?*?", "abc"),
|
||||
glob(true, "?*?", "ac"),
|
||||
glob(false, "sta", "stagnation"),
|
||||
glob(true, "sta*", "stagnation"),
|
||||
glob(false, "sta?", "stagnation"),
|
||||
glob(false, "sta?n", "stagnation"),
|
||||
|
||||
glob(true, "{abc,def}ghi", "defghi"),
|
||||
glob(true, "{abc,abcd}a", "abcda"),
|
||||
glob(true, "{a,ab}{bc,f}", "abc"),
|
||||
glob(true, "{*,**}{a,b}", "ab"),
|
||||
glob(false, "{*,**}{a,b}", "ac"),
|
||||
|
||||
glob(true, "/{rate,[a-z][a-z][a-z]}*", "/rate"),
|
||||
glob(true, "/{rate,[0-9][0-9][0-9]}*", "/rate"),
|
||||
glob(true, "/{rate,[a-z][a-z][a-z]}*", "/usd"),
|
||||
|
||||
glob(true, "{*.google.*,*.yandex.*}", "www.google.com", '.'),
|
||||
glob(true, "{*.google.*,*.yandex.*}", "www.yandex.com", '.'),
|
||||
glob(false, "{*.google.*,*.yandex.*}", "yandex.com", '.'),
|
||||
glob(false, "{*.google.*,*.yandex.*}", "google.com", '.'),
|
||||
|
||||
glob(true, "{*.google.*,yandex.*}", "www.google.com", '.'),
|
||||
glob(true, "{*.google.*,yandex.*}", "yandex.com", '.'),
|
||||
glob(false, "{*.google.*,yandex.*}", "www.yandex.com", '.'),
|
||||
glob(false, "{*.google.*,yandex.*}", "google.com", '.'),
|
||||
|
||||
glob(true, "*//{,*.}example.com", "https://www.example.com"),
|
||||
glob(true, "*//{,*.}example.com", "http://example.com"),
|
||||
glob(false, "*//{,*.}example.com", "http://example.com.net"),
|
||||
|
||||
glob(true, pattern_all, fixture_all_match),
|
||||
glob(false, pattern_all, fixture_all_mismatch),
|
||||
|
||||
glob(true, pattern_plain, fixture_plain_match),
|
||||
glob(false, pattern_plain, fixture_plain_mismatch),
|
||||
|
||||
glob(true, pattern_multiple, fixture_multiple_match),
|
||||
glob(false, pattern_multiple, fixture_multiple_mismatch),
|
||||
|
||||
glob(true, pattern_alternatives, fixture_alternatives_match),
|
||||
glob(false, pattern_alternatives, fixture_alternatives_mismatch),
|
||||
|
||||
glob(true, pattern_alternatives_suffix, fixture_alternatives_suffix_first_match),
|
||||
glob(false, pattern_alternatives_suffix, fixture_alternatives_suffix_first_mismatch),
|
||||
glob(true, pattern_alternatives_suffix, fixture_alternatives_suffix_second),
|
||||
|
||||
glob(true, pattern_alternatives_combine_hard, fixture_alternatives_combine_hard),
|
||||
|
||||
glob(true, pattern_alternatives_combine_lite, fixture_alternatives_combine_lite),
|
||||
|
||||
glob(true, pattern_prefix, fixture_prefix_suffix_match),
|
||||
glob(false, pattern_prefix, fixture_prefix_suffix_mismatch),
|
||||
|
||||
glob(true, pattern_suffix, fixture_prefix_suffix_match),
|
||||
glob(false, pattern_suffix, fixture_prefix_suffix_mismatch),
|
||||
|
||||
glob(true, pattern_prefix_suffix, fixture_prefix_suffix_match),
|
||||
glob(false, pattern_prefix_suffix, fixture_prefix_suffix_mismatch),
|
||||
} {
|
||||
g, err := Compile(test.pattern, test.delimiters...)
|
||||
require.NoError(t, err)
|
||||
result := g.Match(test.match)
|
||||
assert.Equal(t, test.should, result, "pattern %q matching %q should be %v but got %v, compiled=%s", test.pattern, test.match, test.should, result, g.(*globCompiler).regexpPattern)
|
||||
}
|
||||
}
|
||||
|
||||
func TestQuoteMeta(t *testing.T) {
|
||||
for id, test := range []struct {
|
||||
in, out string
|
||||
}{
|
||||
{
|
||||
in: `[foo*]`,
|
||||
out: `\[foo\*\]`,
|
||||
},
|
||||
{
|
||||
in: `{foo*}`,
|
||||
out: `\{foo\*\}`,
|
||||
},
|
||||
{
|
||||
in: `*?\[]{}`,
|
||||
out: `\*\?\\\[\]\{\}`,
|
||||
},
|
||||
{
|
||||
in: `some text and *?\[]{}`,
|
||||
out: `some text and \*\?\\\[\]\{\}`,
|
||||
},
|
||||
} {
|
||||
act := QuoteMeta(test.in)
|
||||
assert.Equal(t, test.out, act, "QuoteMeta(%q)", test.in)
|
||||
_, err := Compile(act)
|
||||
assert.NoError(t, err, "#%d _, err := Compile(QuoteMeta(%q) = %q); err = %q", id, test.in, act, err)
|
||||
}
|
||||
}
|
@@ -10,10 +10,9 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"code.gitea.io/gitea/modules/glob"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
"github.com/gobwas/glob"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@@ -3,7 +3,7 @@
|
||||
|
||||
package setting
|
||||
|
||||
import "github.com/gobwas/glob"
|
||||
import "code.gitea.io/gitea/modules/glob"
|
||||
|
||||
type GlobMatcher struct {
|
||||
compiledGlob glob.Glob
|
||||
|
@@ -9,10 +9,9 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/modules/glob"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/structs"
|
||||
|
||||
"github.com/gobwas/glob"
|
||||
)
|
||||
|
||||
// enumerates all the types of captchas
|
||||
|
@@ -6,10 +6,10 @@ package setting
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/modules/glob"
|
||||
"code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
|
||||
"github.com/gobwas/glob"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
|
@@ -10,10 +10,10 @@ import (
|
||||
|
||||
"code.gitea.io/gitea/modules/auth"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/glob"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"gitea.com/go-chi/binding"
|
||||
"github.com/gobwas/glob"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@@ -6,8 +6,9 @@ package validation
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/modules/glob"
|
||||
|
||||
"gitea.com/go-chi/binding"
|
||||
"github.com/gobwas/glob"
|
||||
)
|
||||
|
||||
func getGlobPatternErrorString(pattern string) string {
|
||||
|
@@ -11,9 +11,8 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"code.gitea.io/gitea/modules/glob"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
"github.com/gobwas/glob"
|
||||
)
|
||||
|
||||
type globalVarsStruct struct {
|
||||
|
Reference in New Issue
Block a user