mirror of
https://github.com/go-shiori/shiori.git
synced 2024-11-14 05:05:27 +08:00
dde1b44e77
* fix(real_ip): get user real ip from headers of request * fix(real_ip): compatible with those header with multiple IP values separated by commas * test(real_ip): add benchmark for IPv4 and IPv6 private address check * fix(real_ip): check empty, then remove leading and tailing comma char, finally locate first IP field * test(real_ip): move checker logic into utils and add more unit test cases * test(real_ip): write unit tests covering all code branches of the `util-ip` module * refactor(real_ip): use one-line `testify.assert.Panics` to capture intended panic in test case * chore(real_ip): add module private variable `UserRealIpHeaderCandidates` put those headers together, make it easy to manage in one place * doc(real_ip): write docstring for each function in the `utils-ip` module * chore(real_ip): choose more concrete and unambiguous name for test helper function It is to avoid polluting the module name-space with too general names. * chore(naming): change function names according to code style * refactor(real_ip): simplify the code indicated by 'gosimple' and `golangci` * chore(naming): rename the `utils-ip` file to `utils_ip` to match with the rest of the file structure --------- Co-authored-by: Felipe Martin <812088+fmartingr@users.noreply.github.com>
229 lines
7.2 KiB
Go
229 lines
7.2 KiB
Go
package webserver
|
|
|
|
import (
|
|
"fmt"
|
|
"math/rand"
|
|
"net"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
func TestParseCidr(t *testing.T) {
|
|
res := parseCIDR("192.168.0.0/16", "internal 192.168.x.x")
|
|
assert.Equal(t, res.IP, net.IP([]byte{192, 168, 0, 0}))
|
|
assert.Equal(t, res.Mask, net.IPMask([]byte{255, 255, 0, 0}))
|
|
}
|
|
|
|
func TestParseCidrInvalidAddr(t *testing.T) {
|
|
assert.Panics(t, func() { parseCIDR("192.168.0.0/34", "internal 192.168.x.x") })
|
|
}
|
|
|
|
func TestIsPrivateIP(t *testing.T) {
|
|
assert.True(t, IsPrivateIP(net.ParseIP("127.0.0.1")), "should be private")
|
|
assert.True(t, IsPrivateIP(net.ParseIP("192.168.254.254")), "should be private")
|
|
assert.True(t, IsPrivateIP(net.ParseIP("10.255.0.3")), "should be private")
|
|
assert.True(t, IsPrivateIP(net.ParseIP("172.16.255.255")), "should be private")
|
|
assert.True(t, IsPrivateIP(net.ParseIP("172.31.255.255")), "should be private")
|
|
assert.True(t, !IsPrivateIP(net.ParseIP("128.0.0.1")), "should be private")
|
|
assert.True(t, !IsPrivateIP(net.ParseIP("192.169.255.255")), "should not be private")
|
|
assert.True(t, !IsPrivateIP(net.ParseIP("9.255.0.255")), "should not be private")
|
|
assert.True(t, !IsPrivateIP(net.ParseIP("172.32.255.255")), "should not be private")
|
|
|
|
assert.True(t, IsPrivateIP(net.ParseIP("::0")), "should be private")
|
|
assert.True(t, IsPrivateIP(net.ParseIP("::1")), "should be private")
|
|
assert.True(t, !IsPrivateIP(net.ParseIP("::2")), "should not be private")
|
|
|
|
assert.True(t, IsPrivateIP(net.ParseIP("fe80::1")), "should be private")
|
|
assert.True(t, IsPrivateIP(net.ParseIP("febf::1")), "should be private")
|
|
assert.True(t, !IsPrivateIP(net.ParseIP("fec0::1")), "should not be private")
|
|
assert.True(t, !IsPrivateIP(net.ParseIP("feff::1")), "should not be private")
|
|
|
|
assert.True(t, IsPrivateIP(net.ParseIP("ff00::1")), "should be private")
|
|
assert.True(t, IsPrivateIP(net.ParseIP("ff10::1")), "should be private")
|
|
assert.True(t, IsPrivateIP(net.ParseIP("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")), "should be private")
|
|
|
|
assert.True(t, IsPrivateIP(net.ParseIP("2002::")), "should be private")
|
|
assert.True(t, IsPrivateIP(net.ParseIP("2002:ffff:ffff:ffff:ffff:ffff:ffff:ffff")), "should be private")
|
|
assert.True(t, IsPrivateIP(net.ParseIP("0100::")), "should be private")
|
|
assert.True(t, IsPrivateIP(net.ParseIP("0100::0000:ffff:ffff:ffff:ffff")), "should be private")
|
|
assert.True(t, !IsPrivateIP(net.ParseIP("0100::0001:0000:0000:0000:0000")), "should be private")
|
|
}
|
|
|
|
func TestIsIpValidAndPublic(t *testing.T) {
|
|
// test empty address
|
|
assert.False(t, IsIPValidAndPublic(""))
|
|
// test public address
|
|
assert.True(t, IsIPValidAndPublic("31.41.244.124"))
|
|
assert.True(t, IsIPValidAndPublic("62.233.50.248"))
|
|
// trim head or tail space
|
|
assert.True(t, IsIPValidAndPublic(" 62.233.50.249"))
|
|
assert.True(t, IsIPValidAndPublic(" 62.233.50.250 "))
|
|
assert.True(t, IsIPValidAndPublic("62.233.50.251 "))
|
|
// test private address
|
|
assert.False(t, IsIPValidAndPublic("10.1.123.52"))
|
|
assert.False(t, IsIPValidAndPublic("192.168.123.24"))
|
|
assert.False(t, IsIPValidAndPublic("172.17.0.1"))
|
|
}
|
|
|
|
func BenchmarkIsPrivateIPv4(b *testing.B) {
|
|
// range: 2-254
|
|
n1 := 2 + rand.Intn(252)
|
|
n2 := 2 + rand.Intn(252)
|
|
for i := 0; i < b.N; i++ {
|
|
IsPrivateIP(net.ParseIP(fmt.Sprintf("192.168.%d.%d", n1, n2)))
|
|
}
|
|
}
|
|
|
|
func BenchmarkIsPrivateIPv6(b *testing.B) {
|
|
n1 := 2 + rand.Intn(252)
|
|
for i := 0; i < b.N; i++ {
|
|
IsPrivateIP(net.ParseIP(fmt.Sprintf("2002::%d", n1)))
|
|
}
|
|
}
|
|
|
|
func testIsPublicHttpRequestAddressHelper(
|
|
t *testing.T, wantIP string, headers map[string]string, isPublic bool,
|
|
) {
|
|
testIsPublicHttpRequestAddressHelperWrapped(t, nil, wantIP, headers, isPublic)
|
|
}
|
|
|
|
func testIsPublicHttpRequestAddressHelperWrapped(
|
|
t *testing.T, r *http.Request, wantIP string, headers map[string]string, isPublic bool,
|
|
) {
|
|
var (
|
|
err error
|
|
userIP string
|
|
)
|
|
if r == nil {
|
|
r = httptest.NewRequest("GET", "/", nil)
|
|
}
|
|
for k, v := range headers {
|
|
r.Header.Set(k, v)
|
|
}
|
|
|
|
origVal := GetUserRealIP(r)
|
|
if strings.Contains(origVal, ":") {
|
|
userIP, _, err = net.SplitHostPort(origVal)
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
} else {
|
|
userIP = origVal
|
|
}
|
|
|
|
if isPublic {
|
|
// should equal first ip in list
|
|
assert.Equal(t, wantIP, userIP)
|
|
assert.True(t, IsIPValidAndPublic(userIP))
|
|
} else {
|
|
assert.Equal(t, origVal, r.RemoteAddr)
|
|
assert.False(t, IsIPValidAndPublic(userIP))
|
|
}
|
|
}
|
|
|
|
func TestGetUserRealIPWithSetRemoteAddr(t *testing.T) {
|
|
// Test Public RemoteAddr
|
|
testIsPublicHttpRequestAddressHelper(t, "", nil, false)
|
|
|
|
r := httptest.NewRequest("GET", "/", nil)
|
|
wantIP := "34.23.123.122"
|
|
r.RemoteAddr = fmt.Sprintf("%s:1234", wantIP)
|
|
testIsPublicHttpRequestAddressHelperWrapped(t, r, wantIP, nil, true)
|
|
}
|
|
|
|
func TestGetUserRealIPWithInvalidRemoteAddr(t *testing.T) {
|
|
// Test Public RemoteAddr
|
|
testIsPublicHttpRequestAddressHelper(t, "", nil, false)
|
|
|
|
r := httptest.NewRequest("GET", "/", nil)
|
|
wantIP := "34.23.123.122"
|
|
// without port
|
|
r.RemoteAddr = wantIP
|
|
testIsPublicHttpRequestAddressHelperWrapped(t, r, wantIP, nil, true)
|
|
}
|
|
|
|
func TestGetUserRealIPWithEmptyHeader(t *testing.T) {
|
|
// Test Empty X-Real-IP
|
|
testIsPublicHttpRequestAddressHelper(t, "", nil, false)
|
|
}
|
|
|
|
func TestGetUserRealIPWithInvalidHeaderValue(t *testing.T) {
|
|
for _, name := range userRealIpHeaderCandidates {
|
|
// invalid ip
|
|
m := map[string]string{
|
|
name: "31.41.24a.12",
|
|
}
|
|
testIsPublicHttpRequestAddressHelper(t, "", m, false)
|
|
}
|
|
}
|
|
|
|
func TestGetUserRealIPWithXRealIpHeader(t *testing.T) {
|
|
// Test public Real IP
|
|
for _, name := range userRealIpHeaderCandidates {
|
|
wantIP := "31.41.242.12"
|
|
m := map[string]string{
|
|
name: wantIP,
|
|
}
|
|
testIsPublicHttpRequestAddressHelper(t, wantIP, m, true)
|
|
}
|
|
}
|
|
|
|
func TestGetUserRealIPWithPrivateXRealIpHeader(t *testing.T) {
|
|
for _, name := range userRealIpHeaderCandidates {
|
|
wantIP := "192.168.123.123"
|
|
// test private ip in header
|
|
m := map[string]string{
|
|
name: wantIP,
|
|
}
|
|
testIsPublicHttpRequestAddressHelper(t, wantIP, m, false)
|
|
}
|
|
}
|
|
|
|
func TestGetUserRealIPWithXRealIpListHeader(t *testing.T) {
|
|
// Test Real IP List
|
|
for _, name := range userRealIpHeaderCandidates {
|
|
ipList := []string{"34.23.123.122", "34.23.123.123"}
|
|
// should equal first ip in list
|
|
wantIP := ipList[0]
|
|
// test private ip in header
|
|
m := map[string]string{
|
|
name: strings.Join(ipList, ", "),
|
|
}
|
|
testIsPublicHttpRequestAddressHelper(t, wantIP, m, true)
|
|
}
|
|
}
|
|
|
|
func TestGetUserRealIPWithXRealIpHeaderIgnoreComma(t *testing.T) {
|
|
// Test Real IP List with leading or tailing comma
|
|
wantIP := "34.23.123.124"
|
|
ipVariants := []string{
|
|
",34.23.123.124", " ,34.23.123.124", "\t,34.23.123.124",
|
|
",34.23.123.124,", " ,34.23.123.124, ", "\t,34.23.123.124,\t",
|
|
"34.23.123.124,", "34.23.123.124, ", "34.23.123.124,\t"}
|
|
for _, variant := range ipVariants {
|
|
for _, name := range userRealIpHeaderCandidates {
|
|
m := map[string]string{name: variant}
|
|
testIsPublicHttpRequestAddressHelper(t, wantIP, m, true)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestGetUserRealIPWithDifferentHeaderOrder(t *testing.T) {
|
|
var m map[string]string
|
|
wantIP := "34.23.123.124"
|
|
m = map[string]string{
|
|
"X-Real-Ip": "192.168.123.122",
|
|
"X-Forwarded-For": wantIP,
|
|
}
|
|
|
|
testIsPublicHttpRequestAddressHelper(t, wantIP, m, true)
|
|
m = map[string]string{
|
|
"X-Real-Ip": wantIP,
|
|
"X-Forwarded-For": "192.168.123.122",
|
|
}
|
|
testIsPublicHttpRequestAddressHelper(t, wantIP, m, true)
|
|
}
|