* Add correct git branch name validation * Change git refname validation error constant name * Implement URL validation based on GoLang url.Parse method * Backward compatibility with older Go compiler * Add git reference name validation unit tests * Remove unused variable in unit test * Implement URL validation based on GoLang url.Parse method * Backward compatibility with older Go compiler * Add url validation unit testsfor-closed-social
@ -0,0 +1,102 @@ | |||
// Copyright 2017 The Gitea Authors. All rights reserved. | |||
// Use of this source code is governed by a MIT-style | |||
// license that can be found in the LICENSE file. | |||
package validation | |||
import ( | |||
"fmt" | |||
"net/url" | |||
"regexp" | |||
"strings" | |||
"github.com/go-macaron/binding" | |||
) | |||
const ( | |||
// ErrGitRefName is git reference name error | |||
ErrGitRefName = "GitRefNameError" | |||
) | |||
var ( | |||
// GitRefNamePattern is regular expression wirh unallowed characters in git reference name | |||
GitRefNamePattern = regexp.MustCompile("[^\\d\\w-_\\./]") | |||
) | |||
// AddBindingRules adds additional binding rules | |||
func AddBindingRules() { | |||
addGitRefNameBindingRule() | |||
addValidURLBindingRule() | |||
} | |||
func addGitRefNameBindingRule() { | |||
// Git refname validation rule | |||
binding.AddRule(&binding.Rule{ | |||
IsMatch: func(rule string) bool { | |||
return strings.HasPrefix(rule, "GitRefName") | |||
}, | |||
IsValid: func(errs binding.Errors, name string, val interface{}) (bool, binding.Errors) { | |||
str := fmt.Sprintf("%v", val) | |||
if GitRefNamePattern.MatchString(str) { | |||
errs.Add([]string{name}, ErrGitRefName, "GitRefName") | |||
return false, errs | |||
} | |||
// Additional rules as described at https://www.kernel.org/pub/software/scm/git/docs/git-check-ref-format.html | |||
if strings.HasPrefix(str, "/") || strings.HasSuffix(str, "/") || | |||
strings.HasPrefix(str, ".") || strings.HasSuffix(str, ".") || | |||
strings.HasSuffix(str, ".lock") || | |||
strings.Contains(str, "..") || strings.Contains(str, "//") { | |||
errs.Add([]string{name}, ErrGitRefName, "GitRefName") | |||
return false, errs | |||
} | |||
return true, errs | |||
}, | |||
}) | |||
} | |||
func addValidURLBindingRule() { | |||
// URL validation rule | |||
binding.AddRule(&binding.Rule{ | |||
IsMatch: func(rule string) bool { | |||
return strings.HasPrefix(rule, "ValidUrl") | |||
}, | |||
IsValid: func(errs binding.Errors, name string, val interface{}) (bool, binding.Errors) { | |||
str := fmt.Sprintf("%v", val) | |||
if len(str) != 0 { | |||
if u, err := url.ParseRequestURI(str); err != nil || | |||
(u.Scheme != "http" && u.Scheme != "https") || | |||
!validPort(portOnly(u.Host)) { | |||
errs.Add([]string{name}, binding.ERR_URL, "Url") | |||
return false, errs | |||
} | |||
} | |||
return true, errs | |||
}, | |||
}) | |||
} | |||
func portOnly(hostport string) string { | |||
colon := strings.IndexByte(hostport, ':') | |||
if colon == -1 { | |||
return "" | |||
} | |||
if i := strings.Index(hostport, "]:"); i != -1 { | |||
return hostport[i+len("]:"):] | |||
} | |||
if strings.Contains(hostport, "]") { | |||
return "" | |||
} | |||
return hostport[colon+len(":"):] | |||
} | |||
func validPort(p string) bool { | |||
for _, r := range []byte(p) { | |||
if r < '0' || r > '9' { | |||
return false | |||
} | |||
} | |||
return true | |||
} |
@ -0,0 +1,62 @@ | |||
// Copyright 2017 The Gitea Authors. All rights reserved. | |||
// Use of this source code is governed by a MIT-style | |||
// license that can be found in the LICENSE file. | |||
package validation | |||
import ( | |||
"fmt" | |||
"net/http" | |||
"net/http/httptest" | |||
"testing" | |||
"github.com/go-macaron/binding" | |||
"github.com/stretchr/testify/assert" | |||
"gopkg.in/macaron.v1" | |||
) | |||
const ( | |||
testRoute = "/test" | |||
) | |||
type ( | |||
validationTestCase struct { | |||
description string | |||
data interface{} | |||
expectedErrors binding.Errors | |||
} | |||
handlerFunc func(interface{}, ...interface{}) macaron.Handler | |||
modeler interface { | |||
Model() string | |||
} | |||
TestForm struct { | |||
BranchName string `form:"BranchName" binding:"GitRefName"` | |||
URL string `form:"ValidUrl" binding:"ValidUrl"` | |||
} | |||
) | |||
func performValidationTest(t *testing.T, testCase validationTestCase) { | |||
httpRecorder := httptest.NewRecorder() | |||
m := macaron.Classic() | |||
m.Post(testRoute, binding.Validate(testCase.data), func(actual binding.Errors) { | |||
assert.Equal(t, fmt.Sprintf("%+v", testCase.expectedErrors), fmt.Sprintf("%+v", actual)) | |||
}) | |||
req, err := http.NewRequest("POST", testRoute, nil) | |||
if err != nil { | |||
panic(err) | |||
} | |||
m.ServeHTTP(httpRecorder, req) | |||
switch httpRecorder.Code { | |||
case http.StatusNotFound: | |||
panic("Routing is messed up in test fixture (got 404): check methods and paths") | |||
case http.StatusInternalServerError: | |||
panic("Something bad happened on '" + testCase.description + "'") | |||
} | |||
} |
@ -0,0 +1,142 @@ | |||
// Copyright 2017 The Gitea Authors. All rights reserved. | |||
// Use of this source code is governed by a MIT-style | |||
// license that can be found in the LICENSE file. | |||
package validation | |||
import ( | |||
"testing" | |||
"github.com/go-macaron/binding" | |||
) | |||
var gitRefNameValidationTestCases = []validationTestCase{ | |||
{ | |||
description: "Referece contains only characters", | |||
data: TestForm{ | |||
BranchName: "test", | |||
}, | |||
expectedErrors: binding.Errors{}, | |||
}, | |||
{ | |||
description: "Reference name contains single slash", | |||
data: TestForm{ | |||
BranchName: "feature/test", | |||
}, | |||
expectedErrors: binding.Errors{}, | |||
}, | |||
{ | |||
description: "Reference name contains backslash", | |||
data: TestForm{ | |||
BranchName: "feature\\test", | |||
}, | |||
expectedErrors: binding.Errors{ | |||
binding.Error{ | |||
FieldNames: []string{"BranchName"}, | |||
Classification: ErrGitRefName, | |||
Message: "GitRefName", | |||
}, | |||
}, | |||
}, | |||
{ | |||
description: "Reference name starts with dot", | |||
data: TestForm{ | |||
BranchName: ".test", | |||
}, | |||
expectedErrors: binding.Errors{ | |||
binding.Error{ | |||
FieldNames: []string{"BranchName"}, | |||
Classification: ErrGitRefName, | |||
Message: "GitRefName", | |||
}, | |||
}, | |||
}, | |||
{ | |||
description: "Reference name ends with dot", | |||
data: TestForm{ | |||
BranchName: "test.", | |||
}, | |||
expectedErrors: binding.Errors{ | |||
binding.Error{ | |||
FieldNames: []string{"BranchName"}, | |||
Classification: ErrGitRefName, | |||
Message: "GitRefName", | |||
}, | |||
}, | |||
}, | |||
{ | |||
description: "Reference name starts with slash", | |||
data: TestForm{ | |||
BranchName: "/test", | |||
}, | |||
expectedErrors: binding.Errors{ | |||
binding.Error{ | |||
FieldNames: []string{"BranchName"}, | |||
Classification: ErrGitRefName, | |||
Message: "GitRefName", | |||
}, | |||
}, | |||
}, | |||
{ | |||
description: "Reference name ends with slash", | |||
data: TestForm{ | |||
BranchName: "test/", | |||
}, | |||
expectedErrors: binding.Errors{ | |||
binding.Error{ | |||
FieldNames: []string{"BranchName"}, | |||
Classification: ErrGitRefName, | |||
Message: "GitRefName", | |||
}, | |||
}, | |||
}, | |||
{ | |||
description: "Reference name ends with .lock", | |||
data: TestForm{ | |||
BranchName: "test.lock", | |||
}, | |||
expectedErrors: binding.Errors{ | |||
binding.Error{ | |||
FieldNames: []string{"BranchName"}, | |||
Classification: ErrGitRefName, | |||
Message: "GitRefName", | |||
}, | |||
}, | |||
}, | |||
{ | |||
description: "Reference name contains multiple consecutive dots", | |||
data: TestForm{ | |||
BranchName: "te..st", | |||
}, | |||
expectedErrors: binding.Errors{ | |||
binding.Error{ | |||
FieldNames: []string{"BranchName"}, | |||
Classification: ErrGitRefName, | |||
Message: "GitRefName", | |||
}, | |||
}, | |||
}, | |||
{ | |||
description: "Reference name contains multiple consecutive slashes", | |||
data: TestForm{ | |||
BranchName: "te//st", | |||
}, | |||
expectedErrors: binding.Errors{ | |||
binding.Error{ | |||
FieldNames: []string{"BranchName"}, | |||
Classification: ErrGitRefName, | |||
Message: "GitRefName", | |||
}, | |||
}, | |||
}, | |||
} | |||
func Test_GitRefNameValidation(t *testing.T) { | |||
AddBindingRules() | |||
for _, testCase := range gitRefNameValidationTestCases { | |||
t.Run(testCase.description, func(t *testing.T) { | |||
performValidationTest(t, testCase) | |||
}) | |||
} | |||
} |
@ -0,0 +1,111 @@ | |||
// Copyright 2017 The Gitea Authors. All rights reserved. | |||
// Use of this source code is governed by a MIT-style | |||
// license that can be found in the LICENSE file. | |||
package validation | |||
import ( | |||
"testing" | |||
"github.com/go-macaron/binding" | |||
) | |||
var urlValidationTestCases = []validationTestCase{ | |||
{ | |||
description: "Empty URL", | |||
data: TestForm{ | |||
URL: "", | |||
}, | |||
expectedErrors: binding.Errors{}, | |||
}, | |||
{ | |||
description: "URL without port", | |||
data: TestForm{ | |||
URL: "http://test.lan/", | |||
}, | |||
expectedErrors: binding.Errors{}, | |||
}, | |||
{ | |||
description: "URL with port", | |||
data: TestForm{ | |||
URL: "http://test.lan:3000/", | |||
}, | |||
expectedErrors: binding.Errors{}, | |||
}, | |||
{ | |||
description: "URL with IPv6 address without port", | |||
data: TestForm{ | |||
URL: "http://[::1]/", | |||
}, | |||
expectedErrors: binding.Errors{}, | |||
}, | |||
{ | |||
description: "URL with IPv6 address with port", | |||
data: TestForm{ | |||
URL: "http://[::1]:3000/", | |||
}, | |||
expectedErrors: binding.Errors{}, | |||
}, | |||
{ | |||
description: "Invalid URL", | |||
data: TestForm{ | |||
URL: "http//test.lan/", | |||
}, | |||
expectedErrors: binding.Errors{ | |||
binding.Error{ | |||
FieldNames: []string{"URL"}, | |||
Classification: binding.ERR_URL, | |||
Message: "Url", | |||
}, | |||
}, | |||
}, | |||
{ | |||
description: "Invalid schema", | |||
data: TestForm{ | |||
URL: "ftp://test.lan/", | |||
}, | |||
expectedErrors: binding.Errors{ | |||
binding.Error{ | |||
FieldNames: []string{"URL"}, | |||
Classification: binding.ERR_URL, | |||
Message: "Url", | |||
}, | |||
}, | |||
}, | |||
{ | |||
description: "Invalid port", | |||
data: TestForm{ | |||
URL: "http://test.lan:3x4/", | |||
}, | |||
expectedErrors: binding.Errors{ | |||
binding.Error{ | |||
FieldNames: []string{"URL"}, | |||
Classification: binding.ERR_URL, | |||
Message: "Url", | |||
}, | |||
}, | |||
}, | |||
{ | |||
description: "Invalid port with IPv6 address", | |||
data: TestForm{ | |||
URL: "http://[::1]:3x4/", | |||
}, | |||
expectedErrors: binding.Errors{ | |||
binding.Error{ | |||
FieldNames: []string{"URL"}, | |||
Classification: binding.ERR_URL, | |||
Message: "Url", | |||
}, | |||
}, | |||
}, | |||
} | |||
func Test_ValidURLValidation(t *testing.T) { | |||
AddBindingRules() | |||
for _, testCase := range urlValidationTestCases { | |||
t.Run(testCase.description, func(t *testing.T) { | |||
performValidationTest(t, testCase) | |||
}) | |||
} | |||
} |