Ctrl K
ComunidadGuías…

internal/forwarded/forwarded_test.go

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

Archivo original ↗Preguntar a KiBot
package forwarded

import (
	"crypto/tls"
	"net/http"
	"net/http/httptest"
	"net/netip"
	"testing"
)

func mustParse(t *testing.T, list string) Trusted {
	t.Helper()
	tr, err := Parse(list)
	if err != nil {
		t.Fatal(err)
	}
	return tr
}

func TestParse(t *testing.T) {
	for _, ok := range []string{"", " , ", "127.0.0.1", "127.0.0.1, ::1", "10.0.0.0/8,fd00::/8", " 192.168.1.5/24 ", "::ffff:10.0.0.1"} {
		if _, err := Parse(ok); err != nil {
			t.Errorf("Parse(%q) = %v", ok, err)
		}
	}
	for _, bad := range []string{"localhost", "127.0.0.1,proxy", "10.0.0.0/33", "300.1.1.1", "127.0.0.1:80", "fe80::1%eth0"} {
		if _, err := Parse(bad); err == nil {
			t.Errorf("Parse(%q) accepted it", bad)
		}
	}
	if !mustParse(t, "").Empty() || mustParse(t, "::1").Empty() {
		t.Error("Empty is wrong")
	}
	// A range given with host bits set still means the whole range.
	if tr := mustParse(t, "192.168.1.5/24"); !tr.contains(mustAddr(t, "192.168.1.200")) {
		t.Error("192.168.1.5/24 does not contain 192.168.1.200")
	}
}

func req(peer string, xff ...string) *http.Request {
	r := httptest.NewRequest(http.MethodGet, "/", nil)
	r.RemoteAddr = peer
	for _, v := range xff {
		r.Header.Add("X-Forwarded-For", v)
	}
	return r
}

func TestClient(t *testing.T) {
	tr := mustParse(t, "127.0.0.1,::1,10.0.0.0/8")
	cases := []struct {
		name string
		r    *http.Request
		want string
	}{
		{"untrusted peer, forged header", req("192.168.178.20:5555", "6.6.6.6"), "192.168.178.20"},
		{"untrusted peer, no header", req("192.168.178.20:5555"), "192.168.178.20"},
		{"trusted peer, one hop", req("127.0.0.1:40000", "203.0.113.7"), "203.0.113.7"},
		{"client wrote the leftmost entry", req("127.0.0.1:40000", "6.6.6.6, 203.0.113.7"), "203.0.113.7"},
		{"several trusted hops", req("127.0.0.1:40000", "6.6.6.6, 203.0.113.7, 10.1.2.3"), "203.0.113.7"},
		{"several header lines", req("127.0.0.1:40000", "6.6.6.6", "203.0.113.7", "10.1.2.3"), "203.0.113.7"},
		{"every hop trusted", req("127.0.0.1:40000", "10.0.0.5, 127.0.0.1"), "127.0.0.1"},
		{"trusted peer, no header", req("127.0.0.1:40000"), "127.0.0.1"},
		{"garbage", req("127.0.0.1:40000", "not-an-ip"), "127.0.0.1"},
		{"garbage left of the client is never reached", req("127.0.0.1:40000", "junk, 203.0.113.7"), "203.0.113.7"},
		{"IPv6 peer and client", req("[::1]:40000", "2001:db8::7"), "2001:db8::7"},
		{"bracketed entry with port", req("[::1]:40000", "[2001:db8::7]:1234"), "2001:db8::7"},
		{"IPv4 entry with port", req("127.0.0.1:40000", "203.0.113.7:1234"), "203.0.113.7"},
		{"IPv4-mapped peer", req("[::ffff:127.0.0.1]:40000", "203.0.113.7"), "203.0.113.7"},
	}
	for _, c := range cases {
		if got := tr.Client(c.r); got != c.want {
			t.Errorf("%s: Client = %q, want %q", c.name, got, c.want)
		}
	}
	if got := (Trusted{}).Client(req("127.0.0.1:40000", "203.0.113.7")); got != "127.0.0.1" {
		t.Errorf("nobody trusted: Client = %q, want the peer", got)
	}
}

func TestScheme(t *testing.T) {
	tr := mustParse(t, "127.0.0.1")
	proto := func(peer string, tlsOn bool, values ...string) string {
		r := req(peer)
		for _, v := range values {
			r.Header.Add("X-Forwarded-Proto", v)
		}
		if tlsOn {
			r.TLS = &tls.ConnectionState{}
		}
		return tr.Scheme(r)
	}
	cases := []struct {
		name, got, want string
	}{
		{"trusted https", proto("127.0.0.1:1", false, "https"), "https"},
		{"trusted http over TLS", proto("127.0.0.1:1", true, "http"), "http"},
		{"untrusted https ignored", proto("192.168.1.2:1", false, "https"), "http"},
		{"untrusted, TLS", proto("192.168.1.2:1", true, "http"), "https"},
		{"unknown value ignored", proto("127.0.0.1:1", false, "HTTPS"), "http"},
		{"garbage ignored", proto("127.0.0.1:1", true, "javascript"), "https"},
		{"last value wins", proto("127.0.0.1:1", false, "http", "https"), "https"},
		{"last in a list wins", proto("127.0.0.1:1", false, "http, https"), "https"},
		{"no header", proto("127.0.0.1:1", false), "http"},
	}
	for _, c := range cases {
		if c.got != c.want {
			t.Errorf("%s: Scheme = %q, want %q", c.name, c.got, c.want)
		}
	}
}

func mustAddr(t *testing.T, s string) netip.Addr {
	t.Helper()
	a, err := netip.ParseAddr(s)
	if err != nil {
		t.Fatal(err)
	}
	return a
}