snapshot of https://github.com/go-gitea/gitea.git (main, 2026-09-15; blobs > 131072B omitted)
Some checks failed
cache-seeder / prune (push) Has been skipped
cache-seeder / frontend (push) Failing after 3s
cache-seeder / python (push) Failing after 3s
cache-seeder / gobuild (push) Failing after 4s
cache-seeder / lint (bindata, lint-backend) (push) Failing after 3s
giteabot backport / giteabot (push) Has been skipped
giteabot / giteabot (push) Has been skipped
cache-prune / prune (push) Has been skipped
release-nightly-snapcraft / build-and-publish (ubuntu-24.04) (push) Has been cancelled
release-nightly-snapcraft / build-and-publish (ubuntu-24.04-arm) (push) Has been cancelled
release-nightly / nightly-binary (push) Has been cancelled
release-nightly / nightly-container (push) Has been cancelled
cron-renovate / cron-renovate (push) Has been skipped
Some checks failed
cache-seeder / prune (push) Has been skipped
cache-seeder / frontend (push) Failing after 3s
cache-seeder / python (push) Failing after 3s
cache-seeder / gobuild (push) Failing after 4s
cache-seeder / lint (bindata, lint-backend) (push) Failing after 3s
giteabot backport / giteabot (push) Has been skipped
giteabot / giteabot (push) Has been skipped
cache-prune / prune (push) Has been skipped
release-nightly-snapcraft / build-and-publish (ubuntu-24.04) (push) Has been cancelled
release-nightly-snapcraft / build-and-publish (ubuntu-24.04-arm) (push) Has been cancelled
release-nightly / nightly-binary (push) Has been cancelled
release-nightly / nightly-container (push) Has been cancelled
cron-renovate / cron-renovate (push) Has been skipped
This commit is contained in:
150
modules/validation/binding.go
Normal file
150
modules/validation/binding.go
Normal file
@@ -0,0 +1,150 @@
|
||||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package validation
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"gitea.dev/modules/auth"
|
||||
"gitea.dev/modules/git"
|
||||
"gitea.dev/modules/glob"
|
||||
"gitea.dev/modules/json"
|
||||
"gitea.dev/modules/util"
|
||||
|
||||
"gitea.com/go-chi/binding" //nolint:depguard // this package wraps it
|
||||
)
|
||||
|
||||
const (
|
||||
ErrCustomMessage = "CustomMessage"
|
||||
ErrGitRefName = "GitRefNameError"
|
||||
ErrGlobPattern = "GlobPattern"
|
||||
ErrRegexPattern = "RegexPattern"
|
||||
ErrUsername = "UsernameError"
|
||||
ErrInvalidGroupTeamMap = "InvalidGroupTeamMap"
|
||||
ErrInvalidBadgeSlug = "InvalidBadgeSlug"
|
||||
)
|
||||
|
||||
type jsonProvider struct{}
|
||||
|
||||
func (j jsonProvider) Marshal(v any) ([]byte, error) { return json.Marshal(v) }
|
||||
|
||||
func (j jsonProvider) Unmarshal(data []byte, v any) error { return json.Unmarshal(data, v) }
|
||||
|
||||
func (j jsonProvider) NewDecoder(reader io.Reader) binding.JSONDecoder {
|
||||
return json.NewDecoder(reader)
|
||||
}
|
||||
|
||||
func (j jsonProvider) NewEncoder(writer io.Writer) binding.JSONEncoder {
|
||||
return json.NewEncoder(writer)
|
||||
}
|
||||
|
||||
func newFieldError(field reflect.StructField, cls, msg string) *BindingError {
|
||||
return &BindingError{[]string{field.Name}, cls, msg} //nolint:govet // make sure no missing fields
|
||||
}
|
||||
|
||||
// AddBindingRules adds additional binding rules
|
||||
func AddBindingRules(b *binding.Binder) {
|
||||
b.AddRuleNonZero("GitRefName", func(ctx context.Context, f *binding.ValidationField) *binding.Error {
|
||||
if !git.IsValidRefPattern(f.ValueMustString()) {
|
||||
return newFieldError(f.StructField, ErrGitRefName, "GitRefName")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
b.AddRuleNonZero("ValidUrl", func(ctx context.Context, f *binding.ValidationField) *binding.Error {
|
||||
if !IsValidURL(f.ValueMustString()) {
|
||||
return newFieldError(f.StructField, binding.ERR_URL, "Url")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
b.AddRuleNonZero("ValidSiteUrl", func(ctx context.Context, f *binding.ValidationField) *binding.Error {
|
||||
if !IsValidSiteURL(f.ValueMustString()) {
|
||||
return newFieldError(f.StructField, binding.ERR_URL, "Url")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
b.AddRuleNonZero("BadgeSlug", func(ctx context.Context, f *binding.ValidationField) *binding.Error {
|
||||
if !IsValidBadgeSlug(f.ValueMustString()) {
|
||||
return newFieldError(f.StructField, ErrInvalidBadgeSlug, "invalid badge slug")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
ruleGlobPattern := func(_ context.Context, f *binding.ValidationField) *binding.Error {
|
||||
if _, err := glob.Compile(f.ValueMustString()); err != nil {
|
||||
return newFieldError(f.StructField, ErrGlobPattern, err.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
b.AddRuleNonZero("GlobPattern", ruleGlobPattern)
|
||||
ruleRegexPattern := func(_ context.Context, f *binding.ValidationField, val string) *binding.Error {
|
||||
if _, err := regexp.Compile(val); err != nil {
|
||||
return newFieldError(f.StructField, ErrRegexPattern, err.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
b.AddRuleNonZero("RegexPattern", func(ctx context.Context, f *binding.ValidationField) *binding.Error {
|
||||
return ruleRegexPattern(ctx, f, f.ValueMustString())
|
||||
})
|
||||
b.AddRuleNonZero("GlobOrRegexPattern", func(ctx context.Context, f *binding.ValidationField) *binding.Error {
|
||||
str := f.ValueMustString()
|
||||
if len(str) >= 2 && strings.HasPrefix(str, "/") && strings.HasSuffix(str, "/") {
|
||||
return ruleRegexPattern(ctx, f, str[1:len(str)-1])
|
||||
}
|
||||
return ruleGlobPattern(ctx, f)
|
||||
})
|
||||
|
||||
b.AddRuleNonZero("Username", func(ctx context.Context, f *binding.ValidationField) *binding.Error {
|
||||
if !IsValidUsername(f.ValueMustString()) {
|
||||
return newFieldError(f.StructField, ErrUsername, "invalid username")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
b.AddRuleNonZero("ValidGroupTeamMap", func(ctx context.Context, f *binding.ValidationField) *binding.Error {
|
||||
_, err := auth.UnmarshalGroupTeamMapping(f.ValueMustString())
|
||||
if err != nil {
|
||||
return newFieldError(f.StructField, ErrInvalidGroupTeamMap, err.Error())
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func portOnly(hostport string) string {
|
||||
_, after, ok := strings.Cut(hostport, ":")
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
if _, after2, ok2 := strings.Cut(hostport, "]:"); ok2 {
|
||||
return after2
|
||||
}
|
||||
if strings.Contains(hostport, "]") {
|
||||
return ""
|
||||
}
|
||||
return after
|
||||
}
|
||||
|
||||
func validPort(p string) bool {
|
||||
for _, r := range []byte(p) {
|
||||
if r < '0' || r > '9' {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
var Binder = sync.OnceValue(func() *binding.Binder {
|
||||
b := binding.NewBinder().WithJSONProvider(jsonProvider{}).WithDefaultRules().WithNameMapper(util.ToSnakeCase)
|
||||
AddBindingRules(b)
|
||||
return b
|
||||
})
|
||||
|
||||
type (
|
||||
BindingErrors = binding.Errors
|
||||
BindingError = binding.Error
|
||||
)
|
||||
29
modules/validation/binding_test.go
Normal file
29
modules/validation/binding_test.go
Normal file
@@ -0,0 +1,29 @@
|
||||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package validation
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type (
|
||||
validationTestCase struct {
|
||||
description string
|
||||
data any
|
||||
expectedErrors BindingErrors
|
||||
}
|
||||
|
||||
TestForm struct {
|
||||
BranchName string `form:"BranchName" binding:"GitRefName"`
|
||||
URL string `form:"ValidUrl" binding:"ValidUrl"`
|
||||
GlobPattern string `form:"GlobPattern" binding:"GlobPattern"`
|
||||
RegexPattern string `form:"RegexPattern" binding:"RegexPattern"`
|
||||
}
|
||||
)
|
||||
|
||||
func performValidationTest(t *testing.T, testCase validationTestCase) {
|
||||
assert.Equal(t, testCase.expectedErrors, Binder().Validate(t.Context(), testCase.data))
|
||||
}
|
||||
56
modules/validation/glob_pattern_test.go
Normal file
56
modules/validation/glob_pattern_test.go
Normal file
@@ -0,0 +1,56 @@
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package validation
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"gitea.dev/modules/glob"
|
||||
)
|
||||
|
||||
func getGlobPatternErrorString(pattern string) string {
|
||||
// It would be unwise to rely on that glob
|
||||
// compilation errors don't ever change.
|
||||
if _, err := glob.Compile(pattern); err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func Test_GlobPatternValidation(t *testing.T) {
|
||||
globValidationTestCases := []validationTestCase{
|
||||
{
|
||||
description: "Empty glob pattern",
|
||||
data: &TestForm{
|
||||
GlobPattern: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Valid glob",
|
||||
data: &TestForm{
|
||||
GlobPattern: "{master,release*}",
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
description: "Invalid glob",
|
||||
data: &TestForm{
|
||||
GlobPattern: "[a-",
|
||||
},
|
||||
expectedErrors: BindingErrors{
|
||||
BindingError{
|
||||
FieldNames: []string{"GlobPattern"},
|
||||
Classification: ErrGlobPattern,
|
||||
Message: getGlobPatternErrorString("[a-"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range globValidationTestCases {
|
||||
t.Run(testCase.description, func(t *testing.T) {
|
||||
performValidationTest(t, testCase)
|
||||
})
|
||||
}
|
||||
}
|
||||
110
modules/validation/helpers.go
Normal file
110
modules/validation/helpers.go
Normal file
@@ -0,0 +1,110 @@
|
||||
// Copyright 2018 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package validation
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"gitea.dev/modules/glob"
|
||||
"gitea.dev/modules/setting"
|
||||
)
|
||||
|
||||
type globalVarsStruct struct {
|
||||
externalTrackerRegex *regexp.Regexp
|
||||
validUsernamePattern *regexp.Regexp
|
||||
invalidUsernamePattern *regexp.Regexp
|
||||
validBadgeSlugPattern *regexp.Regexp
|
||||
invalidBadgeSlugPattern *regexp.Regexp
|
||||
}
|
||||
|
||||
var globalVars = sync.OnceValue(func() *globalVarsStruct {
|
||||
return &globalVarsStruct{
|
||||
externalTrackerRegex: regexp.MustCompile(`({?)(?:user|repo|index)+?(}?)`),
|
||||
validUsernamePattern: regexp.MustCompile(`^[\da-zA-Z][-.\w]*$`),
|
||||
invalidUsernamePattern: regexp.MustCompile(`[-._]{2,}|[-._]$`), // No consecutive or trailing non-alphanumeric chars
|
||||
validBadgeSlugPattern: regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9._-]*$`),
|
||||
invalidBadgeSlugPattern: regexp.MustCompile(`[-._]{2,}|[-._]$`),
|
||||
}
|
||||
})
|
||||
|
||||
// IsValidURL checks if URL is valid
|
||||
func IsValidURL(uri string) bool {
|
||||
if u, err := url.ParseRequestURI(uri); err != nil ||
|
||||
(u.Scheme != "http" && u.Scheme != "https") ||
|
||||
!validPort(portOnly(u.Host)) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// IsValidSiteURL checks if URL is valid
|
||||
func IsValidSiteURL(uri string) bool {
|
||||
u, err := url.ParseRequestURI(uri)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if !validPort(portOnly(u.Host)) {
|
||||
return false
|
||||
}
|
||||
|
||||
return slices.Contains(setting.Service.ValidSiteURLSchemes, u.Scheme)
|
||||
}
|
||||
|
||||
// IsEmailDomainListed checks whether the domain of an email address
|
||||
// matches a list of domains
|
||||
func IsEmailDomainListed(globs []glob.Glob, email string) bool {
|
||||
if len(globs) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
localPart, domain, found := strings.CutLast(email, "@")
|
||||
if !found || localPart == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
domain = strings.ToLower(domain)
|
||||
|
||||
for _, g := range globs {
|
||||
if g.Match(domain) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// IsValidExternalTrackerURLFormat checks if URL matches required syntax for external trackers
|
||||
func IsValidExternalTrackerURLFormat(uri string) bool {
|
||||
if !IsValidURL(uri) {
|
||||
return false
|
||||
}
|
||||
vars := globalVars()
|
||||
// check for typoed variables like /{index/ or /[repo}
|
||||
for _, match := range vars.externalTrackerRegex.FindAllStringSubmatch(uri, -1) {
|
||||
if (match[1] == "{" || match[2] == "}") && (match[1] != "{" || match[2] != "}") {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// IsValidUsername checks if username is valid
|
||||
func IsValidUsername(name string) bool {
|
||||
// It is difficult to find a single pattern that is both readable and effective,
|
||||
// but it's easier to use positive and negative checks.
|
||||
vars := globalVars()
|
||||
return vars.validUsernamePattern.MatchString(name) && !vars.invalidUsernamePattern.MatchString(name)
|
||||
}
|
||||
|
||||
func IsValidBadgeSlug(slug string) bool {
|
||||
vars := globalVars()
|
||||
return vars.validBadgeSlugPattern.MatchString(slug) && !vars.invalidBadgeSlugPattern.MatchString(slug)
|
||||
}
|
||||
162
modules/validation/helpers_test.go
Normal file
162
modules/validation/helpers_test.go
Normal file
@@ -0,0 +1,162 @@
|
||||
// Copyright 2018 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package validation
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_IsValidURL(t *testing.T) {
|
||||
cases := []struct {
|
||||
description string
|
||||
url string
|
||||
valid bool
|
||||
}{
|
||||
{
|
||||
description: "Empty URL",
|
||||
url: "",
|
||||
valid: false,
|
||||
},
|
||||
{
|
||||
description: "Loopback IPv4 URL",
|
||||
url: "http://127.0.1.1:5678/",
|
||||
valid: true,
|
||||
},
|
||||
{
|
||||
description: "Loopback IPv6 URL",
|
||||
url: "https://[::1]/",
|
||||
valid: true,
|
||||
},
|
||||
{
|
||||
description: "Missing semicolon after schema",
|
||||
url: "http//meh/",
|
||||
valid: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range cases {
|
||||
t.Run(testCase.description, func(t *testing.T) {
|
||||
assert.Equal(t, testCase.valid, IsValidURL(testCase.url))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_IsValidExternalTrackerURLFormat(t *testing.T) {
|
||||
cases := []struct {
|
||||
description string
|
||||
url string
|
||||
valid bool
|
||||
}{
|
||||
{
|
||||
description: "Correct external tracker URL with all placeholders",
|
||||
url: "https://github.com/{user}/{repo}/issues/{index}",
|
||||
valid: true,
|
||||
},
|
||||
{
|
||||
description: "Local external tracker URL with all placeholders",
|
||||
url: "https://127.0.0.1/{user}/{repo}/issues/{index}",
|
||||
valid: true,
|
||||
},
|
||||
{
|
||||
description: "External tracker URL with typo placeholder",
|
||||
url: "https://github.com/{user}/{repo/issues/{index}",
|
||||
valid: false,
|
||||
},
|
||||
{
|
||||
description: "External tracker URL with typo placeholder",
|
||||
url: "https://github.com/[user}/{repo/issues/{index}",
|
||||
valid: false,
|
||||
},
|
||||
{
|
||||
description: "External tracker URL with typo placeholder",
|
||||
url: "https://github.com/{user}/repo}/issues/{index}",
|
||||
valid: false,
|
||||
},
|
||||
{
|
||||
description: "External tracker URL missing optional placeholder",
|
||||
url: "https://github.com/{user}/issues/{index}",
|
||||
valid: true,
|
||||
},
|
||||
{
|
||||
description: "External tracker URL missing optional placeholder",
|
||||
url: "https://github.com/{repo}/issues/{index}",
|
||||
valid: true,
|
||||
},
|
||||
{
|
||||
description: "External tracker URL missing optional placeholder",
|
||||
url: "https://github.com/issues/{index}",
|
||||
valid: true,
|
||||
},
|
||||
{
|
||||
description: "External tracker URL missing optional placeholder",
|
||||
url: "https://github.com/issues/{user}",
|
||||
valid: true,
|
||||
},
|
||||
{
|
||||
description: "External tracker URL with similar placeholder names test",
|
||||
url: "https://github.com/user/repo/issues/{index}",
|
||||
valid: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range cases {
|
||||
t.Run(testCase.description, func(t *testing.T) {
|
||||
assert.Equal(t, testCase.valid, IsValidExternalTrackerURLFormat(testCase.url))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsValidUsername(t *testing.T) {
|
||||
tests := []struct {
|
||||
arg string
|
||||
want bool
|
||||
}{
|
||||
{arg: "a", want: true},
|
||||
{arg: "abc", want: true},
|
||||
{arg: "0.b-c", want: true},
|
||||
{arg: "a.b-c_d", want: true},
|
||||
{arg: "", want: false},
|
||||
{arg: ".abc", want: false},
|
||||
{arg: "abc.", want: false},
|
||||
{arg: "a..bc", want: false},
|
||||
{arg: "a...bc", want: false},
|
||||
{arg: "a.-bc", want: false},
|
||||
{arg: "a._bc", want: false},
|
||||
{arg: "a_-bc", want: false},
|
||||
{arg: "a/bc", want: false},
|
||||
{arg: "☁️", want: false},
|
||||
{arg: "-", want: false},
|
||||
{arg: "--diff", want: false},
|
||||
{arg: "-im-here", want: false},
|
||||
{arg: "a space", want: false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.arg, func(t *testing.T) {
|
||||
assert.Equalf(t, tt.want, IsValidUsername(tt.arg), "IsValidUsername(%v)", tt.arg)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsValidBadgeSlug(t *testing.T) {
|
||||
tests := []struct {
|
||||
arg string
|
||||
want bool
|
||||
}{
|
||||
{arg: "badge-1", want: true},
|
||||
{arg: "badge.slug", want: true},
|
||||
{arg: "new", want: true},
|
||||
{arg: "Badge_1", want: true},
|
||||
{arg: "a..b", want: false},
|
||||
{arg: "a/b", want: false},
|
||||
{arg: "Awesome!", want: false},
|
||||
{arg: "Emoji 💯", want: false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.arg, func(t *testing.T) {
|
||||
assert.Equalf(t, tt.want, IsValidBadgeSlug(tt.arg), "IsValidBadgeSlug(%v)", tt.arg)
|
||||
})
|
||||
}
|
||||
}
|
||||
258
modules/validation/refname_test.go
Normal file
258
modules/validation/refname_test.go
Normal file
@@ -0,0 +1,258 @@
|
||||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package validation
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_GitRefNameValidation(t *testing.T) {
|
||||
gitRefNameValidationTestCases := []validationTestCase{
|
||||
{
|
||||
description: "Reference name contains only characters",
|
||||
data: &TestForm{
|
||||
BranchName: "test",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Reference name contains single slash",
|
||||
data: &TestForm{
|
||||
BranchName: "feature/test",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Reference name has allowed special characters",
|
||||
data: &TestForm{
|
||||
BranchName: "debian/1%1.6.0-2",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Reference name contains backslash",
|
||||
data: &TestForm{
|
||||
BranchName: "feature\\test",
|
||||
},
|
||||
expectedErrors: BindingErrors{
|
||||
BindingError{
|
||||
FieldNames: []string{"BranchName"},
|
||||
Classification: ErrGitRefName,
|
||||
Message: "GitRefName",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Reference name starts with dot",
|
||||
data: &TestForm{
|
||||
BranchName: ".test",
|
||||
},
|
||||
expectedErrors: BindingErrors{
|
||||
BindingError{
|
||||
FieldNames: []string{"BranchName"},
|
||||
Classification: ErrGitRefName,
|
||||
Message: "GitRefName",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Reference name ends with dot",
|
||||
data: &TestForm{
|
||||
BranchName: "test.",
|
||||
},
|
||||
expectedErrors: BindingErrors{
|
||||
BindingError{
|
||||
FieldNames: []string{"BranchName"},
|
||||
Classification: ErrGitRefName,
|
||||
Message: "GitRefName",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Reference name starts with slash",
|
||||
data: &TestForm{
|
||||
BranchName: "/test",
|
||||
},
|
||||
expectedErrors: BindingErrors{
|
||||
BindingError{
|
||||
FieldNames: []string{"BranchName"},
|
||||
Classification: ErrGitRefName,
|
||||
Message: "GitRefName",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Reference name ends with slash",
|
||||
data: &TestForm{
|
||||
BranchName: "test/",
|
||||
},
|
||||
expectedErrors: BindingErrors{
|
||||
BindingError{
|
||||
FieldNames: []string{"BranchName"},
|
||||
Classification: ErrGitRefName,
|
||||
Message: "GitRefName",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Reference name ends with .lock",
|
||||
data: &TestForm{
|
||||
BranchName: "test.lock",
|
||||
},
|
||||
expectedErrors: BindingErrors{
|
||||
BindingError{
|
||||
FieldNames: []string{"BranchName"},
|
||||
Classification: ErrGitRefName,
|
||||
Message: "GitRefName",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Reference name contains multiple consecutive dots",
|
||||
data: &TestForm{
|
||||
BranchName: "te..st",
|
||||
},
|
||||
expectedErrors: BindingErrors{
|
||||
BindingError{
|
||||
FieldNames: []string{"BranchName"},
|
||||
Classification: ErrGitRefName,
|
||||
Message: "GitRefName",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Reference name contains multiple consecutive slashes",
|
||||
data: &TestForm{
|
||||
BranchName: "te//st",
|
||||
},
|
||||
expectedErrors: BindingErrors{
|
||||
BindingError{
|
||||
FieldNames: []string{"BranchName"},
|
||||
Classification: ErrGitRefName,
|
||||
Message: "GitRefName",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Reference name is single @",
|
||||
data: &TestForm{
|
||||
BranchName: "@",
|
||||
},
|
||||
expectedErrors: BindingErrors{
|
||||
BindingError{
|
||||
FieldNames: []string{"BranchName"},
|
||||
Classification: ErrGitRefName,
|
||||
Message: "GitRefName",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Reference name has @{",
|
||||
data: &TestForm{
|
||||
BranchName: "branch@{",
|
||||
},
|
||||
expectedErrors: BindingErrors{
|
||||
BindingError{
|
||||
FieldNames: []string{"BranchName"},
|
||||
Classification: ErrGitRefName,
|
||||
Message: "GitRefName",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Reference name has unallowed special character ~",
|
||||
data: &TestForm{
|
||||
BranchName: "~debian/1%1.6.0-2",
|
||||
},
|
||||
expectedErrors: BindingErrors{
|
||||
BindingError{
|
||||
FieldNames: []string{"BranchName"},
|
||||
Classification: ErrGitRefName,
|
||||
Message: "GitRefName",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Reference name has unallowed special character *",
|
||||
data: &TestForm{
|
||||
BranchName: "*debian/1%1.6.0-2",
|
||||
},
|
||||
expectedErrors: BindingErrors{
|
||||
BindingError{
|
||||
FieldNames: []string{"BranchName"},
|
||||
Classification: ErrGitRefName,
|
||||
Message: "GitRefName",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Reference name has unallowed special character ?",
|
||||
data: &TestForm{
|
||||
BranchName: "?debian/1%1.6.0-2",
|
||||
},
|
||||
expectedErrors: BindingErrors{
|
||||
BindingError{
|
||||
FieldNames: []string{"BranchName"},
|
||||
Classification: ErrGitRefName,
|
||||
Message: "GitRefName",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Reference name has unallowed special character ^",
|
||||
data: &TestForm{
|
||||
BranchName: "^debian/1%1.6.0-2",
|
||||
},
|
||||
expectedErrors: BindingErrors{
|
||||
BindingError{
|
||||
FieldNames: []string{"BranchName"},
|
||||
Classification: ErrGitRefName,
|
||||
Message: "GitRefName",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Reference name has unallowed special character :",
|
||||
data: &TestForm{
|
||||
BranchName: "debian:jessie",
|
||||
},
|
||||
expectedErrors: BindingErrors{
|
||||
BindingError{
|
||||
FieldNames: []string{"BranchName"},
|
||||
Classification: ErrGitRefName,
|
||||
Message: "GitRefName",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Reference name has unallowed special character (whitespace)",
|
||||
data: &TestForm{
|
||||
BranchName: "debian jessie",
|
||||
},
|
||||
expectedErrors: BindingErrors{
|
||||
BindingError{
|
||||
FieldNames: []string{"BranchName"},
|
||||
Classification: ErrGitRefName,
|
||||
Message: "GitRefName",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Reference name has unallowed special character [",
|
||||
data: &TestForm{
|
||||
BranchName: "debian[jessie",
|
||||
},
|
||||
expectedErrors: BindingErrors{
|
||||
BindingError{
|
||||
FieldNames: []string{"BranchName"},
|
||||
Classification: ErrGitRefName,
|
||||
Message: "GitRefName",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range gitRefNameValidationTestCases {
|
||||
t.Run(testCase.description, func(t *testing.T) {
|
||||
performValidationTest(t, testCase)
|
||||
})
|
||||
}
|
||||
}
|
||||
53
modules/validation/regex_pattern_test.go
Normal file
53
modules/validation/regex_pattern_test.go
Normal file
@@ -0,0 +1,53 @@
|
||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package validation
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func getRegexPatternErrorString(pattern string) string {
|
||||
if _, err := regexp.Compile(pattern); err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func Test_RegexPatternValidation(t *testing.T) {
|
||||
regexValidationTestCases := []validationTestCase{
|
||||
{
|
||||
description: "Empty regex pattern",
|
||||
data: &TestForm{
|
||||
RegexPattern: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Valid regex",
|
||||
data: &TestForm{
|
||||
RegexPattern: `(\d{1,3})+`,
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
description: "Invalid regex",
|
||||
data: &TestForm{
|
||||
RegexPattern: "[a-",
|
||||
},
|
||||
expectedErrors: BindingErrors{
|
||||
BindingError{
|
||||
FieldNames: []string{"RegexPattern"},
|
||||
Classification: ErrRegexPattern,
|
||||
Message: getRegexPatternErrorString("[a-"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range regexValidationTestCases {
|
||||
t.Run(testCase.description, func(t *testing.T) {
|
||||
performValidationTest(t, testCase)
|
||||
})
|
||||
}
|
||||
}
|
||||
101
modules/validation/validurl_test.go
Normal file
101
modules/validation/validurl_test.go
Normal file
@@ -0,0 +1,101 @@
|
||||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package validation
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_ValidURLValidation(t *testing.T) {
|
||||
urlValidationTestCases := []validationTestCase{
|
||||
{
|
||||
description: "Empty URL",
|
||||
data: &TestForm{
|
||||
URL: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "URL without port",
|
||||
data: &TestForm{
|
||||
URL: "http://test.lan/",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "URL with port",
|
||||
data: &TestForm{
|
||||
URL: "http://test.lan:3000/",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "URL with IPv6 address without port",
|
||||
data: &TestForm{
|
||||
URL: "http://[::1]/",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "URL with IPv6 address with port",
|
||||
data: &TestForm{
|
||||
URL: "http://[::1]:3000/",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Invalid URL",
|
||||
data: &TestForm{
|
||||
URL: "http//test.lan/",
|
||||
},
|
||||
expectedErrors: BindingErrors{
|
||||
BindingError{
|
||||
FieldNames: []string{"URL"},
|
||||
Classification: "UrlError",
|
||||
Message: "Url",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Invalid schema",
|
||||
data: &TestForm{
|
||||
URL: "ftp://test.lan/",
|
||||
},
|
||||
expectedErrors: BindingErrors{
|
||||
BindingError{
|
||||
FieldNames: []string{"URL"},
|
||||
Classification: "UrlError",
|
||||
Message: "Url",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Invalid port",
|
||||
data: &TestForm{
|
||||
URL: "http://test.lan:3x4/",
|
||||
},
|
||||
expectedErrors: BindingErrors{
|
||||
BindingError{
|
||||
FieldNames: []string{"URL"},
|
||||
Classification: "UrlError",
|
||||
Message: "Url",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Invalid port with IPv6 address",
|
||||
data: &TestForm{
|
||||
URL: "http://[::1]:3x4/",
|
||||
},
|
||||
expectedErrors: BindingErrors{
|
||||
BindingError{
|
||||
FieldNames: []string{"URL"},
|
||||
Classification: "UrlError",
|
||||
Message: "Url",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range urlValidationTestCases {
|
||||
t.Run(testCase.description, func(t *testing.T) {
|
||||
performValidationTest(t, testCase)
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user