httpcaddyfile: allocate route groups across named and site routes

Share the adaptation group counter with named-route extraction because group satisfaction is request-wide. Cover nested invoke and handle_path while retaining sibling exclusivity.

Addresses scan report fb62a11d-5561-49e3-87df-c175ea5a1f4d.

Code written by GitHub Copilot (GPT-6.1 Sol, Claude Opus 5.5); reviewed by mholt.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Matthew HoltandCopilot committed 2026-11-11 11:06:41 -07:00
1 parent ec849f542c
commit b0731f7d2a
2 files changed
+100 -2

No files matched your search

+2 -2
View File
@@ -104,7 +104,7 @@ func (st ServerType) Setup(
// with actual identifiers used by Caddy
replacer := NewShorthandReplacer()
originalServerBlocks, err = st.extractNamedRoutes(originalServerBlocks, options, &warnings, replacer)
originalServerBlocks, err = st.extractNamedRoutes(originalServerBlocks, options, &warnings, replacer, gc)
if err != nil {
return nil, warnings, err
}
@@ -477,11 +477,11 @@ func (ServerType) extractNamedRoutes(
options map[string]any,
warnings *[]caddyconfig.Warning,
replacer ShorthandReplacer,
gc counter,
) ([]serverBlock, error) {
namedRoutes := map[string]*caddyhttp.Route{}
namedRouteInvokes := map[string][]string{}
gc := counter{new(int)}
state := make(map[string]any)
// copy the server blocks so we can
@@ -0,0 +1,98 @@
package httpcaddyfile_test
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
_ "github.com/caddyserver/caddy/v2/modules/caddyhttp/rewrite"
)
func TestNamedRouteGroupsAreDistinct(t *testing.T) {
for _, tc := range []struct {
name, named, site string
}{
{
name: "handle",
named: `&(guard) {
handle /api/admin {
respond "denied" 403
}
handle {
respond "public"
}
}`,
site: `handle /api/* {
invoke guard
respond "allowed"
}`,
},
{
name: "handle_path",
named: `&(guard) {
handle /admin {
respond "denied" 403
}
handle {
respond "public"
}
}`,
site: `handle_path /api/* {
invoke guard
respond "allowed"
}`,
},
{
name: "nested invoke",
named: `&(inner) {
handle /api/admin {
respond "denied" 403
}
handle {
respond "public"
}
}
&(guard) {
handle /api/* {
invoke inner
}
handle {
respond "outer"
}
}`,
site: `handle /api/* {
invoke guard
respond "allowed"
}`,
},
} {
t.Run(tc.name, func(t *testing.T) {
server, handler := adaptedRouteHandler(t, "{\nadmin off\nauto_https off\n}\n"+tc.named+"\n:9080 {\n"+tc.site+`
handle {
respond "fallback" 404
}
}`)
for _, request := range []struct {
path string
code int
body string
}{
{"/api/admin", http.StatusForbidden, "denied"},
{"/api/public", http.StatusOK, "public"},
{"/other", http.StatusNotFound, "fallback"},
} {
req := httptest.NewRequest(http.MethodGet, request.path, nil)
req = caddyhttp.PrepareRequest(req, caddy.NewReplacer(), nil, server)
rec := httptest.NewRecorder()
if err := handler.ServeHTTP(rec, req); err != nil {
t.Fatal(err)
}
if rec.Code != request.code || rec.Body.String() != request.body {
t.Errorf("%s: got %d %q, want %d %q", request.path, rec.Code, rec.Body.String(), request.code, request.body)
}
}
})
}
}