Files
Gitea/modules/validation/binding.go
T
Abhay Pratap Singh 7ebb2caa9e fix(user): unify email validation for registration and settings (#39304)
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2026-09-16 17:34:41 +00:00

164 lines
4.8 KiB
Go

// 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
}
func AddValidationError(errs BindingErrors, fieldName, errorMsg string) BindingErrors {
errs.Add([]string{fieldName}, ErrCustomMessage, errorMsg)
return errs
}
// AddBindingRules adds additional binding rules
func AddBindingRules(b *binding.Binder) {
b.ClearRules("Email")
b.AddRuleNonZero("Email", func(_ context.Context, f *binding.ValidationField) *binding.Error {
if !IsEmailAddressValid(f.ValueMustString()) {
return newFieldError(f.StructField, binding.ERR_EMAIL, "invalid email")
}
return nil
})
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
)