Ctrl K
ComunidadGuías…

internal/server/strip_response_headers_form_test.go

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

Archivo original ↗Preguntar a KiBot
// SPDX-License-Identifier: Apache-2.0

package server

import (
	"net/http"
	"net/url"
	"strings"
	"testing"

	"github.com/X4Applegate/caddyui/internal/models"
)

// The host form used to show two "Strip response headers" inputs bound to two
// different columns — the response-side twin of issue #167. The duplicate input
// is gone and strip_resp_headers is canonical, but the legacy
// strip_response_headers POST value is still accepted for one release so an old
// bookmarked form or an API client posting the v2.9.168 name does not silently
// lose data.
func TestParseProxyHostFormConsolidatesStripResponseHeaders(t *testing.T) {
	cases := []struct {
		name   string
		form   url.Values
		want   string
		reason string
	}{
		{
			name: "canonical field alone",
			form: url.Values{"strip_resp_headers": {"X-Powered-By, Server"}},
			want: "X-Powered-By, Server",
		},
		{
			name:   "legacy field alone is still accepted",
			form:   url.Values{"strip_response_headers": {"X-Legacy-Only"}},
			want:   "X-Legacy-Only",
			reason: "an old bookmarked form must not silently drop the value",
		},
		{
			name: "both posted are merged, not duplicated",
			form: url.Values{
				"strip_resp_headers":     {"Server"},
				"strip_response_headers": {"X-Powered-By"},
			},
			want:   "Server, X-Powered-By",
			reason: "neither value may be discarded",
		},
		{
			name: "same name in both collapses",
			form: url.Values{
				"strip_resp_headers":     {"X-Powered-By"},
				"strip_response_headers": {"x-powered-by"},
			},
			want:   "X-Powered-By",
			reason: "HTTP header names are case-insensitive",
		},
		{
			name: "neither posted stays empty",
			form: url.Values{},
			want: "",
		},
	}

	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			form := url.Values{}
			for k, v := range tc.form {
				form[k] = v
			}
			// parseProxyHostForm needs a minimally valid host.
			form.Set("domains", "example.com")
			form.Set("forward_scheme", "http")
			form.Set("forward_host", "backend")
			form.Set("forward_port", "8080")

			req := httptestPostForm(t, form)
			ph, err := parseProxyHostForm(req)
			if err != nil {
				t.Fatalf("parseProxyHostForm: %v", err)
			}
			if ph.StripRespHeaders != tc.want {
				msg := ""
				if tc.reason != "" {
					msg = " (" + tc.reason + ")"
				}
				t.Errorf("StripRespHeaders = %q, want %q%s",
					ph.StripRespHeaders, tc.want, msg)
			}
			// The legacy column must never be written again.
			if ph.StripResponseHeaders != "" {
				t.Errorf("legacy StripResponseHeaders = %q, want empty",
					ph.StripResponseHeaders)
			}
		})
	}
}

func httptestPostForm(t *testing.T, form url.Values) *http.Request {
	t.Helper()
	req, err := http.NewRequest(http.MethodPost, "/hosts", strings.NewReader(form.Encode()))
	if err != nil {
		t.Fatalf("new request: %v", err)
	}
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	return req
}

// The global "Globally stripped response headers" setting applied itself to
// every host, and it used to read the legacy column. After the consolidation it
// must apply to the canonical field, prepending the global names and
// deduplicating against the per-host list.
func TestGlobalStripResponseHeadersAppliesToCanonicalField(t *testing.T) {
	cases := []struct {
		name     string
		global   string
		perHost  string
		want     string
		whyNotes string
	}{
		{
			name:    "per-host empty takes the global list",
			global:  "Server",
			perHost: "",
			want:    "Server",
		},
		{
			name:     "global applied ahead of per-host",
			global:   "Server",
			perHost:  "X-Powered-By",
			want:     "Server, X-Powered-By",
			whyNotes: "both must be stripped",
		},
		{
			name:     "overlap is deduplicated",
			global:   "Server, X-Powered-By",
			perHost:  "X-Powered-By",
			want:     "Server, X-Powered-By",
			whyNotes: "a header named in both used to be emitted twice",
		},
		{
			name:     "overlap dedupe is case-insensitive",
			global:   "X-Powered-By",
			perHost:  "x-powered-by",
			want:     "X-Powered-By",
			whyNotes: "HTTP header names are case-insensitive",
		},
	}

	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			got := models.MergeHeaderNameLists(tc.global, tc.perHost)
			if got != tc.want {
				msg := ""
				if tc.whyNotes != "" {
					msg = " (" + tc.whyNotes + ")"
				}
				t.Errorf("merge(global=%q, perHost=%q) = %q, want %q%s",
					tc.global, tc.perHost, got, tc.want, msg)
			}
		})
	}
}