github.com/gitbundle/modules@v0.0.0-20231025071548-85b91c5c3b01/validation/binding.go (about) 1 // Copyright 2023 The GitBundle Inc. All rights reserved. 2 // Copyright 2017 The Gitea Authors. All rights reserved. 3 // Use of this source code is governed by a MIT-style 4 // license that can be found in the LICENSE file. 5 6 package validation 7 8 import ( 9 "fmt" 10 "regexp" 11 "strings" 12 13 "gitea.com/go-chi/binding" 14 "github.com/gobwas/glob" 15 ) 16 17 const ( 18 // ErrGitRefName is git reference name error 19 ErrGitRefName = "GitRefNameError" 20 21 // ErrGlobPattern is returned when glob pattern is invalid 22 ErrGlobPattern = "GlobPattern" 23 24 // ErrRegexPattern is returned when a regex pattern is invalid 25 ErrRegexPattern = "RegexPattern" 26 ) 27 28 // GitRefNamePatternInvalid is regular expression with unallowed characters in git reference name 29 // They cannot have ASCII control characters (i.e. bytes whose values are lower than \040, or \177 DEL), space, tilde ~, caret ^, or colon : anywhere. 30 // They cannot have question-mark ?, asterisk *, or open bracket [ anywhere 31 var GitRefNamePatternInvalid = regexp.MustCompile(`[\000-\037\177 \\~^:?*[]+`) 32 33 // CheckGitRefAdditionalRulesValid check name is valid on additional rules 34 func CheckGitRefAdditionalRulesValid(name string) bool { 35 // Additional rules as described at https://www.kernel.org/pub/software/scm/git/docs/git-check-ref-format.html 36 if strings.HasPrefix(name, "/") || strings.HasSuffix(name, "/") || 37 strings.HasSuffix(name, ".") || strings.Contains(name, "..") || 38 strings.Contains(name, "//") || strings.Contains(name, "@{") || 39 name == "@" { 40 return false 41 } 42 parts := strings.Split(name, "/") 43 for _, part := range parts { 44 if strings.HasSuffix(part, ".lock") || strings.HasPrefix(part, ".") { 45 return false 46 } 47 } 48 49 return true 50 } 51 52 // AddBindingRules adds additional binding rules 53 func AddBindingRules() { 54 addGitRefNameBindingRule() 55 addValidURLBindingRule() 56 addValidSiteURLBindingRule() 57 addGlobPatternRule() 58 addRegexPatternRule() 59 addGlobOrRegexPatternRule() 60 } 61 62 func addGitRefNameBindingRule() { 63 // Git refname validation rule 64 binding.AddRule(&binding.Rule{ 65 IsMatch: func(rule string) bool { 66 return strings.HasPrefix(rule, "GitRefName") 67 }, 68 IsValid: func(errs binding.Errors, name string, val interface{}) (bool, binding.Errors) { 69 str := fmt.Sprintf("%v", val) 70 71 if GitRefNamePatternInvalid.MatchString(str) { 72 errs.Add([]string{name}, ErrGitRefName, "GitRefName") 73 return false, errs 74 } 75 76 if !CheckGitRefAdditionalRulesValid(str) { 77 errs.Add([]string{name}, ErrGitRefName, "GitRefName") 78 return false, errs 79 } 80 81 return true, errs 82 }, 83 }) 84 } 85 86 func addValidURLBindingRule() { 87 // URL validation rule 88 binding.AddRule(&binding.Rule{ 89 IsMatch: func(rule string) bool { 90 return strings.HasPrefix(rule, "ValidUrl") 91 }, 92 IsValid: func(errs binding.Errors, name string, val interface{}) (bool, binding.Errors) { 93 str := fmt.Sprintf("%v", val) 94 if len(str) != 0 && !IsValidURL(str) { 95 errs.Add([]string{name}, binding.ERR_URL, "Url") 96 return false, errs 97 } 98 99 return true, errs 100 }, 101 }) 102 } 103 104 func addValidSiteURLBindingRule() { 105 // URL validation rule 106 binding.AddRule(&binding.Rule{ 107 IsMatch: func(rule string) bool { 108 return strings.HasPrefix(rule, "ValidSiteUrl") 109 }, 110 IsValid: func(errs binding.Errors, name string, val interface{}) (bool, binding.Errors) { 111 str := fmt.Sprintf("%v", val) 112 if len(str) != 0 && !IsValidSiteURL(str) { 113 errs.Add([]string{name}, binding.ERR_URL, "Url") 114 return false, errs 115 } 116 117 return true, errs 118 }, 119 }) 120 } 121 122 func addGlobPatternRule() { 123 binding.AddRule(&binding.Rule{ 124 IsMatch: func(rule string) bool { 125 return rule == "GlobPattern" 126 }, 127 IsValid: globPatternValidator, 128 }) 129 } 130 131 func globPatternValidator(errs binding.Errors, name string, val interface{}) (bool, binding.Errors) { 132 str := fmt.Sprintf("%v", val) 133 134 if len(str) != 0 { 135 if _, err := glob.Compile(str); err != nil { 136 errs.Add([]string{name}, ErrGlobPattern, err.Error()) 137 return false, errs 138 } 139 } 140 141 return true, errs 142 } 143 144 func addRegexPatternRule() { 145 binding.AddRule(&binding.Rule{ 146 IsMatch: func(rule string) bool { 147 return rule == "RegexPattern" 148 }, 149 IsValid: regexPatternValidator, 150 }) 151 } 152 153 func regexPatternValidator(errs binding.Errors, name string, val interface{}) (bool, binding.Errors) { 154 str := fmt.Sprintf("%v", val) 155 156 if _, err := regexp.Compile(str); err != nil { 157 errs.Add([]string{name}, ErrRegexPattern, err.Error()) 158 return false, errs 159 } 160 161 return true, errs 162 } 163 164 func addGlobOrRegexPatternRule() { 165 binding.AddRule(&binding.Rule{ 166 IsMatch: func(rule string) bool { 167 return rule == "GlobOrRegexPattern" 168 }, 169 IsValid: func(errs binding.Errors, name string, val interface{}) (bool, binding.Errors) { 170 str := strings.TrimSpace(fmt.Sprintf("%v", val)) 171 172 if len(str) >= 2 && strings.HasPrefix(str, "/") && strings.HasSuffix(str, "/") { 173 return regexPatternValidator(errs, name, str[1:len(str)-1]) 174 } 175 return globPatternValidator(errs, name, val) 176 }, 177 }) 178 } 179 180 func portOnly(hostport string) string { 181 colon := strings.IndexByte(hostport, ':') 182 if colon == -1 { 183 return "" 184 } 185 if i := strings.Index(hostport, "]:"); i != -1 { 186 return hostport[i+len("]:"):] 187 } 188 if strings.Contains(hostport, "]") { 189 return "" 190 } 191 return hostport[colon+len(":"):] 192 } 193 194 func validPort(p string) bool { 195 for _, r := range []byte(p) { 196 if r < '0' || r > '9' { 197 return false 198 } 199 } 200 return true 201 }