[utils/handlers] Add regex URL validation and remove govalidator
This commit is contained in:
parent
c25d50c35f
commit
5b45a6df96
5 changed files with 129 additions and 8 deletions
1
go.mod
1
go.mod
|
@ -3,7 +3,6 @@ module github.com/aeolyus/gull
|
|||
go 1.14
|
||||
|
||||
require (
|
||||
github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535
|
||||
github.com/gorilla/mux v1.7.4
|
||||
github.com/jinzhu/gorm v1.9.12
|
||||
)
|
||||
|
|
2
go.sum
2
go.sum
|
@ -1,5 +1,3 @@
|
|||
github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 h1:4daAzAu0S6Vi7/lbWECcX0j45yZReDZ56BQsrVBOEEY=
|
||||
github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg=
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd h1:83Wprp6ROGeiHFAP8WJdI2RoxALQYgdllERc3N5N2DM=
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
|
||||
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y=
|
||||
|
|
|
@ -5,7 +5,6 @@ import (
|
|||
"github.com/aeolyus/gull/utils"
|
||||
"net/http"
|
||||
|
||||
valid "github.com/asaskevich/govalidator"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/jinzhu/gorm"
|
||||
_ "github.com/jinzhu/gorm/dialects/sqlite"
|
||||
|
@ -62,7 +61,7 @@ func (a *App) CreateShortURL(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
// Verify URL is valid
|
||||
if !valid.IsRequestURL(u.URL) {
|
||||
if !utils.IsValidURL(u.URL) {
|
||||
http.Error(w, "Invalid URL", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -13,8 +13,49 @@ const (
|
|||
|
||||
// RFC 3986 Section 2.3 URI Unreserved Characters
|
||||
URIUnreservedChars = `^([A-Za-z0-9_.~-])+$`
|
||||
|
||||
// https://gist.github.com/dperini/729294
|
||||
URLRegex = `^` +
|
||||
// protocol identifier (optional)
|
||||
// short syntax // still required
|
||||
`(?:(?:(?:https?|ftp):)?\/\/)` +
|
||||
// user:pass BasicAuth (optional)
|
||||
`(?:\S+(?::\S*)?@)?` +
|
||||
`(?:` +
|
||||
// IP address dotted notation octets
|
||||
// excludes loopback network 0.0.0.0
|
||||
// excludes reserved space >= 224.0.0.0
|
||||
// excludes network & broadcast addresses
|
||||
// (first & last IP address of each class)
|
||||
`(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])` +
|
||||
`(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}` +
|
||||
`(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))` +
|
||||
`|` +
|
||||
// host & domain names, may end with dot
|
||||
// can be replaced by a shortest alternative
|
||||
// (?![-_])(?:[-\w\u00a1-\uffff]{0,63}[^-_]\.)+
|
||||
`(?:` +
|
||||
`(?:` +
|
||||
`[a-z0-9\\u00a1-\\uffff]` +
|
||||
`[a-z0-9\\u00a1-\\uffff_-]{0,62}` +
|
||||
`)?` +
|
||||
`[a-z0-9\\u00a1-\\uffff]\.` +
|
||||
`)+` +
|
||||
// TLD identifier name, may end with dot
|
||||
`(?:[a-z\\u00a1-\\uffff]{2,}\.?)` +
|
||||
`)` +
|
||||
// port number (optional)
|
||||
`(?::\d{2,5})?` +
|
||||
// resource path (optional)
|
||||
`(?:[/?#]\S*)?` +
|
||||
`$`
|
||||
)
|
||||
|
||||
func IsValidURL(str string) bool {
|
||||
valid, err := regexp.MatchString(URLRegex, str)
|
||||
return valid && err == nil
|
||||
}
|
||||
|
||||
// Tests whether a string is in the alphanumeric charset
|
||||
func IsValidAlias(str string) bool {
|
||||
valid, err := regexp.MatchString(URIUnreservedChars, str)
|
||||
|
|
|
@ -4,7 +4,7 @@ import (
|
|||
"testing"
|
||||
)
|
||||
|
||||
func TestValidAlias(t *testing.T) {
|
||||
func TestIsValidAlias(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
name string
|
||||
str string
|
||||
|
@ -27,8 +27,92 @@ func TestValidAlias(t *testing.T) {
|
|||
|
||||
for name, tc := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
if IsValidAlias(tc.str) != tc.val {
|
||||
t.Errorf("For '%s', expected %t. Got %t instead.", tc.str, tc.val, IsValidAlias(tc.str))
|
||||
res := IsValidAlias(tc.str)
|
||||
if res != tc.val {
|
||||
t.Errorf("For '%s', expected %t. Got %t instead.", tc.str, tc.val, res)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsValidURL(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
name string
|
||||
url string
|
||||
val bool
|
||||
}{
|
||||
// Should match
|
||||
"http://foo.com/blah_blah": {url: "http://foo.com/blah_blah", val: true},
|
||||
"http://foo.com/blah_blah/": {url: "http://foo.com/blah_blah/", val: true},
|
||||
"http://foo.com/blah_blah_(wikipedia)": {url: "http://foo.com/blah_blah_(wikipedia)", val: true},
|
||||
"http://foo.com/blah_blah_(wikipedia)_(again)": {url: "http://foo.com/blah_blah_(wikipedia)_(again)", val: true},
|
||||
"http://www.example.com/wpstyle/?p=364": {url: "http://www.example.com/wpstyle/?p=364", val: true},
|
||||
"https://www.example.com/foo/?bar=baz&inga=42&quux": {url: "https://www.example.com/foo/?bar=baz&inga=42&quux", val: true},
|
||||
"http://userid:password@example.com:8080": {url: "http://userid:password@example.com:8080", val: true},
|
||||
"http://userid:password@example.com:8080/": {url: "http://userid:password@example.com:8080/", val: true},
|
||||
"http://userid@example.com": {url: "http://userid@example.com", val: true},
|
||||
"http://userid@example.com/": {url: "http://userid@example.com/", val: true},
|
||||
"http://userid@example.com:8080": {url: "http://userid@example.com:8080", val: true},
|
||||
"http://userid@example.com:8080/": {url: "http://userid@example.com:8080/", val: true},
|
||||
"http://userid:password@example.com": {url: "http://userid:password@example.com", val: true},
|
||||
"http://userid:password@example.com/": {url: "http://userid:password@example.com/", val: true},
|
||||
"http://142.42.1.1/": {url: "http://142.42.1.1/", val: true},
|
||||
"http://142.42.1.1:8080/": {url: "http://142.42.1.1:8080/", val: true},
|
||||
"http://foo.com/blah_(wikipedia)#cite-1": {url: "http://foo.com/blah_(wikipedia)#cite-1", val: true},
|
||||
"http://foo.com/blah_(wikipedia)_blah#cite-1": {url: "http://foo.com/blah_(wikipedia)_blah#cite-1", val: true},
|
||||
"http://foo.com/unicode_(✪)_in_parens": {url: "http://foo.com/unicode_(✪)_in_parens", val: true},
|
||||
"http://foo.com/(something)?after=parens": {url: "http://foo.com/(something)?after=parens", val: true},
|
||||
"http://code.google.com/events/#&product=browser": {url: "http://code.google.com/events/#&product=browser", val: true},
|
||||
"http://j.mp": {url: "http://j.mp", val: true},
|
||||
"ftp://foo.bar/baz": {url: "ftp://foo.bar/baz", val: true},
|
||||
"http://foo.bar/?q=Test%20URL-encoded%20stuff": {url: "http://foo.bar/?q=Test%20URL-encoded%20stuff", val: true},
|
||||
"http://1337.net": {url: "http://1337.net", val: true},
|
||||
"http://a.b-c.de": {url: "http://a.b-c.de", val: true},
|
||||
"http://223.255.255.254": {url: "http://223.255.255.254", val: true},
|
||||
"https://foo_bar.example.com/": {url: "https://foo_bar.example.com/", val: true},
|
||||
"http://www.foo.bar./": {url: "http://www.foo.bar.", val: true},
|
||||
"http://a.b--c.de/": {url: "http://a.b--c.de/", val: true},
|
||||
// Should not match
|
||||
"http://": {url: "http://", val: false},
|
||||
"http://.": {url: "http://.", val: false},
|
||||
"http://..": {url: "http://..", val: false},
|
||||
"http://../": {url: "http://../", val: false},
|
||||
"http://?": {url: "http://?", val: false},
|
||||
"http://??": {url: "http://??", val: false},
|
||||
"http://??/": {url: "http://??/", val: false},
|
||||
"http://#": {url: "http://#", val: false},
|
||||
"http://##": {url: "http://##", val: false},
|
||||
"http://##/": {url: "http://##/", val: false},
|
||||
"http://foo.bar?q=Spaces should be encoded": {url: "http://foo.bar?q=Spaces should be encoded", val: false},
|
||||
"//": {url: "//", val: false},
|
||||
"//a": {url: "//a", val: false},
|
||||
"///a": {url: "///a", val: false},
|
||||
"///": {url: "///", val: false},
|
||||
"http:///a": {url: "http:///a", val: false},
|
||||
"foo.com": {url: "foo.com", val: false},
|
||||
"rdar://1234": {url: "rdar://1234", val: false},
|
||||
"h://test": {url: "h://test", val: false},
|
||||
"http:// shouldfail.com": {url: "http:// shouldfail.com", val: false},
|
||||
":// should fail": {url: ":// should fail", val: false},
|
||||
"http://foo.bar/foo(bar)baz quux": {url: "http://foo.bar/foo(bar)baz quux", val: false},
|
||||
"ftps://foo.bar/": {url: "ftps://foo.bar/", val: false},
|
||||
"http://-error-.invalid/": {url: "http://-error-.invalid/", val: false},
|
||||
"http://-a.b.co": {url: "http://-a.b.co", val: false},
|
||||
"http://a.b-.co": {url: "http://a.b-.co", val: false},
|
||||
"http://0.0.0.0": {url: "http://0.0.0.0", val: false},
|
||||
"http://10.1.1.0": {url: "http://10.1.1.0", val: false},
|
||||
"http://224.1.1.1": {url: "http://224.1.1.1", val: false},
|
||||
"http://1.1.1.1.1": {url: "http://1.1.1.1.1", val: false},
|
||||
"http://3628126748": {url: "http://3628126748", val: false},
|
||||
"http://.www.foo.bar/": {url: "http://.www.foo.bar/", val: false},
|
||||
"http://.www.foo.bar./": {url: "http://.www.foo.bar./", val: false},
|
||||
}
|
||||
|
||||
for name, tc := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
res := IsValidURL(tc.url)
|
||||
if res != tc.val {
|
||||
t.Errorf("For '%s', expected %t. Got %t instead.", tc.url, tc.val, res)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue