internal/helpers/client_ip_test.go
Rama main · réplica de lectura del repositorio original.
package helpers
import (
"net/http"
"net/netip"
"testing"
)
func requestWith(remoteAddr, xff string) *http.Request {
r := &http.Request{
RemoteAddr: remoteAddr,
Header: http.Header{},
}
if xff != "" {
r.Header.Set("X-Forwarded-For", xff)
}
return r
}
func TestClientIP(t *testing.T) {
tests := []struct {
name string
trustHeaders string
depth string
remoteAddr string
xff string
want string // expected address, or "" for the zero (unresolvable) Addr
}{
{
name: "no proxy trust falls back to RemoteAddr and ignores XFF",
remoteAddr: "203.0.113.9:54321",
xff: "1.1.1.1",
want: "203.0.113.9",
},
{
name: "RemoteAddr without a port still parses",
remoteAddr: "203.0.113.9",
want: "203.0.113.9",
},
{
name: "unparseable RemoteAddr yields the zero Addr",
remoteAddr: "not-an-ip",
want: "",
},
{
name: "single proxy: rightmost XFF entry is the client",
trustHeaders: "true",
remoteAddr: "10.0.0.1:8080",
xff: "203.0.113.7",
want: "203.0.113.7",
},
{
name: "single proxy: client-forged left entry is ignored",
trustHeaders: "true",
remoteAddr: "10.0.0.1:8080",
xff: "1.1.1.1, 203.0.113.7",
want: "203.0.113.7",
},
{
name: "two proxies: client is two hops from the right",
trustHeaders: "true",
depth: "2",
remoteAddr: "10.0.0.1:8080",
xff: "203.0.113.7, 10.0.0.5",
want: "203.0.113.7",
},
{
name: "two proxies: client-forged entries do not shift the result",
trustHeaders: "true",
depth: "2",
remoteAddr: "10.0.0.1:8080",
xff: "1.1.1.1, 203.0.113.7, 10.0.0.5",
want: "203.0.113.7",
},
{
name: "depth larger than the chain falls back to RemoteAddr",
trustHeaders: "true",
depth: "2",
remoteAddr: "10.0.0.1:8080",
xff: "203.0.113.7",
want: "10.0.0.1",
},
{
name: "trust enabled but no XFF header falls back to RemoteAddr",
trustHeaders: "true",
remoteAddr: "10.0.0.1:8080",
want: "10.0.0.1",
},
{
name: "IPv4-mapped IPv6 entry is unmapped",
trustHeaders: "true",
remoteAddr: "10.0.0.1:8080",
xff: "::ffff:203.0.113.7",
want: "203.0.113.7",
},
{
name: "IPv6 client entry is returned as-is",
trustHeaders: "true",
remoteAddr: "10.0.0.1:8080",
xff: "2001:db8::1",
want: "2001:db8::1",
},
{
name: "unparseable target entry falls back to RemoteAddr",
trustHeaders: "true",
remoteAddr: "10.0.0.1:8080",
xff: "garbage",
want: "10.0.0.1",
},
{
name: "invalid depth is clamped to 1 (rightmost)",
trustHeaders: "true",
depth: "abc",
remoteAddr: "10.0.0.1:8080",
xff: "1.1.1.1, 203.0.113.7",
want: "203.0.113.7",
},
{
name: "zero depth is clamped to 1 and does not panic",
trustHeaders: "true",
depth: "0",
remoteAddr: "10.0.0.1:8080",
xff: "1.1.1.1, 203.0.113.7",
want: "203.0.113.7",
},
{
name: "negative depth is clamped to 1",
trustHeaders: "true",
depth: "-3",
remoteAddr: "10.0.0.1:8080",
xff: "1.1.1.1, 203.0.113.7",
want: "203.0.113.7",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// t.Setenv also unsets after the test, so cases without these keys
// see an empty env rather than a value bleeding in from a sibling.
t.Setenv("TRUST_PROXY_HEADERS", tt.trustHeaders)
t.Setenv("TRUST_PROXY_DEPTH", tt.depth)
got := ClientIP(requestWith(tt.remoteAddr, tt.xff))
if tt.want == "" {
if got.IsValid() {
t.Fatalf("expected the zero Addr, got %v", got)
}
return
}
want := netip.MustParseAddr(tt.want)
if got != want {
t.Fatalf("expected %v, got %v", want, got)
}
})
}
}