* Improve URL validation for external wiki and external issues * Do not allow also localhost address for external URLsfor-closed-social
@ -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 | |||
} |
@ -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)) | |||
}) | |||
} | |||
} |