package staticsite import ( "context" "errors" "net" "net/http" "strings" "testing" "time" ) func TestValidateBaseURL(t *testing.T) { cases := []struct { name string input string wantError bool }{ {"https", "https://git.example.com", false}, {"http", "http://git.example.com", false}, {"trailing_slash", "https://git.example.com/", false}, {"with_path", "https://git.example.com/sub", false}, {"with_port", "https://git.example.com:8080", false}, {"empty", "", true}, {"whitespace_only", " ", true}, {"ftp_scheme", "ftp://git.example.com", true}, {"file_scheme", "file:///etc/passwd", true}, {"no_scheme", "git.example.com", true}, {"scheme_no_host", "https://", true}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { err := ValidateBaseURL(tc.input) if tc.wantError && err == nil { t.Errorf("ValidateBaseURL(%q) = nil, want error", tc.input) } if !tc.wantError && err != nil { t.Errorf("ValidateBaseURL(%q) = %v, want nil", tc.input, err) } }) } } func TestBlockReason_PolicyMatrix(t *testing.T) { cases := []struct { name string ip string wantBlocked bool }{ // Allowed. {"public_v4", "8.8.8.8", false}, {"rfc1918_10", "10.0.0.1", false}, {"rfc1918_172_16", "172.16.0.1", false}, {"rfc1918_192_168", "192.168.1.1", false}, {"public_v6", "2606:4700:4700::1111", false}, {"ula_v6", "fd00::1", false}, // ULA private — allowed, mirrors RFC1918 // Blocked. {"loopback_v4", "127.0.0.1", true}, {"loopback_v6", "::1", true}, {"unspecified_v4", "0.0.0.0", true}, {"unspecified_v6", "::", true}, {"link_local_v4_metadata", "169.254.169.254", true}, // AWS/GCP metadata {"link_local_v6", "fe80::1", true}, {"multicast_v4", "224.0.0.1", true}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { ip := net.ParseIP(tc.ip) if ip == nil { t.Fatalf("parse %q", tc.ip) } got := blockReason(ip) blocked := got != "" if blocked != tc.wantBlocked { t.Errorf("blockReason(%s) = %q (blocked=%v), want blocked=%v", tc.ip, got, blocked, tc.wantBlocked) } }) } } // TestSafeHTTPClient_RejectsLoopbackLiteral exercises the actual dial // path: a request to a loopback literal must fail before any TCP work // happens, with ErrBlockedAddress in the chain. func TestSafeHTTPClient_RejectsLoopbackLiteral(t *testing.T) { client := NewSafeHTTPClient(2 * time.Second) req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "http://127.0.0.1:1/", nil) if err != nil { t.Fatalf("new request: %v", err) } _, err = client.Do(req) if err == nil { t.Fatal("expected error, got nil") } if !errors.Is(err, ErrBlockedAddress) && !strings.Contains(err.Error(), "blocked") { t.Errorf("err = %v, expected ErrBlockedAddress in chain or 'blocked' in message", err) } } // TestSafeHTTPClient_RejectsAWSMetadataLiteral mirrors the loopback // case but for the AWS/GCP cloud metadata IP (link-local). func TestSafeHTTPClient_RejectsAWSMetadataLiteral(t *testing.T) { client := NewSafeHTTPClient(2 * time.Second) req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "http://169.254.169.254/latest/meta-data/", nil) if err != nil { t.Fatalf("new request: %v", err) } _, err = client.Do(req) if err == nil { t.Fatal("expected error, got nil") } if !errors.Is(err, ErrBlockedAddress) && !strings.Contains(err.Error(), "blocked") { t.Errorf("err = %v, expected ErrBlockedAddress in chain or 'blocked' in message", err) } }