|
|
@@ -4,6 +4,7 @@ import (
|
|
|
"context"
|
|
|
"errors"
|
|
|
"fmt"
|
|
|
+ "net"
|
|
|
"net/http"
|
|
|
"net/mail"
|
|
|
"strings"
|
|
|
@@ -12,12 +13,9 @@ import (
|
|
|
|
|
|
/* validation errors */
|
|
|
var (
|
|
|
- EInvalidInteger = errors.New("not a valid integer value")
|
|
|
- EInvalidFloat = errors.New("not a valid float value")
|
|
|
- ERequired = errors.New("this field is required")
|
|
|
- EInvalidEmail = errors.New("invalid email address")
|
|
|
- EInvalidDomain = errors.New("invalid domain name")
|
|
|
- EInvalidDomainTLD = errors.New("invalid domain TLD")
|
|
|
+ EInvalidInteger = errors.New("not a valid integer value")
|
|
|
+ EInvalidFloat = errors.New("not a valid float value")
|
|
|
+ ERequired = errors.New("this field is required")
|
|
|
)
|
|
|
|
|
|
/* A globaly defined set with valid domain TLDs */
|
|
|
@@ -65,33 +63,27 @@ func ValidLettersGeneric(Letters string, Error error) ValidatorFunc {
|
|
|
return Callback
|
|
|
}
|
|
|
|
|
|
-/* ValidLength makes sure that a string length is between specified min and max values. */
|
|
|
-func ValidLength(min, max int) ValidatorFunc {
|
|
|
- var ELength = errors.New(
|
|
|
- fmt.Sprintf("must be a string between %d and %d characters in length", min, max))
|
|
|
+/* ValidLength makes sure that a string length is between specified vMin and vMax values. */
|
|
|
+func ValidLength(vMin, vMax int, err error) ValidatorFunc {
|
|
|
+ ELength := fmt.Errorf("must be a string between %d and %d characters in length", vMin, vMax)
|
|
|
return func(field *FormField, ctx context.Context) error {
|
|
|
- if len(field.GetString()) != 0 && (len(field.GetString()) < min || len(field.GetString()) > max) {
|
|
|
- return ELength
|
|
|
+ if len(field.GetString()) != 0 && (len(field.GetString()) < vMin || len(field.GetString()) > vMax) {
|
|
|
+ return fallbackError(err, ELength)
|
|
|
}
|
|
|
return nil
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/* ValidFieldIn verifies if item is within the list of items */
|
|
|
-func ValidFieldIn(list []string) ValidatorFunc {
|
|
|
- var EInvalidValue = errors.New(
|
|
|
- fmt.Sprintf(
|
|
|
- "field value must be one of: %s",
|
|
|
- strings.Join(list, ","),
|
|
|
- ),
|
|
|
- )
|
|
|
+func ValidFieldIn(list []string, err error) ValidatorFunc {
|
|
|
+ EInvalidValue := fmt.Errorf("field value must be one of: %s", strings.Join(list, ","))
|
|
|
return func(field *FormField, ctx context.Context) error {
|
|
|
for _, item := range list {
|
|
|
if item == field.GetString() {
|
|
|
return nil
|
|
|
}
|
|
|
}
|
|
|
- return EInvalidValue
|
|
|
+ return fallbackError(err, EInvalidValue)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -105,16 +97,11 @@ func ValidInt(field *FormField, ctx context.Context) error {
|
|
|
}
|
|
|
|
|
|
/* ValidBetween makes sure that field is integer value within the specified range. */
|
|
|
-func ValidBetween(min, max int) ValidatorFunc {
|
|
|
- var EInvalidInterval = errors.New(
|
|
|
- fmt.Sprintf("must be integer between %d and %d", min, max))
|
|
|
+func ValidBetween(minV, maxV int, err error) ValidatorFunc {
|
|
|
+ var EInvalidInterval = fmt.Errorf("must be integer between %d and %d", minV, maxV)
|
|
|
return func(field *FormField, ctx context.Context) error {
|
|
|
- value, err := field.GetInt()
|
|
|
- if err != nil {
|
|
|
- return EInvalidInteger
|
|
|
- }
|
|
|
- if value < min || value > max {
|
|
|
- return EInvalidInterval
|
|
|
+ if value, err := field.GetInt(); err != nil || (value < minV || value > maxV) {
|
|
|
+ return fallbackError(err, EInvalidInterval)
|
|
|
}
|
|
|
return nil
|
|
|
}
|
|
|
@@ -146,79 +133,186 @@ func ValidBetweenFloat(min, max float64) ValidatorFunc {
|
|
|
}
|
|
|
|
|
|
/* ValidFieldEqualTo is a validator that checks if two fields have the same value. */
|
|
|
-func ValidFieldEqualTo(Other *FormField, err error) ValidatorFunc {
|
|
|
+func ValidFieldEqualTo(other *FormField, err error) ValidatorFunc {
|
|
|
+ defaultErr := errors.New("The text field must match other field's text exactly")
|
|
|
return func(field *FormField, ctx context.Context) error {
|
|
|
- if field.GetString() != Other.GetString() {
|
|
|
- return err
|
|
|
+ if field.GetString() != other.GetString() {
|
|
|
+ return fallbackError(err, defaultErr)
|
|
|
}
|
|
|
return nil
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-/* validDomainNameStr performs checks if the domain name stored in *FormField is valid */
|
|
|
-func validDomainNameStr(domain string) error {
|
|
|
- domain = strings.ToLower(strings.TrimSpace(domain))
|
|
|
- domainLen := len(domain)
|
|
|
- /* common domain name checks */
|
|
|
- if domainLen == 0 || domainLen > 253 || strings.ContainsAny(domain, " \t\r\n") {
|
|
|
- return EInvalidDomain
|
|
|
- }
|
|
|
- if domain[0] == '.' || domain[domainLen-1] == '.' {
|
|
|
- return EInvalidDomain
|
|
|
+/* ValidExct checks if text field has exact value */
|
|
|
+func ValidExact(text string, err error) ValidatorFunc {
|
|
|
+ defaultErr := fmt.Errorf("The the text field must be exactly '%s'", text)
|
|
|
+ return func(field *FormField, ctx context.Context) error {
|
|
|
+ if field.GetString() != text {
|
|
|
+ return fallbackError(err, defaultErr)
|
|
|
+ }
|
|
|
+ return nil
|
|
|
}
|
|
|
- /* make sure domain name only contains allowed letters */
|
|
|
- for _, r := range domain {
|
|
|
- if (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') || r == '.' || r == '-' {
|
|
|
- continue
|
|
|
+}
|
|
|
+
|
|
|
+/* validHostnameStr performs checks if the hostname name stored in *FormField is valid */
|
|
|
+func validHostnameStr(minLevel int, err error) func(string) error {
|
|
|
+ EInvalidHostname := errors.New("invalid hostname")
|
|
|
+ return func(hostname string) error {
|
|
|
+ hostname = strings.ToLower(strings.TrimSpace(hostname))
|
|
|
+ hostnameLen := len(hostname)
|
|
|
+ /* common hostname name checks */
|
|
|
+ if hostnameLen == 0 || hostnameLen > 253 || strings.ContainsAny(hostname, " \t\r\n") {
|
|
|
+ return fallbackError(err, EInvalidHostname)
|
|
|
+ }
|
|
|
+ if hostname[0] == '.' {
|
|
|
+ return fallbackError(err, EInvalidHostname)
|
|
|
+ }
|
|
|
+ /* make sure hostname name only contains allowed letters */
|
|
|
+ for _, r := range hostname {
|
|
|
+ if (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') || r == '.' || r == '-' {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ return fallbackError(err, EInvalidHostname)
|
|
|
+ }
|
|
|
+ /* split to levels (tld, hostname[, subhostname...] */
|
|
|
+ levels := strings.Split(hostname, ".")
|
|
|
+ if len(levels) < minLevel {
|
|
|
+ return fallbackError(err, EInvalidHostname)
|
|
|
}
|
|
|
- return EInvalidDomain
|
|
|
+ /* perform common checks on levels */
|
|
|
+ for _, level := range levels {
|
|
|
+ levelLen := len(level)
|
|
|
+ if levelLen == 0 {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ if levelLen > 63 {
|
|
|
+ return fallbackError(err, EInvalidHostname)
|
|
|
+ }
|
|
|
+ if level[0] == '-' || level[levelLen-1] == '-' {
|
|
|
+ return fallbackError(err, EInvalidHostname)
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+ /* make sure hostname is not an IP address */
|
|
|
+ if ip := net.ParseIP(hostname); ip != nil {
|
|
|
+ return fallbackError(err, EInvalidHostname)
|
|
|
+ }
|
|
|
+ return nil
|
|
|
}
|
|
|
- /* split to levels (tld, domain[, subdomain...] */
|
|
|
- levels := strings.Split(domain, ".")
|
|
|
- levelsLen := len(levels)
|
|
|
- if levelsLen < 2 {
|
|
|
- return EInvalidDomain
|
|
|
+}
|
|
|
+
|
|
|
+/* ValidHostname performs basic hostname checks */
|
|
|
+func ValidHostname(err error) ValidatorFunc {
|
|
|
+ callbackCheck := validHostnameStr(0, err)
|
|
|
+ /* hostname name checks */
|
|
|
+ return func(field *FormField, ctx context.Context) error {
|
|
|
+ return callbackCheck(field.GetString())
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/* ValidDomainName performs basic domain name checks */
|
|
|
+func ValidDomainName(err error) ValidatorFunc {
|
|
|
+ callbackCheck := validHostnameStr(2, err)
|
|
|
+ /* domain name checks */
|
|
|
+ return func(field *FormField, ctx context.Context) error {
|
|
|
+ return callbackCheck(field.GetString())
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/* ValidDomainNameTLD checks if a field in a domain name has a whitelisted TLD */
|
|
|
+func ValidDomainNameTLD(err error) ValidatorFunc {
|
|
|
+ EInvalidDomainTLD := errors.New("invalid domain TLD")
|
|
|
+ return func(field *FormField, ctx context.Context) error {
|
|
|
+ /* make sure tld has proper size and is whitelisted */
|
|
|
+ domain := strings.ToLower(strings.TrimSpace(field.GetString()))
|
|
|
+ levels := strings.Split(domain, ".")
|
|
|
+ domainTLD := levels[len(levels)-1]
|
|
|
+ if len(domainTLD) < 2 {
|
|
|
+ return fallbackError(err, EInvalidDomainTLD)
|
|
|
+ }
|
|
|
+ if _, ok := validTLDs[domainTLD]; !ok {
|
|
|
+ return fallbackError(err, EInvalidDomainTLD)
|
|
|
+ }
|
|
|
+ return nil
|
|
|
}
|
|
|
- /* perform common checks on levels */
|
|
|
- for _, level := range levels {
|
|
|
- levelLen := len(level)
|
|
|
- if levelLen == 0 || levelLen > 63 {
|
|
|
- return EInvalidDomain
|
|
|
+}
|
|
|
+
|
|
|
+/* ValidEmailLocal checks a valid local part for email address */
|
|
|
+func ValidEmailLocal(err error) ValidatorFunc {
|
|
|
+ EInvalidEmail := errors.New("invalid email address")
|
|
|
+ validLetters := ValidLettersGeneric(
|
|
|
+ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ._-+",
|
|
|
+ fallbackError(err, EInvalidEmail),
|
|
|
+ )
|
|
|
+ return func(field *FormField, ctx context.Context) error {
|
|
|
+ /* check for allowed symbols in local part */
|
|
|
+ if err := validLetters(field, ctx); err != nil {
|
|
|
+ return err
|
|
|
}
|
|
|
- if level[0] == '-' || level[levelLen-1] == '-' {
|
|
|
- return EInvalidDomain
|
|
|
+ /* common checks */
|
|
|
+ local := field.GetString()
|
|
|
+ if len(local) > 64 || local[0] == '.' || local[len(local)-1] == '.' || strings.Index(local, "..") != -1 {
|
|
|
+ return fallbackError(err, EInvalidEmail)
|
|
|
}
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+}
|
|
|
|
|
|
+/* ValidEmail checks if field contains a valid email address */
|
|
|
+func ValidEmail(err error) ValidatorFunc {
|
|
|
+ EInvalidEmail := errors.New("invalid email address")
|
|
|
+ domainValidator := validHostnameStr(2, nil)
|
|
|
+ return func(field *FormField, ctx context.Context) error {
|
|
|
+ /* sanitize input */
|
|
|
+ addrStr := strings.TrimSpace(field.GetString())
|
|
|
+ if addrStr == "" || !utf8.ValidString(addrStr) || strings.ContainsAny(addrStr, " \t\r\n") {
|
|
|
+ return fallbackError(err, EInvalidEmail)
|
|
|
+ }
|
|
|
+ /* use mail.ParseAddress on the sanitized text */
|
|
|
+ addr, err := mail.ParseAddress(addrStr)
|
|
|
+ if err != nil || addr.Address != addrStr {
|
|
|
+ return fallbackError(err, EInvalidEmail)
|
|
|
+ }
|
|
|
+ /* make sure email address has a valid domain name */
|
|
|
+ domain := strings.SplitN(addrStr, "@", 2)[1]
|
|
|
+ if err := domainValidator(domain); err != nil {
|
|
|
+ return fallbackError(err, EInvalidEmail)
|
|
|
+ }
|
|
|
+ return nil
|
|
|
}
|
|
|
- /* make sure tld has proper size and is whitelisted */
|
|
|
- domainTLD := levels[len(levels)-1]
|
|
|
- if len(domainTLD) < 2 {
|
|
|
- return EInvalidDomainTLD
|
|
|
+}
|
|
|
+
|
|
|
+/* ValidIP checks if field contains a valid network address */
|
|
|
+func ValidIP(err error) ValidatorFunc {
|
|
|
+ EInvalidAddress := errors.New("invalid network address")
|
|
|
+ return func(field *FormField, ctx context.Context) error {
|
|
|
+ if ip := net.ParseIP(field.GetString()); ip == nil {
|
|
|
+ return fallbackError(err, EInvalidAddress)
|
|
|
+ }
|
|
|
+ return nil
|
|
|
}
|
|
|
- if _, ok := validTLDs[domainTLD]; !ok {
|
|
|
- return EInvalidDomainTLD
|
|
|
+}
|
|
|
+
|
|
|
+/* ValidIPv4 checks if field contains a valid IPv4 network address */
|
|
|
+func ValidIPv4(err error) ValidatorFunc {
|
|
|
+ EInvalidAddress := errors.New("invalid ipv4 address")
|
|
|
+ return func(field *FormField, ctx context.Context) error {
|
|
|
+ if ip := net.ParseIP(field.GetString()); ip == nil || ip.To4() == nil {
|
|
|
+ return fallbackError(err, EInvalidAddress)
|
|
|
+ }
|
|
|
+ return nil
|
|
|
}
|
|
|
- return nil
|
|
|
}
|
|
|
|
|
|
-/* ValidEmail checks if field contains a valid email address */
|
|
|
-func ValidEmail(field *FormField, ctx context.Context) error {
|
|
|
- /* sanitize input */
|
|
|
- addrStr := strings.TrimSpace(field.GetString())
|
|
|
- if addrStr == "" || !utf8.ValidString(addrStr) || strings.ContainsAny(addrStr, " \t\r\n") {
|
|
|
- return EInvalidEmail
|
|
|
- }
|
|
|
- /* use mail.ParseAddress on the sanitized text */
|
|
|
- addr, err := mail.ParseAddress(addrStr)
|
|
|
- if err != nil || addr.Address != addrStr {
|
|
|
- return EInvalidEmail
|
|
|
- }
|
|
|
- /* make sure email address has a valid domain name */
|
|
|
- parts := strings.Split(addrStr, "@")
|
|
|
- if err := validDomainNameStr(parts[len(parts)-1]); err != nil {
|
|
|
- return EInvalidEmail
|
|
|
+/* ValidIPv6 checks if field contains a valid IPv6 network address */
|
|
|
+func ValidIPv6(err error) ValidatorFunc {
|
|
|
+ EInvalidAddress := errors.New("invalid ipv6 address")
|
|
|
+ return func(field *FormField, ctx context.Context) error {
|
|
|
+ if ip := net.ParseIP(field.GetString()); ip == nil || ip.To4() != nil {
|
|
|
+ return fallbackError(err, EInvalidAddress)
|
|
|
+ }
|
|
|
+ return nil
|
|
|
}
|
|
|
- return nil
|
|
|
}
|
|
|
|
|
|
/* ValidCSRF checks if CSRF token is valid */
|