...

Source file src/k8s.io/apimachinery/pkg/util/validation/validation.go

Documentation: k8s.io/apimachinery/pkg/util/validation

     1  /*
     2  Copyright 2014 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package validation
    18  
    19  import (
    20  	"fmt"
    21  	"math"
    22  	"regexp"
    23  	"strings"
    24  	"unicode"
    25  
    26  	"k8s.io/apimachinery/pkg/util/validation/field"
    27  	netutils "k8s.io/utils/net"
    28  )
    29  
    30  const qnameCharFmt string = "[A-Za-z0-9]"
    31  const qnameExtCharFmt string = "[-A-Za-z0-9_.]"
    32  const qualifiedNameFmt string = "(" + qnameCharFmt + qnameExtCharFmt + "*)?" + qnameCharFmt
    33  const qualifiedNameErrMsg string = "must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character"
    34  const qualifiedNameMaxLength int = 63
    35  
    36  var qualifiedNameRegexp = regexp.MustCompile("^" + qualifiedNameFmt + "$")
    37  
    38  // IsQualifiedName tests whether the value passed is what Kubernetes calls a
    39  // "qualified name".  This is a format used in various places throughout the
    40  // system.  If the value is not valid, a list of error strings is returned.
    41  // Otherwise an empty list (or nil) is returned.
    42  func IsQualifiedName(value string) []string {
    43  	var errs []string
    44  	parts := strings.Split(value, "/")
    45  	var name string
    46  	switch len(parts) {
    47  	case 1:
    48  		name = parts[0]
    49  	case 2:
    50  		var prefix string
    51  		prefix, name = parts[0], parts[1]
    52  		if len(prefix) == 0 {
    53  			errs = append(errs, "prefix part "+EmptyError())
    54  		} else if msgs := IsDNS1123Subdomain(prefix); len(msgs) != 0 {
    55  			errs = append(errs, prefixEach(msgs, "prefix part ")...)
    56  		}
    57  	default:
    58  		return append(errs, "a qualified name "+RegexError(qualifiedNameErrMsg, qualifiedNameFmt, "MyName", "my.name", "123-abc")+
    59  			" with an optional DNS subdomain prefix and '/' (e.g. 'example.com/MyName')")
    60  	}
    61  
    62  	if len(name) == 0 {
    63  		errs = append(errs, "name part "+EmptyError())
    64  	} else if len(name) > qualifiedNameMaxLength {
    65  		errs = append(errs, "name part "+MaxLenError(qualifiedNameMaxLength))
    66  	}
    67  	if !qualifiedNameRegexp.MatchString(name) {
    68  		errs = append(errs, "name part "+RegexError(qualifiedNameErrMsg, qualifiedNameFmt, "MyName", "my.name", "123-abc"))
    69  	}
    70  	return errs
    71  }
    72  
    73  // IsFullyQualifiedName checks if the name is fully qualified. This is similar
    74  // to IsFullyQualifiedDomainName but requires a minimum of 3 segments instead of
    75  // 2 and does not accept a trailing . as valid.
    76  // TODO: This function is deprecated and preserved until all callers migrate to
    77  // IsFullyQualifiedDomainName; please don't add new callers.
    78  func IsFullyQualifiedName(fldPath *field.Path, name string) field.ErrorList {
    79  	var allErrors field.ErrorList
    80  	if len(name) == 0 {
    81  		return append(allErrors, field.Required(fldPath, ""))
    82  	}
    83  	if errs := IsDNS1123Subdomain(name); len(errs) > 0 {
    84  		return append(allErrors, field.Invalid(fldPath, name, strings.Join(errs, ",")))
    85  	}
    86  	if len(strings.Split(name, ".")) < 3 {
    87  		return append(allErrors, field.Invalid(fldPath, name, "should be a domain with at least three segments separated by dots"))
    88  	}
    89  	return allErrors
    90  }
    91  
    92  // IsFullyQualifiedDomainName checks if the domain name is fully qualified. This
    93  // is similar to IsFullyQualifiedName but only requires a minimum of 2 segments
    94  // instead of 3 and accepts a trailing . as valid.
    95  func IsFullyQualifiedDomainName(fldPath *field.Path, name string) field.ErrorList {
    96  	var allErrors field.ErrorList
    97  	if len(name) == 0 {
    98  		return append(allErrors, field.Required(fldPath, ""))
    99  	}
   100  	if strings.HasSuffix(name, ".") {
   101  		name = name[:len(name)-1]
   102  	}
   103  	if errs := IsDNS1123Subdomain(name); len(errs) > 0 {
   104  		return append(allErrors, field.Invalid(fldPath, name, strings.Join(errs, ",")))
   105  	}
   106  	if len(strings.Split(name, ".")) < 2 {
   107  		return append(allErrors, field.Invalid(fldPath, name, "should be a domain with at least two segments separated by dots"))
   108  	}
   109  	for _, label := range strings.Split(name, ".") {
   110  		if errs := IsDNS1123Label(label); len(errs) > 0 {
   111  			return append(allErrors, field.Invalid(fldPath, label, strings.Join(errs, ",")))
   112  		}
   113  	}
   114  	return allErrors
   115  }
   116  
   117  // Allowed characters in an HTTP Path as defined by RFC 3986. A HTTP path may
   118  // contain:
   119  // * unreserved characters (alphanumeric, '-', '.', '_', '~')
   120  // * percent-encoded octets
   121  // * sub-delims ("!", "$", "&", "'", "(", ")", "*", "+", ",", ";", "=")
   122  // * a colon character (":")
   123  const httpPathFmt string = `[A-Za-z0-9/\-._~%!$&'()*+,;=:]+`
   124  
   125  var httpPathRegexp = regexp.MustCompile("^" + httpPathFmt + "$")
   126  
   127  // IsDomainPrefixedPath checks if the given string is a domain-prefixed path
   128  // (e.g. acme.io/foo). All characters before the first "/" must be a valid
   129  // subdomain as defined by RFC 1123. All characters trailing the first "/" must
   130  // be valid HTTP Path characters as defined by RFC 3986.
   131  func IsDomainPrefixedPath(fldPath *field.Path, dpPath string) field.ErrorList {
   132  	var allErrs field.ErrorList
   133  	if len(dpPath) == 0 {
   134  		return append(allErrs, field.Required(fldPath, ""))
   135  	}
   136  
   137  	segments := strings.SplitN(dpPath, "/", 2)
   138  	if len(segments) != 2 || len(segments[0]) == 0 || len(segments[1]) == 0 {
   139  		return append(allErrs, field.Invalid(fldPath, dpPath, "must be a domain-prefixed path (such as \"acme.io/foo\")"))
   140  	}
   141  
   142  	host := segments[0]
   143  	for _, err := range IsDNS1123Subdomain(host) {
   144  		allErrs = append(allErrs, field.Invalid(fldPath, host, err))
   145  	}
   146  
   147  	path := segments[1]
   148  	if !httpPathRegexp.MatchString(path) {
   149  		return append(allErrs, field.Invalid(fldPath, path, RegexError("Invalid path", httpPathFmt)))
   150  	}
   151  
   152  	return allErrs
   153  }
   154  
   155  const labelValueFmt string = "(" + qualifiedNameFmt + ")?"
   156  const labelValueErrMsg string = "a valid label must be an empty string or consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character"
   157  
   158  // LabelValueMaxLength is a label's max length
   159  const LabelValueMaxLength int = 63
   160  
   161  var labelValueRegexp = regexp.MustCompile("^" + labelValueFmt + "$")
   162  
   163  // IsValidLabelValue tests whether the value passed is a valid label value.  If
   164  // the value is not valid, a list of error strings is returned.  Otherwise an
   165  // empty list (or nil) is returned.
   166  func IsValidLabelValue(value string) []string {
   167  	var errs []string
   168  	if len(value) > LabelValueMaxLength {
   169  		errs = append(errs, MaxLenError(LabelValueMaxLength))
   170  	}
   171  	if !labelValueRegexp.MatchString(value) {
   172  		errs = append(errs, RegexError(labelValueErrMsg, labelValueFmt, "MyValue", "my_value", "12345"))
   173  	}
   174  	return errs
   175  }
   176  
   177  const dns1123LabelFmt string = "[a-z0-9]([-a-z0-9]*[a-z0-9])?"
   178  const dns1123LabelErrMsg string = "a lowercase RFC 1123 label must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character"
   179  
   180  // DNS1123LabelMaxLength is a label's max length in DNS (RFC 1123)
   181  const DNS1123LabelMaxLength int = 63
   182  
   183  var dns1123LabelRegexp = regexp.MustCompile("^" + dns1123LabelFmt + "$")
   184  
   185  // IsDNS1123Label tests for a string that conforms to the definition of a label in
   186  // DNS (RFC 1123).
   187  func IsDNS1123Label(value string) []string {
   188  	var errs []string
   189  	if len(value) > DNS1123LabelMaxLength {
   190  		errs = append(errs, MaxLenError(DNS1123LabelMaxLength))
   191  	}
   192  	if !dns1123LabelRegexp.MatchString(value) {
   193  		if dns1123SubdomainRegexp.MatchString(value) {
   194  			// It was a valid subdomain and not a valid label.  Since we
   195  			// already checked length, it must be dots.
   196  			errs = append(errs, "must not contain dots")
   197  		} else {
   198  			errs = append(errs, RegexError(dns1123LabelErrMsg, dns1123LabelFmt, "my-name", "123-abc"))
   199  		}
   200  	}
   201  	return errs
   202  }
   203  
   204  const dns1123SubdomainFmt string = dns1123LabelFmt + "(\\." + dns1123LabelFmt + ")*"
   205  const dns1123SubdomainErrorMsg string = "a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character"
   206  
   207  // DNS1123SubdomainMaxLength is a subdomain's max length in DNS (RFC 1123)
   208  const DNS1123SubdomainMaxLength int = 253
   209  
   210  var dns1123SubdomainRegexp = regexp.MustCompile("^" + dns1123SubdomainFmt + "$")
   211  
   212  // IsDNS1123Subdomain tests for a string that conforms to the definition of a
   213  // subdomain in DNS (RFC 1123).
   214  func IsDNS1123Subdomain(value string) []string {
   215  	var errs []string
   216  	if len(value) > DNS1123SubdomainMaxLength {
   217  		errs = append(errs, MaxLenError(DNS1123SubdomainMaxLength))
   218  	}
   219  	if !dns1123SubdomainRegexp.MatchString(value) {
   220  		errs = append(errs, RegexError(dns1123SubdomainErrorMsg, dns1123SubdomainFmt, "example.com"))
   221  	}
   222  	return errs
   223  }
   224  
   225  const dns1035LabelFmt string = "[a-z]([-a-z0-9]*[a-z0-9])?"
   226  const dns1035LabelErrMsg string = "a DNS-1035 label must consist of lower case alphanumeric characters or '-', start with an alphabetic character, and end with an alphanumeric character"
   227  
   228  // DNS1035LabelMaxLength is a label's max length in DNS (RFC 1035)
   229  const DNS1035LabelMaxLength int = 63
   230  
   231  var dns1035LabelRegexp = regexp.MustCompile("^" + dns1035LabelFmt + "$")
   232  
   233  // IsDNS1035Label tests for a string that conforms to the definition of a label in
   234  // DNS (RFC 1035).
   235  func IsDNS1035Label(value string) []string {
   236  	var errs []string
   237  	if len(value) > DNS1035LabelMaxLength {
   238  		errs = append(errs, MaxLenError(DNS1035LabelMaxLength))
   239  	}
   240  	if !dns1035LabelRegexp.MatchString(value) {
   241  		errs = append(errs, RegexError(dns1035LabelErrMsg, dns1035LabelFmt, "my-name", "abc-123"))
   242  	}
   243  	return errs
   244  }
   245  
   246  // wildcard definition - RFC 1034 section 4.3.3.
   247  // examples:
   248  // - valid: *.bar.com, *.foo.bar.com
   249  // - invalid: *.*.bar.com, *.foo.*.com, *bar.com, f*.bar.com, *
   250  const wildcardDNS1123SubdomainFmt = "\\*\\." + dns1123SubdomainFmt
   251  const wildcardDNS1123SubdomainErrMsg = "a wildcard DNS-1123 subdomain must start with '*.', followed by a valid DNS subdomain, which must consist of lower case alphanumeric characters, '-' or '.' and end with an alphanumeric character"
   252  
   253  // IsWildcardDNS1123Subdomain tests for a string that conforms to the definition of a
   254  // wildcard subdomain in DNS (RFC 1034 section 4.3.3).
   255  func IsWildcardDNS1123Subdomain(value string) []string {
   256  	wildcardDNS1123SubdomainRegexp := regexp.MustCompile("^" + wildcardDNS1123SubdomainFmt + "$")
   257  
   258  	var errs []string
   259  	if len(value) > DNS1123SubdomainMaxLength {
   260  		errs = append(errs, MaxLenError(DNS1123SubdomainMaxLength))
   261  	}
   262  	if !wildcardDNS1123SubdomainRegexp.MatchString(value) {
   263  		errs = append(errs, RegexError(wildcardDNS1123SubdomainErrMsg, wildcardDNS1123SubdomainFmt, "*.example.com"))
   264  	}
   265  	return errs
   266  }
   267  
   268  const cIdentifierFmt string = "[A-Za-z_][A-Za-z0-9_]*"
   269  const identifierErrMsg string = "a valid C identifier must start with alphabetic character or '_', followed by a string of alphanumeric characters or '_'"
   270  
   271  var cIdentifierRegexp = regexp.MustCompile("^" + cIdentifierFmt + "$")
   272  
   273  // IsCIdentifier tests for a string that conforms the definition of an identifier
   274  // in C. This checks the format, but not the length.
   275  func IsCIdentifier(value string) []string {
   276  	if !cIdentifierRegexp.MatchString(value) {
   277  		return []string{RegexError(identifierErrMsg, cIdentifierFmt, "my_name", "MY_NAME", "MyName")}
   278  	}
   279  	return nil
   280  }
   281  
   282  // IsValidPortNum tests that the argument is a valid, non-zero port number.
   283  func IsValidPortNum(port int) []string {
   284  	if 1 <= port && port <= 65535 {
   285  		return nil
   286  	}
   287  	return []string{InclusiveRangeError(1, 65535)}
   288  }
   289  
   290  // IsInRange tests that the argument is in an inclusive range.
   291  func IsInRange(value int, min int, max int) []string {
   292  	if value >= min && value <= max {
   293  		return nil
   294  	}
   295  	return []string{InclusiveRangeError(min, max)}
   296  }
   297  
   298  // Now in libcontainer UID/GID limits is 0 ~ 1<<31 - 1
   299  // TODO: once we have a type for UID/GID we should make these that type.
   300  const (
   301  	minUserID  = 0
   302  	maxUserID  = math.MaxInt32
   303  	minGroupID = 0
   304  	maxGroupID = math.MaxInt32
   305  )
   306  
   307  // IsValidGroupID tests that the argument is a valid Unix GID.
   308  func IsValidGroupID(gid int64) []string {
   309  	if minGroupID <= gid && gid <= maxGroupID {
   310  		return nil
   311  	}
   312  	return []string{InclusiveRangeError(minGroupID, maxGroupID)}
   313  }
   314  
   315  // IsValidUserID tests that the argument is a valid Unix UID.
   316  func IsValidUserID(uid int64) []string {
   317  	if minUserID <= uid && uid <= maxUserID {
   318  		return nil
   319  	}
   320  	return []string{InclusiveRangeError(minUserID, maxUserID)}
   321  }
   322  
   323  var portNameCharsetRegex = regexp.MustCompile("^[-a-z0-9]+$")
   324  var portNameOneLetterRegexp = regexp.MustCompile("[a-z]")
   325  
   326  // IsValidPortName check that the argument is valid syntax. It must be
   327  // non-empty and no more than 15 characters long. It may contain only [-a-z0-9]
   328  // and must contain at least one letter [a-z]. It must not start or end with a
   329  // hyphen, nor contain adjacent hyphens.
   330  //
   331  // Note: We only allow lower-case characters, even though RFC 6335 is case
   332  // insensitive.
   333  func IsValidPortName(port string) []string {
   334  	var errs []string
   335  	if len(port) > 15 {
   336  		errs = append(errs, MaxLenError(15))
   337  	}
   338  	if !portNameCharsetRegex.MatchString(port) {
   339  		errs = append(errs, "must contain only alpha-numeric characters (a-z, 0-9), and hyphens (-)")
   340  	}
   341  	if !portNameOneLetterRegexp.MatchString(port) {
   342  		errs = append(errs, "must contain at least one letter (a-z)")
   343  	}
   344  	if strings.Contains(port, "--") {
   345  		errs = append(errs, "must not contain consecutive hyphens")
   346  	}
   347  	if len(port) > 0 && (port[0] == '-' || port[len(port)-1] == '-') {
   348  		errs = append(errs, "must not begin or end with a hyphen")
   349  	}
   350  	return errs
   351  }
   352  
   353  // IsValidIP tests that the argument is a valid IP address.
   354  func IsValidIP(fldPath *field.Path, value string) field.ErrorList {
   355  	var allErrors field.ErrorList
   356  	if netutils.ParseIPSloppy(value) == nil {
   357  		allErrors = append(allErrors, field.Invalid(fldPath, value, "must be a valid IP address, (e.g. 10.9.8.7 or 2001:db8::ffff)"))
   358  	}
   359  	return allErrors
   360  }
   361  
   362  // IsValidIPv4Address tests that the argument is a valid IPv4 address.
   363  func IsValidIPv4Address(fldPath *field.Path, value string) field.ErrorList {
   364  	var allErrors field.ErrorList
   365  	ip := netutils.ParseIPSloppy(value)
   366  	if ip == nil || ip.To4() == nil {
   367  		allErrors = append(allErrors, field.Invalid(fldPath, value, "must be a valid IPv4 address"))
   368  	}
   369  	return allErrors
   370  }
   371  
   372  // IsValidIPv6Address tests that the argument is a valid IPv6 address.
   373  func IsValidIPv6Address(fldPath *field.Path, value string) field.ErrorList {
   374  	var allErrors field.ErrorList
   375  	ip := netutils.ParseIPSloppy(value)
   376  	if ip == nil || ip.To4() != nil {
   377  		allErrors = append(allErrors, field.Invalid(fldPath, value, "must be a valid IPv6 address"))
   378  	}
   379  	return allErrors
   380  }
   381  
   382  // IsValidCIDR tests that the argument is a valid CIDR value.
   383  func IsValidCIDR(fldPath *field.Path, value string) field.ErrorList {
   384  	var allErrors field.ErrorList
   385  	_, _, err := netutils.ParseCIDRSloppy(value)
   386  	if err != nil {
   387  		allErrors = append(allErrors, field.Invalid(fldPath, value, "must be a valid CIDR value, (e.g. 10.9.8.0/24 or 2001:db8::/64)"))
   388  	}
   389  	return allErrors
   390  }
   391  
   392  const percentFmt string = "[0-9]+%"
   393  const percentErrMsg string = "a valid percent string must be a numeric string followed by an ending '%'"
   394  
   395  var percentRegexp = regexp.MustCompile("^" + percentFmt + "$")
   396  
   397  // IsValidPercent checks that string is in the form of a percentage
   398  func IsValidPercent(percent string) []string {
   399  	if !percentRegexp.MatchString(percent) {
   400  		return []string{RegexError(percentErrMsg, percentFmt, "1%", "93%")}
   401  	}
   402  	return nil
   403  }
   404  
   405  const httpHeaderNameFmt string = "[-A-Za-z0-9]+"
   406  const httpHeaderNameErrMsg string = "a valid HTTP header must consist of alphanumeric characters or '-'"
   407  
   408  var httpHeaderNameRegexp = regexp.MustCompile("^" + httpHeaderNameFmt + "$")
   409  
   410  // IsHTTPHeaderName checks that a string conforms to the Go HTTP library's
   411  // definition of a valid header field name (a stricter subset than RFC7230).
   412  func IsHTTPHeaderName(value string) []string {
   413  	if !httpHeaderNameRegexp.MatchString(value) {
   414  		return []string{RegexError(httpHeaderNameErrMsg, httpHeaderNameFmt, "X-Header-Name")}
   415  	}
   416  	return nil
   417  }
   418  
   419  const envVarNameFmt = "[-._a-zA-Z][-._a-zA-Z0-9]*"
   420  const envVarNameFmtErrMsg string = "a valid environment variable name must consist of alphabetic characters, digits, '_', '-', or '.', and must not start with a digit"
   421  
   422  // TODO(hirazawaui): Rename this when the RelaxedEnvironmentVariableValidation gate is removed.
   423  const relaxedEnvVarNameFmtErrMsg string = "a valid environment variable name must consist only of printable ASCII characters other than '='"
   424  
   425  var envVarNameRegexp = regexp.MustCompile("^" + envVarNameFmt + "$")
   426  
   427  // IsEnvVarName tests if a string is a valid environment variable name.
   428  func IsEnvVarName(value string) []string {
   429  	var errs []string
   430  	if !envVarNameRegexp.MatchString(value) {
   431  		errs = append(errs, RegexError(envVarNameFmtErrMsg, envVarNameFmt, "my.env-name", "MY_ENV.NAME", "MyEnvName1"))
   432  	}
   433  
   434  	errs = append(errs, hasChDirPrefix(value)...)
   435  	return errs
   436  }
   437  
   438  // IsRelaxedEnvVarName tests if a string is a valid environment variable name.
   439  func IsRelaxedEnvVarName(value string) []string {
   440  	var errs []string
   441  
   442  	if len(value) == 0 {
   443  		errs = append(errs, "environment variable name "+EmptyError())
   444  	}
   445  
   446  	for _, r := range value {
   447  		if r > unicode.MaxASCII || !unicode.IsPrint(r) || r == '=' {
   448  			errs = append(errs, relaxedEnvVarNameFmtErrMsg)
   449  			break
   450  		}
   451  	}
   452  
   453  	return errs
   454  }
   455  
   456  const configMapKeyFmt = `[-._a-zA-Z0-9]+`
   457  const configMapKeyErrMsg string = "a valid config key must consist of alphanumeric characters, '-', '_' or '.'"
   458  
   459  var configMapKeyRegexp = regexp.MustCompile("^" + configMapKeyFmt + "$")
   460  
   461  // IsConfigMapKey tests for a string that is a valid key for a ConfigMap or Secret
   462  func IsConfigMapKey(value string) []string {
   463  	var errs []string
   464  	if len(value) > DNS1123SubdomainMaxLength {
   465  		errs = append(errs, MaxLenError(DNS1123SubdomainMaxLength))
   466  	}
   467  	if !configMapKeyRegexp.MatchString(value) {
   468  		errs = append(errs, RegexError(configMapKeyErrMsg, configMapKeyFmt, "key.name", "KEY_NAME", "key-name"))
   469  	}
   470  	errs = append(errs, hasChDirPrefix(value)...)
   471  	return errs
   472  }
   473  
   474  // MaxLenError returns a string explanation of a "string too long" validation
   475  // failure.
   476  func MaxLenError(length int) string {
   477  	return fmt.Sprintf("must be no more than %d characters", length)
   478  }
   479  
   480  // RegexError returns a string explanation of a regex validation failure.
   481  func RegexError(msg string, fmt string, examples ...string) string {
   482  	if len(examples) == 0 {
   483  		return msg + " (regex used for validation is '" + fmt + "')"
   484  	}
   485  	msg += " (e.g. "
   486  	for i := range examples {
   487  		if i > 0 {
   488  			msg += " or "
   489  		}
   490  		msg += "'" + examples[i] + "', "
   491  	}
   492  	msg += "regex used for validation is '" + fmt + "')"
   493  	return msg
   494  }
   495  
   496  // EmptyError returns a string explanation of a "must not be empty" validation
   497  // failure.
   498  func EmptyError() string {
   499  	return "must be non-empty"
   500  }
   501  
   502  func prefixEach(msgs []string, prefix string) []string {
   503  	for i := range msgs {
   504  		msgs[i] = prefix + msgs[i]
   505  	}
   506  	return msgs
   507  }
   508  
   509  // InclusiveRangeError returns a string explanation of a numeric "must be
   510  // between" validation failure.
   511  func InclusiveRangeError(lo, hi int) string {
   512  	return fmt.Sprintf(`must be between %d and %d, inclusive`, lo, hi)
   513  }
   514  
   515  func hasChDirPrefix(value string) []string {
   516  	var errs []string
   517  	switch {
   518  	case value == ".":
   519  		errs = append(errs, `must not be '.'`)
   520  	case value == "..":
   521  		errs = append(errs, `must not be '..'`)
   522  	case strings.HasPrefix(value, ".."):
   523  		errs = append(errs, `must not start with '..'`)
   524  	}
   525  	return errs
   526  }
   527  

View as plain text