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  }