|
|
@ -7,6 +7,7 @@ import ( |
|
|
|
"encoding/hex" |
|
|
|
"encoding/hex" |
|
|
|
"encoding/json" |
|
|
|
"encoding/json" |
|
|
|
"fmt" |
|
|
|
"fmt" |
|
|
|
|
|
|
|
"io/fs" |
|
|
|
"net" |
|
|
|
"net" |
|
|
|
"net/url" |
|
|
|
"net/url" |
|
|
|
"os" |
|
|
|
"os" |
|
|
@ -14,13 +15,14 @@ import ( |
|
|
|
"strconv" |
|
|
|
"strconv" |
|
|
|
"strings" |
|
|
|
"strings" |
|
|
|
"sync" |
|
|
|
"sync" |
|
|
|
|
|
|
|
"syscall" |
|
|
|
"time" |
|
|
|
"time" |
|
|
|
"unicode/utf8" |
|
|
|
"unicode/utf8" |
|
|
|
|
|
|
|
|
|
|
|
"golang.org/x/crypto/sha3" |
|
|
|
"golang.org/x/crypto/sha3" |
|
|
|
"golang.org/x/text/language" |
|
|
|
"golang.org/x/text/language" |
|
|
|
|
|
|
|
|
|
|
|
urn "github.com/leodido/go-urn" |
|
|
|
"github.com/leodido/go-urn" |
|
|
|
) |
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
// Func accepts a FieldLevel interface for all validation needs. The return
|
|
|
|
// Func accepts a FieldLevel interface for all validation needs. The return
|
|
|
@ -127,6 +129,7 @@ var ( |
|
|
|
"uri": isURI, |
|
|
|
"uri": isURI, |
|
|
|
"urn_rfc2141": isUrnRFC2141, // RFC 2141
|
|
|
|
"urn_rfc2141": isUrnRFC2141, // RFC 2141
|
|
|
|
"file": isFile, |
|
|
|
"file": isFile, |
|
|
|
|
|
|
|
"filepath": isFilePath, |
|
|
|
"base64": isBase64, |
|
|
|
"base64": isBase64, |
|
|
|
"base64url": isBase64URL, |
|
|
|
"base64url": isBase64URL, |
|
|
|
"base64rawurl": isBase64RawURL, |
|
|
|
"base64rawurl": isBase64RawURL, |
|
|
@ -199,6 +202,7 @@ var ( |
|
|
|
"html_encoded": isHTMLEncoded, |
|
|
|
"html_encoded": isHTMLEncoded, |
|
|
|
"url_encoded": isURLEncoded, |
|
|
|
"url_encoded": isURLEncoded, |
|
|
|
"dir": isDir, |
|
|
|
"dir": isDir, |
|
|
|
|
|
|
|
"dirpath": isDirPath, |
|
|
|
"json": isJSON, |
|
|
|
"json": isJSON, |
|
|
|
"jwt": isJWT, |
|
|
|
"jwt": isJWT, |
|
|
|
"hostname_port": isHostnamePort, |
|
|
|
"hostname_port": isHostnamePort, |
|
|
@ -1464,7 +1468,7 @@ func isUrnRFC2141(fl FieldLevel) bool { |
|
|
|
panic(fmt.Sprintf("Bad field type %T", field.Interface())) |
|
|
|
panic(fmt.Sprintf("Bad field type %T", field.Interface())) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// isFile is the validation function for validating if the current field's value is a valid file path.
|
|
|
|
// isFile is the validation function for validating if the current field's value is a valid existing file path.
|
|
|
|
func isFile(fl FieldLevel) bool { |
|
|
|
func isFile(fl FieldLevel) bool { |
|
|
|
field := fl.Field() |
|
|
|
field := fl.Field() |
|
|
|
|
|
|
|
|
|
|
@ -1481,6 +1485,57 @@ func isFile(fl FieldLevel) bool { |
|
|
|
panic(fmt.Sprintf("Bad field type %T", field.Interface())) |
|
|
|
panic(fmt.Sprintf("Bad field type %T", field.Interface())) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// isFilePath is the validation function for validating if the current field's value is a valid file path.
|
|
|
|
|
|
|
|
func isFilePath(fl FieldLevel) bool { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var exists bool |
|
|
|
|
|
|
|
var err error |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
field := fl.Field() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// If it exists, it obviously is valid.
|
|
|
|
|
|
|
|
// This is done first to avoid code duplication and unnecessary additional logic.
|
|
|
|
|
|
|
|
if exists = isFile(fl); exists { |
|
|
|
|
|
|
|
return true |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// It does not exist but may still be a valid filepath.
|
|
|
|
|
|
|
|
switch field.Kind() { |
|
|
|
|
|
|
|
case reflect.String: |
|
|
|
|
|
|
|
// Every OS allows for whitespace, but none
|
|
|
|
|
|
|
|
// let you use a file with no filename (to my knowledge).
|
|
|
|
|
|
|
|
// Unless you're dealing with raw inodes, but I digress.
|
|
|
|
|
|
|
|
if strings.TrimSpace(field.String()) == "" { |
|
|
|
|
|
|
|
return false |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
// We make sure it isn't a directory.
|
|
|
|
|
|
|
|
if strings.HasSuffix(field.String(), string(os.PathSeparator)) { |
|
|
|
|
|
|
|
return false |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if _, err = os.Stat(field.String()); err != nil { |
|
|
|
|
|
|
|
switch t := err.(type) { |
|
|
|
|
|
|
|
case *fs.PathError: |
|
|
|
|
|
|
|
if t.Err == syscall.EINVAL { |
|
|
|
|
|
|
|
// It's definitely an invalid character in the filepath.
|
|
|
|
|
|
|
|
return false |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
// It could be a permission error, a does-not-exist error, etc.
|
|
|
|
|
|
|
|
// Out-of-scope for this validation, though.
|
|
|
|
|
|
|
|
return true |
|
|
|
|
|
|
|
default: |
|
|
|
|
|
|
|
// Something went *seriously* wrong.
|
|
|
|
|
|
|
|
/* |
|
|
|
|
|
|
|
Per https://pkg.go.dev/os#Stat:
|
|
|
|
|
|
|
|
"If there is an error, it will be of type *PathError." |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
panic(err) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
panic(fmt.Sprintf("Bad field type %T", field.Interface())) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// isE164 is the validation function for validating if the current field's value is a valid e.164 formatted phone number.
|
|
|
|
// isE164 is the validation function for validating if the current field's value is a valid e.164 formatted phone number.
|
|
|
|
func isE164(fl FieldLevel) bool { |
|
|
|
func isE164(fl FieldLevel) bool { |
|
|
|
return e164Regex.MatchString(fl.Field().String()) |
|
|
|
return e164Regex.MatchString(fl.Field().String()) |
|
|
@ -2354,7 +2409,7 @@ func isFQDN(fl FieldLevel) bool { |
|
|
|
return fqdnRegexRFC1123.MatchString(val) |
|
|
|
return fqdnRegexRFC1123.MatchString(val) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// isDir is the validation function for validating if the current field's value is a valid directory.
|
|
|
|
// isDir is the validation function for validating if the current field's value is a valid existing directory.
|
|
|
|
func isDir(fl FieldLevel) bool { |
|
|
|
func isDir(fl FieldLevel) bool { |
|
|
|
field := fl.Field() |
|
|
|
field := fl.Field() |
|
|
|
|
|
|
|
|
|
|
@ -2370,6 +2425,64 @@ func isDir(fl FieldLevel) bool { |
|
|
|
panic(fmt.Sprintf("Bad field type %T", field.Interface())) |
|
|
|
panic(fmt.Sprintf("Bad field type %T", field.Interface())) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// isDirPath is the validation function for validating if the current field's value is a valid directory.
|
|
|
|
|
|
|
|
func isDirPath(fl FieldLevel) bool { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var exists bool |
|
|
|
|
|
|
|
var err error |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
field := fl.Field() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// If it exists, it obviously is valid.
|
|
|
|
|
|
|
|
// This is done first to avoid code duplication and unnecessary additional logic.
|
|
|
|
|
|
|
|
if exists = isDir(fl); exists { |
|
|
|
|
|
|
|
return true |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// It does not exist but may still be a valid path.
|
|
|
|
|
|
|
|
switch field.Kind() { |
|
|
|
|
|
|
|
case reflect.String: |
|
|
|
|
|
|
|
// Every OS allows for whitespace, but none
|
|
|
|
|
|
|
|
// let you use a dir with no name (to my knowledge).
|
|
|
|
|
|
|
|
// Unless you're dealing with raw inodes, but I digress.
|
|
|
|
|
|
|
|
if strings.TrimSpace(field.String()) == "" { |
|
|
|
|
|
|
|
return false |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if _, err = os.Stat(field.String()); err != nil { |
|
|
|
|
|
|
|
switch t := err.(type) { |
|
|
|
|
|
|
|
case *fs.PathError: |
|
|
|
|
|
|
|
if t.Err == syscall.EINVAL { |
|
|
|
|
|
|
|
// It's definitely an invalid character in the path.
|
|
|
|
|
|
|
|
return false |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
// It could be a permission error, a does-not-exist error, etc.
|
|
|
|
|
|
|
|
// Out-of-scope for this validation, though.
|
|
|
|
|
|
|
|
// Lastly, we make sure it is a directory.
|
|
|
|
|
|
|
|
if strings.HasSuffix(field.String(), string(os.PathSeparator)) { |
|
|
|
|
|
|
|
return true |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
return false |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
default: |
|
|
|
|
|
|
|
// Something went *seriously* wrong.
|
|
|
|
|
|
|
|
/* |
|
|
|
|
|
|
|
Per https://pkg.go.dev/os#Stat:
|
|
|
|
|
|
|
|
"If there is an error, it will be of type *PathError." |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
panic(err) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
// We repeat the check here to make sure it is an explicit directory in case the above os.Stat didn't trigger an error.
|
|
|
|
|
|
|
|
if strings.HasSuffix(field.String(), string(os.PathSeparator)) { |
|
|
|
|
|
|
|
return true |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
return false |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
panic(fmt.Sprintf("Bad field type %T", field.Interface())) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// isJSON is the validation function for validating if the current field's value is a valid json string.
|
|
|
|
// isJSON is the validation function for validating if the current field's value is a valid json string.
|
|
|
|
func isJSON(fl FieldLevel) bool { |
|
|
|
func isJSON(fl FieldLevel) bool { |
|
|
|
field := fl.Field() |
|
|
|
field := fl.Field() |
|
|
|