summaryrefslogtreecommitdiffstats
path: root/modules
diff options
context:
space:
mode:
authorLauris BH <lauris@nix.lv>2018-08-15 09:29:37 +0300
committerGitHub <noreply@github.com>2018-08-15 09:29:37 +0300
commit92466129ec242536c71b66a8987d9b37e6bc0bce (patch)
treeb9ac6959ef6365a6215868cba4083f53b74fa094 /modules
parent0449330dbce812e67f3309c11e265eb6a5bc0c7e (diff)
downloadgitea-92466129ec242536c71b66a8987d9b37e6bc0bce.tar.gz
gitea-92466129ec242536c71b66a8987d9b37e6bc0bce.zip
Improve URL validation for external wiki and external issues (#4710)
* Improve URL validation for external wiki and external issues * Do not allow also localhost address for external URLs
Diffstat (limited to 'modules')
-rw-r--r--modules/validation/binding.go11
-rw-r--r--modules/validation/helpers.go77
-rw-r--r--modules/validation/helpers_test.go90
3 files changed, 170 insertions, 8 deletions
diff --git a/modules/validation/binding.go b/modules/validation/binding.go
index 7aaed59c12..bf3e6c4f92 100644
--- a/modules/validation/binding.go
+++ b/modules/validation/binding.go
@@ -6,7 +6,6 @@ package validation
import (
"fmt"
- "net/url"
"regexp"
"strings"
@@ -70,13 +69,9 @@ func addValidURLBindingRule() {
},
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
- }
+ if len(str) != 0 && !IsValidURL(str) {
+ errs.Add([]string{name}, binding.ERR_URL, "Url")
+ return false, errs
}
return true, errs
diff --git a/modules/validation/helpers.go b/modules/validation/helpers.go
new file mode 100644
index 0000000000..9a4dfab7a4
--- /dev/null
+++ b/modules/validation/helpers.go
@@ -0,0 +1,77 @@
+// Copyright 2018 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 (
+ "net"
+ "net/url"
+ "strings"
+
+ "code.gitea.io/gitea/modules/setting"
+)
+
+var loopbackIPBlocks []*net.IPNet
+
+func init() {
+ for _, cidr := range []string{
+ "127.0.0.0/8", // IPv4 loopback
+ "::1/128", // IPv6 loopback
+ } {
+ if _, block, err := net.ParseCIDR(cidr); err == nil {
+ loopbackIPBlocks = append(loopbackIPBlocks, block)
+ }
+ }
+}
+
+func isLoopbackIP(ip string) bool {
+ pip := net.ParseIP(ip)
+ if pip == nil {
+ return false
+ }
+ for _, block := range loopbackIPBlocks {
+ if block.Contains(pip) {
+ return true
+ }
+ }
+ return false
+}
+
+// IsValidURL checks if URL is valid
+func IsValidURL(uri string) bool {
+ if u, err := url.ParseRequestURI(uri); err != nil ||
+ (u.Scheme != "http" && u.Scheme != "https") ||
+ !validPort(portOnly(u.Host)) {
+ return false
+ }
+
+ return true
+}
+
+// IsAPIURL checks if URL is current Gitea instance API URL
+func IsAPIURL(uri string) bool {
+ return strings.HasPrefix(strings.ToLower(uri), strings.ToLower(setting.AppURL+"api"))
+}
+
+// IsValidExternalURL checks if URL is valid external URL
+func IsValidExternalURL(uri string) bool {
+ if !IsValidURL(uri) || IsAPIURL(uri) {
+ return false
+ }
+
+ u, err := url.ParseRequestURI(uri)
+ if err != nil {
+ return false
+ }
+
+ // Currently check only if not loopback IP is provided to keep compatibility
+ if isLoopbackIP(u.Hostname()) || strings.ToLower(u.Hostname()) == "localhost" {
+ return false
+ }
+
+ // TODO: Later it should be added to allow local network IP addreses
+ // only if allowed by special setting
+
+ return true
+}
diff --git a/modules/validation/helpers_test.go b/modules/validation/helpers_test.go
new file mode 100644
index 0000000000..875625a02c
--- /dev/null
+++ b/modules/validation/helpers_test.go
@@ -0,0 +1,90 @@
+// Copyright 2018 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/stretchr/testify/assert"
+
+ "code.gitea.io/gitea/modules/setting"
+)
+
+func Test_IsValidURL(t *testing.T) {
+ cases := []struct {
+ description string
+ url string
+ valid bool
+ }{
+ {
+ description: "Empty URL",
+ url: "",
+ valid: false,
+ },
+ {
+ description: "Loobpack IPv4 URL",
+ url: "http://127.0.1.1:5678/",
+ valid: true,
+ },
+ {
+ description: "Loobpack IPv6 URL",
+ url: "https://[::1]/",
+ valid: true,
+ },
+ {
+ description: "Missing semicolon after schema",
+ url: "http//meh/",
+ valid: false,
+ },
+ }
+
+ for _, testCase := range cases {
+ t.Run(testCase.description, func(t *testing.T) {
+ assert.Equal(t, testCase.valid, IsValidURL(testCase.url))
+ })
+ }
+}
+
+func Test_IsValidExternalURL(t *testing.T) {
+ setting.AppURL = "https://try.gitea.io/"
+
+ cases := []struct {
+ description string
+ url string
+ valid bool
+ }{
+ {
+ description: "Current instance URL",
+ url: "https://try.gitea.io/test",
+ valid: true,
+ },
+ {
+ description: "Loobpack IPv4 URL",
+ url: "http://127.0.1.1:5678/",
+ valid: false,
+ },
+ {
+ description: "Current instance API URL",
+ url: "https://try.gitea.io/api/v1/user/follow",
+ valid: false,
+ },
+ {
+ description: "Local network URL",
+ url: "http://192.168.1.2/api/v1/user/follow",
+ valid: true,
+ },
+ {
+ description: "Local URL",
+ url: "http://LOCALHOST:1234/whatever",
+ valid: false,
+ },
+ }
+
+ for _, testCase := range cases {
+ t.Run(testCase.description, func(t *testing.T) {
+ assert.Equal(t, testCase.valid, IsValidExternalURL(testCase.url))
+ })
+ }
+}