package httprequest

import (
	"crypto/tls"
	"net/http"
	"net/url"
	"testing"
)

func TestSchemeHost(t *testing.T) {

	testcases := map[string]struct {
		req            *http.Request
		expectedScheme string
		expectedHost   string
	}{
		"X-Forwarded-Host and X-Forwarded-Port combined": {
			req: &http.Request{
				URL:  &url.URL{Path: "/"},
				Host: "127.0.0.1",
				Header: http.Header{
					"X-Forwarded-Host":  []string{"example.com"},
					"X-Forwarded-Port":  []string{"443"},
					"X-Forwarded-Proto": []string{"https"},
				},
			},
			expectedScheme: "https",
			expectedHost:   "example.com",
		},
		"X-Forwarded-Port overwrites X-Forwarded-Host port": {
			req: &http.Request{
				URL:  &url.URL{Path: "/"},
				Host: "127.0.0.1",
				Header: http.Header{
					"X-Forwarded-Host":  []string{"example.com:1234"},
					"X-Forwarded-Port":  []string{"443"},
					"X-Forwarded-Proto": []string{"https"},
				},
			},
			expectedScheme: "https",
			expectedHost:   "example.com:443",
		},
		"X-Forwarded-* multiple attrs": {
			req: &http.Request{
				URL:  &url.URL{Host: "urlhost", Path: "/"},
				Host: "reqhost",
				Header: http.Header{
					"X-Forwarded-Host":  []string{"example.com,foo.com"},
					"X-Forwarded-Port":  []string{"443,123"},
					"X-Forwarded-Proto": []string{"https,http"},
				},
			},
			expectedScheme: "https",
			expectedHost:   "example.com",
		},
		"stripped X-Forwarded-Host and X-Forwarded-Port with non-standard port": {
			req: &http.Request{
				URL:  &url.URL{Path: "/"},
				Host: "127.0.0.1",
				Header: http.Header{
					"X-Forwarded-Host":  []string{"example.com"},
					"X-Forwarded-Port":  []string{"80"},
					"X-Forwarded-Proto": []string{"https"},
				},
			},
			expectedScheme: "https",
			expectedHost:   "example.com:80",
		},
		"detect scheme from X-Forwarded-Port": {
			req: &http.Request{
				URL:  &url.URL{Path: "/"},
				Host: "127.0.0.1",
				Header: http.Header{
					"X-Forwarded-Host": []string{"example.com"},
					"X-Forwarded-Port": []string{"443"},
				},
			},
			expectedScheme: "https",
			expectedHost:   "example.com",
		},

		"req host": {
			req: &http.Request{
				URL:  &url.URL{Host: "urlhost", Path: "/"},
				Host: "example.com",
			},
			expectedScheme: "http",
			expectedHost:   "example.com",
		},
		"req host with port": {
			req: &http.Request{
				URL:  &url.URL{Host: "urlhost", Path: "/"},
				Host: "example.com:80",
			},
			expectedScheme: "http",
			expectedHost:   "example.com:80",
		},
		"req host with tls port": {
			req: &http.Request{
				URL:  &url.URL{Host: "urlhost", Path: "/"},
				Host: "example.com:443",
			},
			expectedScheme: "https",
			expectedHost:   "example.com:443",
		},

		"req tls": {
			req: &http.Request{
				URL:  &url.URL{Path: "/"},
				Host: "example.com",
				TLS:  &tls.ConnectionState{},
			},
			expectedScheme: "https",
			expectedHost:   "example.com",
		},

		"req url": {
			req: &http.Request{
				URL: &url.URL{Scheme: "https", Host: "example.com", Path: "/"},
			},
			expectedScheme: "https",
			expectedHost:   "example.com",
		},
		"req url with port": {
			req: &http.Request{
				URL: &url.URL{Scheme: "https", Host: "example.com:123", Path: "/"},
			},
			expectedScheme: "https",
			expectedHost:   "example.com:123",
		},

		// The following scenarios are captured from actual direct requests to pods
		"non-tls pod": {
			req: &http.Request{
				URL:  &url.URL{Path: "/"},
				Host: "172.17.0.2:9080",
			},
			expectedScheme: "http",
			expectedHost:   "172.17.0.2:9080",
		},
		"tls pod": {
			req: &http.Request{
				URL:  &url.URL{Path: "/"},
				Host: "172.17.0.2:9443",
				TLS:  &tls.ConnectionState{ /* request has non-nil TLS connection state */ },
			},
			expectedScheme: "https",
			expectedHost:   "172.17.0.2:9443",
		},

		// The following scenarios are captured from actual requests to pods via services
		"svc -> non-tls pod": {
			req: &http.Request{
				URL:  &url.URL{Path: "/"},
				Host: "service.default.svc.cluster.local:10080",
			},
			expectedScheme: "http",
			expectedHost:   "service.default.svc.cluster.local:10080",
		},
		"svc -> tls pod": {
			req: &http.Request{
				URL:  &url.URL{Path: "/"},
				Host: "service.default.svc.cluster.local:10443",
				TLS:  &tls.ConnectionState{ /* request has non-nil TLS connection state */ },
			},
			expectedScheme: "https",
			expectedHost:   "service.default.svc.cluster.local:10443",
		},

		// The following scenarios are captured from actual requests to pods via services via routes serviced by haproxy
		"haproxy non-tls route -> svc -> non-tls pod": {
			req: &http.Request{
				URL:  &url.URL{Path: "/"},
				Host: "route-namespace.router.default.svc.cluster.local",
				Header: http.Header{
					"X-Forwarded-Host":  []string{"route-namespace.router.default.svc.cluster.local"},
					"X-Forwarded-Port":  []string{"80"},
					"X-Forwarded-Proto": []string{"http"},
					"Forwarded":         []string{"for=172.18.2.57;host=route-namespace.router.default.svc.cluster.local;proto=http"},
					"X-Forwarded-For":   []string{"172.18.2.57"},
				},
			},
			expectedScheme: "http",
			expectedHost:   "route-namespace.router.default.svc.cluster.local",
		},
		"haproxy edge terminated route -> svc -> non-tls pod": {
			req: &http.Request{
				URL:  &url.URL{Path: "/"},
				Host: "route-namespace.router.default.svc.cluster.local",
				Header: http.Header{
					"X-Forwarded-Host":  []string{"route-namespace.router.default.svc.cluster.local"},
					"X-Forwarded-Port":  []string{"443"},
					"X-Forwarded-Proto": []string{"https"},
					"Forwarded":         []string{"for=172.18.2.57;host=route-namespace.router.default.svc.cluster.local;proto=https"},
					"X-Forwarded-For":   []string{"172.18.2.57"},
				},
			},
			expectedScheme: "https",
			expectedHost:   "route-namespace.router.default.svc.cluster.local",
		},
		"haproxy edge terminated route -> svc -> non-tls pod with the explicit port": {
			req: &http.Request{
				URL:  &url.URL{Path: "/"},
				Host: "route-namespace.router.default.svc.cluster.local:443",
				Header: http.Header{
					"X-Forwarded-Host":  []string{"route-namespace.router.default.svc.cluster.local:443"},
					"X-Forwarded-Port":  []string{"443"},
					"X-Forwarded-Proto": []string{"https"},
					"Forwarded":         []string{"for=172.18.2.57;host=route-namespace.router.default.svc.cluster.local:443;proto=https"},
					"X-Forwarded-For":   []string{"172.18.2.57"},
				},
			},
			expectedScheme: "https",
			expectedHost:   "route-namespace.router.default.svc.cluster.local:443",
		},
		"haproxy passthrough route -> svc -> tls pod": {
			req: &http.Request{
				URL:  &url.URL{Path: "/"},
				Host: "route-namespace.router.default.svc.cluster.local",
				TLS:  &tls.ConnectionState{ /* request has non-nil TLS connection state */ },
			},
			expectedScheme: "https",
			expectedHost:   "route-namespace.router.default.svc.cluster.local",
		},
	}

	for k, tc := range testcases {
		scheme, host := SchemeHost(tc.req)
		if scheme != tc.expectedScheme {
			t.Errorf("%s: expected scheme %q, got %q", k, tc.expectedScheme, scheme)
		}
		if host != tc.expectedHost {
			t.Errorf("%s: expected host %q, got %q", k, tc.expectedHost, host)
		}
	}
}
