Path: blob/dev/pkg/protocols/http/httpclientpool/clientpool_test.go
4538 views
package httpclientpool12import (3"net/http"4"net/url"5"testing"6)78func TestNormalizeHost(t *testing.T) {9tests := []struct {10name string11scheme string12host string13want string14}{15// No port16{"http no port", "http", "example.com", "example.com"},17{"https no port", "https", "example.com", "example.com"},1819// Default ports stripped20{"http default port 80", "http", "example.com:80", "example.com"},21{"https default port 443", "https", "example.com:443", "example.com"},2223// Non-default ports preserved24{"http non-default port", "http", "example.com:8080", "example.com:8080"},25{"https non-default port", "https", "example.com:8443", "example.com:8443"},2627// Cross-scheme default ports preserved (443 is not default for http)28{"http with port 443", "http", "example.com:443", "example.com:443"},29{"https with port 80", "https", "example.com:80", "example.com:80"},3031// IP addresses32{"ipv4 no port", "http", "127.0.0.1", "127.0.0.1"},33{"ipv4 default port", "http", "127.0.0.1:80", "127.0.0.1"},34{"ipv4 non-default port", "http", "127.0.0.1:8080", "127.0.0.1:8080"},35{"ipv4 https default port", "https", "10.0.0.1:443", "10.0.0.1"},3637// IPv6 addresses38{"ipv6 no port", "http", "[::1]", "[::1]"},39{"ipv6 default port http", "http", "[::1]:80", "[::1]"},40{"ipv6 default port https", "https", "[::1]:443", "[::1]"},41{"ipv6 non-default port", "https", "[::1]:8443", "[::1]:8443"},42{"ipv6 full address default port", "http", "[2001:db8::1]:80", "[2001:db8::1]"},43{"ipv6 full address non-default", "http", "[2001:db8::1]:9090", "[2001:db8::1]:9090"},4445// Empty host46{"empty host", "http", "", ""},47}48for _, tt := range tests {49t.Run(tt.name, func(t *testing.T) {50u := &url.URL{Scheme: tt.scheme, Host: tt.host}51got := normalizeHost(u)52if got != tt.want {53t.Errorf("normalizeHost(%s://%s) = %q, want %q", tt.scheme, tt.host, got, tt.want)54}55})56}57}5859func TestFollowSameHostRedirectWithPort(t *testing.T) {60tests := []struct {61name string62oldURL string63newURL string64shouldAllow bool65}{66// Basic same host67{"same host no port", "http://example.com/a", "http://example.com/b", true},68{"same host same path", "http://example.com/a", "http://example.com/a", true},6970// Default port normalization (the bug from #4685)71{"old has :80 new does not", "http://example.com:80/a", "http://example.com/b", true},72{"new has :80 old does not", "http://example.com/a", "http://example.com:80/b", true},73{"both have :80", "http://example.com:80/a", "http://example.com:80/b", true},74{"old has :443 new does not https", "https://example.com:443/a", "https://example.com/b", true},75{"new has :443 old does not https", "https://example.com/a", "https://example.com:443/b", true},76{"both have :443 https", "https://example.com:443/a", "https://example.com:443/b", true},7778// Relative redirect (host carried from original request)79{"relative redirect same host", "http://example.com/a", "http://example.com/target", true},8081// IP address with default port82{"ip old has :80 new does not", "http://127.0.0.1:80/a", "http://127.0.0.1/b", true},83{"ip new has :80 old does not", "http://127.0.0.1/a", "http://127.0.0.1:80/b", true},84{"ip both no port", "http://127.0.0.1/a", "http://127.0.0.1/b", true},8586// Different hosts should be blocked87{"different host", "http://example.com/a", "http://other.com/b", false},88{"different host with port", "http://example.com:80/a", "http://other.com:80/b", false},89{"different ip", "http://127.0.0.1/a", "http://127.0.0.2/b", false},9091// Non-default ports92{"same non-default port", "http://example.com:8080/a", "http://example.com:8080/b", true},93{"different non-default port", "http://example.com:8080/a", "http://example.com:9090/b", false},94{"non-default vs no port", "http://example.com:8080/a", "http://example.com/b", false},95{"no port vs non-default", "http://example.com/a", "http://example.com:8080/b", false},9697// Cross-scheme port (443 on http is non-default, should not strip)98{"http port 443 vs no port", "http://example.com:443/a", "http://example.com/b", false},99{"https port 80 vs no port", "https://example.com:80/a", "https://example.com/b", false},100101// IPv6102{"ipv6 same host", "http://[::1]/a", "http://[::1]/b", true},103{"ipv6 default port normalization", "http://[::1]:80/a", "http://[::1]/b", true},104}105for _, tt := range tests {106t.Run(tt.name, func(t *testing.T) {107checkFn := makeCheckRedirectFunc(FollowSameHostRedirect, 10)108oldReq, _ := http.NewRequest("GET", tt.oldURL, nil)109newReq, _ := http.NewRequest("GET", tt.newURL, nil)110err := checkFn(newReq, []*http.Request{oldReq})111allowed := err == nil112if allowed != tt.shouldAllow {113t.Errorf("redirect from %q to %q: allowed=%v, want %v", tt.oldURL, tt.newURL, allowed, tt.shouldAllow)114}115})116}117}118119func TestFollowSameHostRedirectViaHost(t *testing.T) {120// When via[0].Host is set (e.g. Host header override), it takes precedence over URL.Host121checkFn := makeCheckRedirectFunc(FollowSameHostRedirect, 10)122123oldReq, _ := http.NewRequest("GET", "http://proxy.internal/a", nil)124oldReq.Host = "example.com"125newReq, _ := http.NewRequest("GET", "http://example.com/b", nil)126127err := checkFn(newReq, []*http.Request{oldReq})128if err != nil {129t.Errorf("redirect should be allowed when via[0].Host matches new host, got err: %v", err)130}131132// Mismatch: via[0].Host differs from redirect target133oldReq2, _ := http.NewRequest("GET", "http://proxy.internal/a", nil)134oldReq2.Host = "other.com"135newReq2, _ := http.NewRequest("GET", "http://example.com/b", nil)136137err = checkFn(newReq2, []*http.Request{oldReq2})138if err == nil {139t.Errorf("redirect should be blocked when via[0].Host differs from new host")140}141}142143func TestDontFollowRedirect(t *testing.T) {144checkFn := makeCheckRedirectFunc(DontFollowRedirect, 10)145oldReq, _ := http.NewRequest("GET", "http://example.com/a", nil)146newReq, _ := http.NewRequest("GET", "http://example.com/b", nil)147148err := checkFn(newReq, []*http.Request{oldReq})149if err != http.ErrUseLastResponse {150t.Errorf("DontFollowRedirect should always return ErrUseLastResponse, got: %v", err)151}152}153154func TestFollowAllRedirect(t *testing.T) {155checkFn := makeCheckRedirectFunc(FollowAllRedirect, 10)156157// Same host158oldReq, _ := http.NewRequest("GET", "http://example.com/a", nil)159newReq, _ := http.NewRequest("GET", "http://example.com/b", nil)160if err := checkFn(newReq, []*http.Request{oldReq}); err != nil {161t.Errorf("FollowAllRedirect should allow same host redirect, got: %v", err)162}163164// Different host165oldReq2, _ := http.NewRequest("GET", "http://example.com/a", nil)166newReq2, _ := http.NewRequest("GET", "http://other.com/b", nil)167if err := checkFn(newReq2, []*http.Request{oldReq2}); err != nil {168t.Errorf("FollowAllRedirect should allow cross-host redirect, got: %v", err)169}170}171172func TestMaxRedirects(t *testing.T) {173// Exceeding explicit max174checkFn := makeCheckRedirectFunc(FollowAllRedirect, 2)175req, _ := http.NewRequest("GET", "http://example.com/c", nil)176via := make([]*http.Request, 3)177for i := range via {178via[i], _ = http.NewRequest("GET", "http://example.com/"+string(rune('a'+i)), nil)179}180if err := checkFn(req, via); err == nil {181t.Errorf("should block after exceeding maxRedirects=2 with 3 via requests")182}183184// Within explicit max185checkFn2 := makeCheckRedirectFunc(FollowAllRedirect, 5)186via2 := make([]*http.Request, 3)187for i := range via2 {188via2[i], _ = http.NewRequest("GET", "http://example.com/"+string(rune('a'+i)), nil)189}190if err := checkFn2(req, via2); err != nil {191t.Errorf("should allow within maxRedirects=5 with 3 via requests, got: %v", err)192}193194// maxRedirects=0 uses default (10)195checkFn3 := makeCheckRedirectFunc(FollowAllRedirect, 0)196via3 := make([]*http.Request, 11)197for i := range via3 {198via3[i], _ = http.NewRequest("GET", "http://example.com/x", nil)199}200if err := checkFn3(req, via3); err == nil {201t.Errorf("should block after exceeding default maxRedirects (10) with 11 via requests")202}203204via4 := make([]*http.Request, 9)205for i := range via4 {206via4[i], _ = http.NewRequest("GET", "http://example.com/x", nil)207}208if err := checkFn3(req, via4); err != nil {209t.Errorf("should allow within default maxRedirects (10) with 9 via requests, got: %v", err)210}211}212213214