@marcel91200 / caddy Mirror · read-only / commits / 56e3a88e
Fork 0

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS

Mirrored from https://github.com/Slazee/caddy.git

Synced Nov 11, 04:45 UTC

caddyhttp: only replace known placeholders in respond headers (#8014)

The respond directive expanded header field names and values with repl.ReplaceAll, which blanks any {...} the replacer does not recognize Header values are config data, often JSON or carrying literal braces, so a value like {key:value} was sent empty and a-{b}-c came out a--c Use ReplaceKnown, matching the header handler fix in #4880 (same class as #4418) and the respond body expansion a few lines below, so unknown braces survive and real placeholders still expand

Abdellatif Anaflous committed Sep 13, 2026 at 03:08 UTC 56e3a88efe39be6e380496778e7b94cb97f60c00
2 files changed +52 -2
modules/caddyhttp/staticresp.go
+2 -2
@@ -194,10 +194,10 @@ func (s StaticResponse) ServeHTTP(w http.ResponseWriter, r *http.Request, next H
194
195 // set all headers
196 for field, vals := range s.Headers {
197 - field = textproto.CanonicalMIMEHeaderKey(repl.ReplaceAll(field, ""))
197 + field = textproto.CanonicalMIMEHeaderKey(repl.ReplaceKnown(field, ""))
198 newVals := make([]string, len(vals))
199 for i := range vals {
200 - newVals[i] = repl.ReplaceAll(vals[i], "")
200 + newVals[i] = repl.ReplaceKnown(vals[i], "")
201 }
202 w.Header()[field] = newVals
203 }
modules/caddyhttp/staticresp_test.go
+50
@@ -64,3 +64,53 @@ func fakeRequest() *http.Request {
64 r = r.WithContext(ctx)
65 return r
66 }
67 +
68 +func TestStaticResponseHeadersKeepUnknownPlaceholders(t *testing.T) {
69 + r := fakeRequest()
70 + w := httptest.NewRecorder()
71 +
72 + s := StaticResponse{
73 + StatusCode: WeakString(strconv.Itoa(http.StatusOK)),
74 + Headers: http.Header{
75 + "X-Json": []string{`{"key":"value"}`},
76 + "X-Lit": []string{"value-{not-a-real-placeholder}-kept"},
77 + },
78 + }
79 +
80 + err := s.ServeHTTP(w, r, nil)
81 + if err != nil {
82 + t.Errorf("did not expect an error, but got: %v", err)
83 + }
84 +
85 + resp := w.Result()
86 +
87 + if got, want := resp.Header.Get("X-Json"), `{"key":"value"}`; got != want {
88 + t.Errorf("X-Json header = %q, want %q (unknown placeholders in header values must not be blanked)", got, want)
89 + }
90 + if got, want := resp.Header.Get("X-Lit"), "value-{not-a-real-placeholder}-kept"; got != want {
91 + t.Errorf("X-Lit header = %q, want %q", got, want)
92 + }
93 +}
94 +
95 +func TestStaticResponseHeadersStillReplaceKnownPlaceholders(t *testing.T) {
96 + r := fakeRequest()
97 + repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
98 + repl.Set("testvar", "replaced")
99 + w := httptest.NewRecorder()
100 +
101 + s := StaticResponse{
102 + StatusCode: WeakString(strconv.Itoa(http.StatusOK)),
103 + Headers: http.Header{
104 + "X-Var": []string{"value-{testvar}-end"},
105 + },
106 + }
107 +
108 + err := s.ServeHTTP(w, r, nil)
109 + if err != nil {
110 + t.Errorf("did not expect an error, but got: %v", err)
111 + }
112 +
113 + if got, want := w.Result().Header.Get("X-Var"), "value-replaced-end"; got != want {
114 + t.Errorf("X-Var header = %q, want %q (real placeholders must still expand)", got, want)
115 + }
116 +}