mirror of
https://github.com/portainer/portainer.git
synced 2025-07-19 05:19:39 +02:00
fix(govalidator): replace govalidator dependency [BE-11574] (#673)
This commit is contained in:
parent
3edacee59b
commit
1a3df54c04
18 changed files with 571 additions and 43 deletions
111
pkg/validate/validate.go
Normal file
111
pkg/validate/validate.go
Normal file
|
@ -0,0 +1,111 @@
|
|||
package validate
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
const (
|
||||
minURLRuneCount = 3
|
||||
maxURLRuneCount = 2083
|
||||
|
||||
ipPattern = `(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))`
|
||||
urlSchemaPattern = `((ftp|tcp|udp|wss?|https?):\/\/)`
|
||||
urlUsernamePattern = `(\S+(:\S*)?@)`
|
||||
urlPathPattern = `((\/|\?|#)[^\s]*)`
|
||||
urlPortPattern = `(:(\d{1,5}))`
|
||||
urlIPPattern = `([1-9]\d?|1\d\d|2[01]\d|22[0-3]|24\d|25[0-5])(\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])){2}(?:\.([0-9]\d?|1\d\d|2[0-4]\d|25[0-5]))`
|
||||
urlSubdomainPattern = `((www\.)|([a-zA-Z0-9]+([-_\.]?[a-zA-Z0-9])*[a-zA-Z0-9]\.[a-zA-Z0-9]+))`
|
||||
urlPattern = `^` + urlSchemaPattern + `?` + urlUsernamePattern + `?` + `((` + urlIPPattern + `|(\[` + ipPattern + `\])|(([a-zA-Z0-9]([a-zA-Z0-9-_]+)?[a-zA-Z0-9]([-\.][a-zA-Z0-9]+)*)|(` + urlSubdomainPattern + `?))?(([a-zA-Z\x{00a1}-\x{ffff}0-9]+-?-?)*[a-zA-Z\x{00a1}-\x{ffff}0-9]+)(?:\.([a-zA-Z\x{00a1}-\x{ffff}]{1,}))?))\.?` + urlPortPattern + `?` + urlPathPattern + `?$`
|
||||
)
|
||||
|
||||
var (
|
||||
urlRegex = regexp.MustCompile(urlPattern)
|
||||
uuidRegex = regexp.MustCompile(`^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$`)
|
||||
hexadecimalRegex = regexp.MustCompile(`^[0-9a-fA-F]+$`)
|
||||
whitespaceRegex = regexp.MustCompile(`^[[:space:]]+$`)
|
||||
dnsNameRegex = regexp.MustCompile(`^([a-zA-Z0-9_]{1}[a-zA-Z0-9_-]{0,62}){1}(\.[a-zA-Z0-9_]{1}[a-zA-Z0-9_-]{0,62})*[\._]?$`)
|
||||
)
|
||||
|
||||
func IsURL(urlString string) bool {
|
||||
if urlString == "" ||
|
||||
utf8.RuneCountInString(urlString) >= maxURLRuneCount ||
|
||||
len(urlString) <= minURLRuneCount ||
|
||||
strings.HasPrefix(urlString, ".") {
|
||||
return false
|
||||
}
|
||||
|
||||
strTemp := urlString
|
||||
if strings.Contains(urlString, ":") && !strings.Contains(urlString, "://") {
|
||||
// support no indicated urlscheme but with colon for port number
|
||||
// http:// is appended so url.Parse will succeed, strTemp used so it does not impact rxURL.MatchString
|
||||
strTemp = "http://" + urlString
|
||||
}
|
||||
|
||||
u, err := url.Parse(strTemp)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if strings.HasPrefix(u.Host, ".") {
|
||||
return false
|
||||
}
|
||||
if u.Host == "" && (u.Path != "" && !strings.Contains(u.Path, ".")) {
|
||||
return false
|
||||
}
|
||||
|
||||
return urlRegex.MatchString(urlString)
|
||||
}
|
||||
|
||||
func IsUUID(uuidString string) bool {
|
||||
return uuidRegex.MatchString(uuidString)
|
||||
}
|
||||
|
||||
func IsHexadecimal(hexString string) bool {
|
||||
return hexadecimalRegex.MatchString(hexString)
|
||||
}
|
||||
|
||||
func HasWhitespaceOnly(s string) bool {
|
||||
return len(s) > 0 && whitespaceRegex.MatchString(s)
|
||||
}
|
||||
|
||||
func MinStringLength(s string, len int) bool {
|
||||
return utf8.RuneCountInString(s) >= len
|
||||
}
|
||||
|
||||
func Matches(s, pattern string) bool {
|
||||
match, err := regexp.MatchString(pattern, s)
|
||||
return err == nil && match
|
||||
}
|
||||
|
||||
func IsNonPositive(f float64) bool {
|
||||
return f <= 0
|
||||
}
|
||||
|
||||
func InRange(val, left, right float64) bool {
|
||||
if left > right {
|
||||
left, right = right, left
|
||||
}
|
||||
|
||||
return val >= left && val <= right
|
||||
}
|
||||
|
||||
func IsHost(s string) bool {
|
||||
return IsIP(s) || IsDNSName(s)
|
||||
}
|
||||
|
||||
func IsIP(s string) bool {
|
||||
return net.ParseIP(s) != nil
|
||||
}
|
||||
|
||||
func IsDNSName(s string) bool {
|
||||
if s == "" || len(strings.ReplaceAll(s, ".", "")) > 255 {
|
||||
// constraints already violated
|
||||
return false
|
||||
}
|
||||
|
||||
return !IsIP(s) && dnsNameRegex.MatchString(s)
|
||||
}
|
424
pkg/validate/validate_test.go
Normal file
424
pkg/validate/validate_test.go
Normal file
|
@ -0,0 +1,424 @@
|
|||
package validate
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_IsURL(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
url string
|
||||
expectedResult bool
|
||||
}{
|
||||
{
|
||||
name: "simple url",
|
||||
url: "https://google.com",
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
name: "no schema",
|
||||
url: "google.com",
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
name: "path",
|
||||
url: "https://google.com/some/thing",
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
name: "query params",
|
||||
url: "https://google.com/some/thing?a=5&b=6",
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
name: "invalid",
|
||||
url: "google",
|
||||
expectedResult: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
result := IsURL(tc.url)
|
||||
require.Equal(t, tc.expectedResult, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_IsUUID(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
uuid string
|
||||
expectedResult bool
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
uuid: "",
|
||||
expectedResult: false,
|
||||
},
|
||||
{
|
||||
name: "version 3 UUID",
|
||||
uuid: "060507eb-3b9a-362e-b850-d5f065eea403",
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
name: "version 4 UUID",
|
||||
uuid: "63e695ee-48a9-498a-98b3-9472ff75e09f",
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
name: "version 5 UUID",
|
||||
uuid: "5daabcd8-f17e-568c-aa6f-da9d92c7032c",
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
name: "text",
|
||||
uuid: "something like this",
|
||||
expectedResult: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
result := IsUUID(tc.uuid)
|
||||
require.Equal(t, tc.expectedResult, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_IsHexadecimal(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
hex string
|
||||
expectedResult bool
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
hex: "",
|
||||
expectedResult: false,
|
||||
},
|
||||
{
|
||||
name: "hex",
|
||||
hex: "48656C6C6F20736F6D657468696E67",
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
name: "text",
|
||||
hex: "something like this",
|
||||
expectedResult: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
result := IsHexadecimal(tc.hex)
|
||||
require.Equal(t, tc.expectedResult, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_HasWhitespaceOnly(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
s string
|
||||
expectedResult bool
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
s: "",
|
||||
expectedResult: false,
|
||||
},
|
||||
{
|
||||
name: "space",
|
||||
s: " ",
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
name: "tab",
|
||||
s: "\t",
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
name: "text",
|
||||
s: "something like this",
|
||||
expectedResult: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
result := HasWhitespaceOnly(tc.s)
|
||||
require.Equal(t, tc.expectedResult, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_MinStringLength(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
s string
|
||||
len int
|
||||
expectedResult bool
|
||||
}{
|
||||
{
|
||||
name: "empty + zero len",
|
||||
s: "",
|
||||
len: 0,
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
name: "empty + non zero len",
|
||||
s: "",
|
||||
len: 10,
|
||||
expectedResult: false,
|
||||
},
|
||||
{
|
||||
name: "long text + non zero len",
|
||||
s: "something else",
|
||||
len: 10,
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
name: "multibyte characters - enough",
|
||||
s: "X生",
|
||||
len: 2,
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
name: "multibyte characters - not enough",
|
||||
s: "X生",
|
||||
len: 3,
|
||||
expectedResult: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
result := MinStringLength(tc.s, tc.len)
|
||||
require.Equal(t, tc.expectedResult, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Matches(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
s string
|
||||
pattern string
|
||||
expectedResult bool
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
s: "",
|
||||
pattern: "",
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
name: "space",
|
||||
s: "something else",
|
||||
pattern: " ",
|
||||
expectedResult: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
result := Matches(tc.s, tc.pattern)
|
||||
require.Equal(t, tc.expectedResult, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_IsNonPositive(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
f float64
|
||||
expectedResult bool
|
||||
}{
|
||||
{
|
||||
name: "zero",
|
||||
f: 0,
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
name: "positive",
|
||||
f: 1,
|
||||
expectedResult: false,
|
||||
},
|
||||
{
|
||||
name: "negative",
|
||||
f: -1,
|
||||
expectedResult: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
result := IsNonPositive(tc.f)
|
||||
require.Equal(t, tc.expectedResult, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_InRange(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
f float64
|
||||
left float64
|
||||
right float64
|
||||
expectedResult bool
|
||||
}{
|
||||
{
|
||||
name: "zero",
|
||||
f: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
name: "equal left",
|
||||
f: 1,
|
||||
left: 1,
|
||||
right: 2,
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
name: "equal right",
|
||||
f: 2,
|
||||
left: 1,
|
||||
right: 2,
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
name: "above",
|
||||
f: 3,
|
||||
left: 1,
|
||||
right: 2,
|
||||
expectedResult: false,
|
||||
},
|
||||
{
|
||||
name: "below",
|
||||
f: 0,
|
||||
left: 1,
|
||||
right: 2,
|
||||
expectedResult: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
result := InRange(tc.f, tc.left, tc.right)
|
||||
require.Equal(t, tc.expectedResult, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_IsHost(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
s string
|
||||
expectedResult bool
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
s: "",
|
||||
expectedResult: false,
|
||||
},
|
||||
{
|
||||
name: "ip address",
|
||||
s: "192.168.1.1",
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
name: "hostname",
|
||||
s: "google.com",
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
name: "text",
|
||||
s: "Something like this",
|
||||
expectedResult: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
result := IsHost(tc.s)
|
||||
require.Equal(t, tc.expectedResult, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_IsIP(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
s string
|
||||
expectedResult bool
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
s: "",
|
||||
expectedResult: false,
|
||||
},
|
||||
{
|
||||
name: "ip address",
|
||||
s: "192.168.1.1",
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
name: "hostname",
|
||||
s: "google.com",
|
||||
expectedResult: false,
|
||||
},
|
||||
{
|
||||
name: "text",
|
||||
s: "Something like this",
|
||||
expectedResult: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
result := IsIP(tc.s)
|
||||
require.Equal(t, tc.expectedResult, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_IsDNSName(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
s string
|
||||
expectedResult bool
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
s: "",
|
||||
expectedResult: false,
|
||||
},
|
||||
{
|
||||
name: "ip address",
|
||||
s: "192.168.1.1",
|
||||
expectedResult: false,
|
||||
},
|
||||
{
|
||||
name: "hostname",
|
||||
s: "google.com",
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
name: "text",
|
||||
s: "Something like this",
|
||||
expectedResult: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
result := IsDNSName(tc.s)
|
||||
require.Equal(t, tc.expectedResult, result)
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue