mirror of
https://github.com/hanzoai/forge.git
synced 2026-10-30 15:00:39 +00:00
Source files keep their SPDX-License-Identifier and Hanzo copyright line; the Gitea, Gogs and LFS Test Server copyrights they used to repeat now live once in NOTICE, alongside LICENSE. The header lint accepts any copyright line above the SPDX line, so it no longer demands an upstream one. Co-authored-by: Hanzo Dev <[email protected]>
246 lines
5.9 KiB
Go
246 lines
5.9 KiB
Go
// Copyright 2026 Hanzo AI, Inc. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package validation
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"github.com/hanzoai/git/modules/auth"
|
|
"github.com/hanzoai/git/modules/git"
|
|
"github.com/hanzoai/git/modules/glob"
|
|
"github.com/hanzoai/git/modules/json"
|
|
|
|
"github.com/hanzoai/git/modules/binding"
|
|
)
|
|
|
|
const (
|
|
// ErrGitRefName is git reference name error
|
|
ErrGitRefName = "GitRefNameError"
|
|
// ErrGlobPattern is returned when glob pattern is invalid
|
|
ErrGlobPattern = "GlobPattern"
|
|
// ErrRegexPattern is returned when a regex pattern is invalid
|
|
ErrRegexPattern = "RegexPattern"
|
|
// ErrUsername is username error
|
|
ErrUsername = "UsernameError"
|
|
// ErrInvalidGroupTeamMap is returned when a group team mapping is invalid
|
|
ErrInvalidGroupTeamMap = "InvalidGroupTeamMap"
|
|
// ErrInvalidBadgeSlug is returned when a badge slug is invalid
|
|
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)
|
|
}
|
|
|
|
// AddBindingRules adds additional binding rules
|
|
func AddBindingRules() {
|
|
binding.JSONProvider = jsonProvider{}
|
|
addGitRefNameBindingRule()
|
|
addValidURLBindingRule()
|
|
addValidSiteURLBindingRule()
|
|
addGlobPatternRule()
|
|
addRegexPatternRule()
|
|
addGlobOrRegexPatternRule()
|
|
addUsernamePatternRule()
|
|
addValidGroupTeamMapRule()
|
|
addSlugPatternRule()
|
|
}
|
|
|
|
func addGitRefNameBindingRule() {
|
|
// Git refname validation rule
|
|
binding.AddRule(&binding.Rule{
|
|
IsMatch: func(rule string) bool {
|
|
return rule == "GitRefName"
|
|
},
|
|
IsValid: func(errs binding.Errors, name string, val any) (bool, binding.Errors) {
|
|
str := fmt.Sprintf("%v", val)
|
|
|
|
if !git.IsValidRefPattern(str) {
|
|
errs.Add([]string{name}, ErrGitRefName, "GitRefName")
|
|
return false, errs
|
|
}
|
|
return true, errs
|
|
},
|
|
})
|
|
}
|
|
|
|
func addValidURLBindingRule() {
|
|
// URL validation rule
|
|
binding.AddRule(&binding.Rule{
|
|
IsMatch: func(rule string) bool {
|
|
return rule == "ValidUrl"
|
|
},
|
|
IsValid: func(errs binding.Errors, name string, val any) (bool, binding.Errors) {
|
|
str := fmt.Sprintf("%v", val)
|
|
if len(str) != 0 && !IsValidURL(str) {
|
|
errs.Add([]string{name}, binding.ERR_URL, "Url")
|
|
return false, errs
|
|
}
|
|
|
|
return true, errs
|
|
},
|
|
})
|
|
}
|
|
|
|
func addValidSiteURLBindingRule() {
|
|
// URL validation rule
|
|
binding.AddRule(&binding.Rule{
|
|
IsMatch: func(rule string) bool {
|
|
return rule == "ValidSiteUrl"
|
|
},
|
|
IsValid: func(errs binding.Errors, name string, val any) (bool, binding.Errors) {
|
|
str := fmt.Sprintf("%v", val)
|
|
if len(str) != 0 && !IsValidSiteURL(str) {
|
|
errs.Add([]string{name}, binding.ERR_URL, "Url")
|
|
return false, errs
|
|
}
|
|
|
|
return true, errs
|
|
},
|
|
})
|
|
}
|
|
|
|
func addSlugPatternRule() {
|
|
binding.AddRule(&binding.Rule{
|
|
IsMatch: func(rule string) bool {
|
|
return rule == "BadgeSlug"
|
|
},
|
|
IsValid: func(errs binding.Errors, name string, val any) (bool, binding.Errors) {
|
|
str := fmt.Sprintf("%v", val)
|
|
if !IsValidBadgeSlug(str) {
|
|
errs.Add([]string{name}, ErrInvalidBadgeSlug, "invalid badge slug")
|
|
return false, errs
|
|
}
|
|
return true, errs
|
|
},
|
|
})
|
|
}
|
|
|
|
func addGlobPatternRule() {
|
|
binding.AddRule(&binding.Rule{
|
|
IsMatch: func(rule string) bool {
|
|
return rule == "GlobPattern"
|
|
},
|
|
IsValid: globPatternValidator,
|
|
})
|
|
}
|
|
|
|
func globPatternValidator(errs binding.Errors, name string, val any) (bool, binding.Errors) {
|
|
str := fmt.Sprintf("%v", val)
|
|
|
|
if len(str) != 0 {
|
|
if _, err := glob.Compile(str); err != nil {
|
|
errs.Add([]string{name}, ErrGlobPattern, err.Error())
|
|
return false, errs
|
|
}
|
|
}
|
|
|
|
return true, errs
|
|
}
|
|
|
|
func addRegexPatternRule() {
|
|
binding.AddRule(&binding.Rule{
|
|
IsMatch: func(rule string) bool {
|
|
return rule == "RegexPattern"
|
|
},
|
|
IsValid: regexPatternValidator,
|
|
})
|
|
}
|
|
|
|
func regexPatternValidator(errs binding.Errors, name string, val any) (bool, binding.Errors) {
|
|
str := fmt.Sprintf("%v", val)
|
|
|
|
if _, err := regexp.Compile(str); err != nil {
|
|
errs.Add([]string{name}, ErrRegexPattern, err.Error())
|
|
return false, errs
|
|
}
|
|
|
|
return true, errs
|
|
}
|
|
|
|
func addGlobOrRegexPatternRule() {
|
|
binding.AddRule(&binding.Rule{
|
|
IsMatch: func(rule string) bool {
|
|
return rule == "GlobOrRegexPattern"
|
|
},
|
|
IsValid: func(errs binding.Errors, name string, val any) (bool, binding.Errors) {
|
|
str := strings.TrimSpace(fmt.Sprintf("%v", val))
|
|
|
|
if len(str) >= 2 && strings.HasPrefix(str, "/") && strings.HasSuffix(str, "/") {
|
|
return regexPatternValidator(errs, name, str[1:len(str)-1])
|
|
}
|
|
return globPatternValidator(errs, name, val)
|
|
},
|
|
})
|
|
}
|
|
|
|
func addUsernamePatternRule() {
|
|
binding.AddRule(&binding.Rule{
|
|
IsMatch: func(rule string) bool {
|
|
return rule == "Username"
|
|
},
|
|
IsValid: func(errs binding.Errors, name string, val any) (bool, binding.Errors) {
|
|
str := fmt.Sprintf("%v", val)
|
|
if !IsValidUsername(str) {
|
|
errs.Add([]string{name}, ErrUsername, "invalid username")
|
|
return false, errs
|
|
}
|
|
return true, errs
|
|
},
|
|
})
|
|
}
|
|
|
|
func addValidGroupTeamMapRule() {
|
|
binding.AddRule(&binding.Rule{
|
|
IsMatch: func(rule string) bool {
|
|
return rule == "ValidGroupTeamMap"
|
|
},
|
|
IsValid: func(errs binding.Errors, name string, val any) (bool, binding.Errors) {
|
|
_, err := auth.UnmarshalGroupTeamMapping(fmt.Sprintf("%v", val))
|
|
if err != nil {
|
|
errs.Add([]string{name}, ErrInvalidGroupTeamMap, err.Error())
|
|
return false, errs
|
|
}
|
|
|
|
return true, errs
|
|
},
|
|
})
|
|
}
|
|
|
|
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
|
|
}
|