diff --git a/baked_in.go b/baked_in.go index 95f56e008..09eedf89f 100644 --- a/baked_in.go +++ b/baked_in.go @@ -1453,33 +1453,37 @@ func isFileURL(path string) bool { // isURL is the validation function for validating if the current field's value is a valid URL. func isURL(fl FieldLevel) bool { field := fl.Field() + var s string switch field.Kind() { case reflect.String: - - s := strings.ToLower(field.String()) - - if len(s) == 0 { - return false + s = strings.ToLower(field.String()) + default: + if stringer, ok := field.Interface().(fmt.Stringer); ok { + s = stringer.String() + } else { + panic(fmt.Sprintf("Bad field type %T", field.Interface())) } + } - if isFileURL(s) { - return true - } + if len(s) == 0 { + return false + } - url, err := url.Parse(s) - if err != nil || url.Scheme == "" { - return false - } + if isFileURL(s) { + return true + } - if url.Host == "" && url.Fragment == "" && url.Opaque == "" { - return false - } + url, err := url.Parse(s) + if err != nil || url.Scheme == "" { + return false + } - return true + if url.Host == "" && url.Fragment == "" && url.Opaque == "" { + return false } - panic(fmt.Sprintf("Bad field type %T", field.Interface())) + return true } // isHttpURL is the validation function for validating if the current field's value is a valid HTTP(s) URL. @@ -1489,20 +1493,24 @@ func isHttpURL(fl FieldLevel) bool { } field := fl.Field() + var s string switch field.Kind() { case reflect.String: - - s := strings.ToLower(field.String()) - - url, err := url.Parse(s) - if err != nil || url.Host == "" { - return false + s = strings.ToLower(field.String()) + default: + if stringer, ok := fl.Field().Interface().(fmt.Stringer); ok { + s = stringer.String() + } else { + panic(fmt.Sprintf("Bad field type %T", field.Interface())) } + } - return url.Scheme == "http" || url.Scheme == "https" + url, err := url.Parse(s) + if err != nil || url.Host == "" { + return false } - panic(fmt.Sprintf("Bad field type %T", field.Interface())) + return url.Scheme == "http" || url.Scheme == "https" } // isUrnRFC2141 is the validation function for validating if the current field's value is a valid URN as per RFC 2141. diff --git a/validator_test.go b/validator_test.go index 2826ae70e..81d19d96e 100644 --- a/validator_test.go +++ b/validator_test.go @@ -11,6 +11,7 @@ import ( "image" "image/jpeg" "image/png" + "net/url" "os" "path/filepath" "reflect" @@ -8134,59 +8135,70 @@ func TestUrnRFC2141(t *testing.T) { PanicMatches(t, func() { _ = validate.Var(i, tag) }, "Bad field type int") } +type Stringer string + +func (s Stringer) String() string { + return string(s) +} + func TestUrl(t *testing.T) { tests := []struct { - param string + param fmt.Stringer expected bool }{ - {"http://foo.bar#com", true}, - {"http://foobar.com", true}, - {"https://foobar.com", true}, - {"foobar.com", false}, - {"http://foobar.coffee/", true}, - {"http://foobar.中文网/", true}, - {"http://foobar.org/", true}, - {"http://foobar.org:8080/", true}, - {"ftp://foobar.ru/", true}, - {"http://user:pass@www.foobar.com/", true}, - {"http://127.0.0.1/", true}, - {"http://duckduckgo.com/?q=%2F", true}, - {"http://localhost:3000/", true}, - {"http://foobar.com/?foo=bar#baz=qux", true}, - {"http://foobar.com?foo=bar", true}, - {"http://www.xn--froschgrn-x9a.net/", true}, - {"", false}, - {"xyz://foobar.com", true}, - {"invalid.", false}, - {".com", false}, - {"rtmp://foobar.com", true}, - {"http://www.foo_bar.com/", true}, - {"http://localhost:3000/", true}, - {"http://foobar.com/#baz", true}, - {"http://foobar.com#baz=qux", true}, - {"http://foobar.com/t$-_.+!*\\'(),", true}, - {"http://www.foobar.com/~foobar", true}, - {"http://www.-foobar.com/", true}, - {"http://www.foo---bar.com/", true}, - {"mailto:someone@example.com", true}, - {"irc://irc.server.org/channel", true}, - {"irc://#channel@network", true}, - {"/abs/test/dir", false}, - {"./rel/test/dir", false}, - {"irc:", false}, - {"http://", false}, - {"file://path/to/file.txt", true}, - {"file:///c:/Windows/file.txt", true}, - {"file://localhost/path/to/file.txt", true}, - {"file://localhost/c:/WINDOWS/file.txt", true}, - {"file://", true}, - {"file:////remotehost/path/file.txt", true}, + {Stringer("http://foo.bar#com"), true}, + {Stringer("http://foobar.com"), true}, + {Stringer("https://foobar.com"), true}, + {Stringer("foobar.com"), false}, + {Stringer("http://foobar.coffee/"), true}, + {Stringer("http://foobar.中文网/"), true}, + {Stringer("http://foobar.org/"), true}, + {Stringer("http://foobar.org:8080/"), true}, + {Stringer("ftp://foobar.ru/"), true}, + {Stringer("http://user:pass@www.foobar.com/"), true}, + {Stringer("http://127.0.0.1/"), true}, + {Stringer("http://duckduckgo.com/?q=%2F"), true}, + {Stringer("http://localhost:3000/"), true}, + {Stringer("http://foobar.com/?foo=bar#baz=qux"), true}, + {Stringer("http://foobar.com?foo=bar"), true}, + {Stringer("http://www.xn--froschgrn-x9a.net/"), true}, + {Stringer(""), false}, + {Stringer("xyz://foobar.com"), true}, + {Stringer("invalid."), false}, + {Stringer(".com"), false}, + {Stringer("rtmp://foobar.com"), true}, + {Stringer("http://www.foo_bar.com/"), true}, + {Stringer("http://localhost:3000/"), true}, + {Stringer("http://foobar.com/#baz"), true}, + {Stringer("http://foobar.com#baz=qux"), true}, + {Stringer("http://foobar.com/t$-_.+!*\\'(),"), true}, + {Stringer("http://www.foobar.com/~foobar"), true}, + {Stringer("http://www.-foobar.com/"), true}, + {Stringer("http://www.foo---bar.com/"), true}, + {Stringer("mailto:someone@example.com"), true}, + {Stringer("irc://irc.server.org/channel"), true}, + {Stringer("irc://#channel@network"), true}, + {Stringer("/abs/test/dir"), false}, + {Stringer("./rel/test/dir"), false}, + {Stringer("irc:"), false}, + {Stringer("http://"), false}, + {Stringer("file://path/to/file.txt"), true}, + {Stringer("file:///c:/Windows/file.txt"), true}, + {Stringer("file://localhost/path/to/file.txt"), true}, + {Stringer("file://localhost/c:/WINDOWS/file.txt"), true}, + {Stringer("file://"), true}, + {Stringer("file:////remotehost/path/file.txt"), true}, + {&url.URL{Scheme: "https", Host: "foobar.com"}, true}, + {&url.URL{Scheme: "file"}, true}, + {&url.URL{Scheme: "file"}, true}, + {&url.URL{Scheme: "file", Path: "/"}, true}, + {&url.URL{Host: "foobar.com"}, false}, + {&url.URL{}, false}, } validate := New() for i, test := range tests { - errs := validate.Var(test.param, "url") if test.expected { @@ -8211,51 +8223,57 @@ func TestUrl(t *testing.T) { func TestHttpUrl(t *testing.T) { tests := []struct { - param string + param fmt.Stringer expected bool }{ - {"http://foo.bar#com", true}, - {"http://foobar.com", true}, - {"HTTP://foobar.com", true}, - {"https://foobar.com", true}, - {"foobar.com", false}, - {"http://foobar.coffee/", true}, - {"http://foobar.中文网/", true}, - {"http://foobar.org/", true}, - {"http://foobar.org:8080/", true}, - {"ftp://foobar.ru/", false}, - {"file:///etc/passwd", false}, - {"file://C:/windows/win.ini", false}, - {"http://user:pass@www.foobar.com/", true}, - {"http://127.0.0.1/", true}, - {"http://duckduckgo.com/?q=%2F", true}, - {"http://localhost:3000/", true}, - {"http://foobar.com/?foo=bar#baz=qux", true}, - {"http://foobar.com?foo=bar", true}, - {"http://www.xn--froschgrn-x9a.net/", true}, - {"", false}, - {"a://b", false}, - {"xyz://foobar.com", false}, - {"invalid.", false}, - {".com", false}, - {"rtmp://foobar.com", false}, - {"http://www.foo_bar.com/", true}, - {"http://localhost:3000/", true}, - {"http://foobar.com/#baz", true}, - {"http://foobar.com#baz=qux", true}, - {"http://foobar.com/t$-_.+!*\\'(),", true}, - {"http://www.foobar.com/~foobar", true}, - {"http://www.-foobar.com/", true}, - {"http://www.foo---bar.com/", true}, - {"mailto:someone@example.com", false}, - {"irc://irc.server.org/channel", false}, - {"irc://#channel@network", false}, - {"/abs/test/dir", false}, - {"./rel/test/dir", false}, - {"http:", false}, - {"http://", false}, - {"http://#invalid", false}, - {"https://1.1.1.1", true}, + {Stringer("http://foo.bar#com"), true}, + {Stringer("http://foobar.com"), true}, + {Stringer("HTTP://foobar.com"), true}, + {Stringer("https://foobar.com"), true}, + {Stringer("foobar.com"), false}, + {Stringer("http://foobar.coffee/"), true}, + {Stringer("http://foobar.中文网/"), true}, + {Stringer("http://foobar.org/"), true}, + {Stringer("http://foobar.org:8080/"), true}, + {Stringer("ftp://foobar.ru/"), false}, + {Stringer("file:///etc/passwd"), false}, + {Stringer("file://C:/windows/win.ini"), false}, + {Stringer("http://user:pass@www.foobar.com/"), true}, + {Stringer("http://127.0.0.1/"), true}, + {Stringer("http://duckduckgo.com/?q=%2F"), true}, + {Stringer("http://localhost:3000/"), true}, + {Stringer("http://foobar.com/?foo=bar#baz=qux"), true}, + {Stringer("http://foobar.com?foo=bar"), true}, + {Stringer("http://www.xn--froschgrn-x9a.net/"), true}, + {Stringer(""), false}, + {Stringer("a://b"), false}, + {Stringer("xyz://foobar.com"), false}, + {Stringer("invalid."), false}, + {Stringer(".com"), false}, + {Stringer("rtmp://foobar.com"), false}, + {Stringer("http://www.foo_bar.com/"), true}, + {Stringer("http://localhost:3000/"), true}, + {Stringer("http://foobar.com/#baz"), true}, + {Stringer("http://foobar.com#baz=qux"), true}, + {Stringer("http://foobar.com/t$-_.+!*\\'(),"), true}, + {Stringer("http://www.foobar.com/~foobar"), true}, + {Stringer("http://www.-foobar.com/"), true}, + {Stringer("http://www.foo---bar.com/"), true}, + {Stringer("mailto:someone@example.com"), false}, + {Stringer("irc://irc.server.org/channel"), false}, + {Stringer("irc://#channel@network"), false}, + {Stringer("/abs/test/dir"), false}, + {Stringer("./rel/test/dir"), false}, + {Stringer("http:"), false}, + {Stringer("http://"), false}, + {Stringer("http://#invalid"), false}, + {Stringer("https://1.1.1.1"), true}, + + {&url.URL{Scheme: "https", Host: "foobar.com"}, true}, + {&url.URL{Scheme: "file"}, false}, + {&url.URL{Scheme: "file", Path: "/"}, false}, + {&url.URL{Host: "foobar.com"}, false}, + {&url.URL{}, false}, } validate := New()