diff --git a/caddyconfig/httpcaddyfile/builtins.go b/caddyconfig/httpcaddyfile/builtins.go index a7bb3b1de..6d6b71fa8 100644 --- a/caddyconfig/httpcaddyfile/builtins.go +++ b/caddyconfig/httpcaddyfile/builtins.go @@ -668,6 +668,8 @@ func parseRoot(h Helper) ([]ConfigValue, error) { if !h.NextArg() { return nil, h.ArgErr() } + // store the unmatched root in block state so sibling directives can access it + h.BlockState["root"] = h.Val() return h.NewRoute(nil, caddyhttp.VarsMiddleware{"root": h.Val()}), nil } @@ -682,6 +684,10 @@ func parseRoot(h Helper) ([]ConfigValue, error) { if !h.NextArg() { return nil, h.ArgErr() } + // store the unmatched root in state so sibling/child directives can access it + if userMatcherSet == nil { + h.BlockState["root"] = h.Val() + } // make the route with the matcher return h.NewRoute(userMatcherSet, caddyhttp.VarsMiddleware{"root": h.Val()}), nil } diff --git a/caddyconfig/httpcaddyfile/directives.go b/caddyconfig/httpcaddyfile/directives.go index eac7f5dc2..fa911b09e 100644 --- a/caddyconfig/httpcaddyfile/directives.go +++ b/caddyconfig/httpcaddyfile/directives.go @@ -202,7 +202,10 @@ func RegisterGlobalOption(opt string, setupFunc UnmarshalGlobalFunc) { type Helper struct { *caddyfile.Dispenser // State stores intermediate variables during caddyfile adaptation. - State map[string]any + State map[string]any + // BlockState stores intermediate variables scoped to the current block. + // It propagates down, but unlike state not back up from child to parent. + BlockState map[string]any options map[string]any warnings *[]caddyconfig.Warning matcherDefs map[string]caddy.ModuleMap @@ -385,6 +388,11 @@ func parseSegmentAsConfig(h Helper) ([]ConfigValue, error) { } } + // clone BlockState once for the entire block so sibling directives + // can share state, but changes don't leak to the parent scope + subBlockState := make(map[string]any, len(h.BlockState)) + maps.Copy(subBlockState, h.BlockState) + // with matchers ready to go, evaluate each directive's segment for _, seg := range segments { dir := seg.Directive() @@ -396,6 +404,7 @@ func parseSegmentAsConfig(h Helper) ([]ConfigValue, error) { subHelper := h subHelper.Dispenser = caddyfile.NewDispenser(seg) subHelper.matcherDefs = matcherDefs + subHelper.BlockState = subBlockState results, err := dirFunc(subHelper) if err != nil { diff --git a/caddyconfig/httpcaddyfile/httptype.go b/caddyconfig/httpcaddyfile/httptype.go index 1b9c625fe..c6979e56d 100644 --- a/caddyconfig/httpcaddyfile/httptype.go +++ b/caddyconfig/httpcaddyfile/httptype.go @@ -143,6 +143,7 @@ func (st ServerType) Setup( parentBlock: sb.block, groupCounter: gc, State: state, + BlockState: state, } results, err := dirFunc(h) @@ -504,6 +505,7 @@ func (ServerType) extractNamedRoutes( parentBlock: sb.block, groupCounter: gc, State: state, + BlockState: state, } handler, err := ParseSegmentAsSubroute(h) diff --git a/caddytest/integration/forwardauth_test.go b/caddytest/integration/forwardauth_test.go index d0ecc2be1..513c80906 100644 --- a/caddytest/integration/forwardauth_test.go +++ b/caddytest/integration/forwardauth_test.go @@ -190,7 +190,7 @@ func TestForwardAuthCopyHeadersAuthResponseWins(t *testing.T) { // its own values. The backend must receive the auth service values. req, _ := http.NewRequest(http.MethodGet, "http://localhost:9080/", nil) req.Header.Set("Authorization", "Bearer token123") - req.Header.Set("X-User-Id", "forged-id") // must be overwritten + req.Header.Set("X-User-Id", "forged-id") // must be overwritten req.Header.Set("X-User-Role", "forged-role") // must be overwritten tester.AssertResponse(req, http.StatusOK, "ok") diff --git a/modules/caddyhttp/reverseproxy/httptransport_test.go b/modules/caddyhttp/reverseproxy/httptransport_test.go index 88ac9d591..55ca3fd33 100644 --- a/modules/caddyhttp/reverseproxy/httptransport_test.go +++ b/modules/caddyhttp/reverseproxy/httptransport_test.go @@ -129,11 +129,11 @@ func TestHTTPTransport_DialTLSContext_ProxyProtocol(t *testing.T) { defer cancel() tests := []struct { - name string - tls *TLSConfig - proxyProtocol string + name string + tls *TLSConfig + proxyProtocol string serverNameHasPlaceholder bool - expectDialTLSContext bool + expectDialTLSContext bool }{ { name: "no TLS, no proxy protocol", @@ -194,4 +194,3 @@ func TestHTTPTransport_DialTLSContext_ProxyProtocol(t *testing.T) { }) } } -