Ctrl K
ComunidadGuías…

internal/helpers/client_ip_test.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
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)
			}
		})
	}
}