rewrite: scope keyed query replace to its named key (#7818)
Some checks failed
Tests / test (./cmd/caddy/caddy, ~1.26.0, macos-14, 0, 1.26, mac) (push) Has been cancelled
Tests / test (./cmd/caddy/caddy, ~1.26.0, ubuntu-latest, 0, 1.26, linux) (push) Has been cancelled
Tests / test (./cmd/caddy/caddy.exe, ~1.26.0, windows-latest, True, 1.26, windows) (push) Has been cancelled
Tests / test (s390x on IBM Z) (push) Has been cancelled
Tests / goreleaser-check (push) Has been cancelled
Cross-Build / build (~1.26.0, 1.26, aix) (push) Has been cancelled
Cross-Build / build (~1.26.0, 1.26, darwin) (push) Has been cancelled
Cross-Build / build (~1.26.0, 1.26, dragonfly) (push) Has been cancelled
Cross-Build / build (~1.26.0, 1.26, freebsd) (push) Has been cancelled
Cross-Build / build (~1.26.0, 1.26, illumos) (push) Has been cancelled
Cross-Build / build (~1.26.0, 1.26, linux) (push) Has been cancelled
Cross-Build / build (~1.26.0, 1.26, netbsd) (push) Has been cancelled
Cross-Build / build (~1.26.0, 1.26, openbsd) (push) Has been cancelled
Cross-Build / build (~1.26.0, 1.26, solaris) (push) Has been cancelled
Cross-Build / build (~1.26.0, 1.26, windows) (push) Has been cancelled
Lint / lint (push) Has been cancelled
Lint / lint-1 (push) Has been cancelled
Lint / lint-2 (push) Has been cancelled
Lint / govulncheck (push) Has been cancelled
Lint / dependency-review (push) Has been cancelled
OpenSSF Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled

* rewrite: scope keyed query replace to its named key

* rewrite: cover keyed search_regexp query replace in test

* rewrite: provision query replace test via Provision path
This commit is contained in:
alhuda 2026-06-15 20:31:05 +05:30 committed by GitHub
parent 39c9a85f80
commit ae9bc028e6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 69 additions and 7 deletions

View file

@ -606,13 +606,15 @@ func (q *queryOps) do(r *http.Request, repl *caddy.Replacer) {
continue
}
for fieldName, vals := range query {
for i := range vals {
if replaceParam.re != nil {
query[fieldName][i] = replaceParam.re.ReplaceAllString(query[fieldName][i], replace)
} else {
query[fieldName][i] = strings.ReplaceAll(query[fieldName][i], search, replace)
}
vals, ok := query[key]
if !ok {
continue
}
for i := range vals {
if replaceParam.re != nil {
query[key][i] = replaceParam.re.ReplaceAllString(query[key][i], replace)
} else {
query[key][i] = strings.ReplaceAll(query[key][i], search, replace)
}
}
}

View file

@ -483,6 +483,66 @@ func TestQueryOpsRenameNoOpCases(t *testing.T) {
}
}
func TestQueryOpsReplaceScopedToKey(t *testing.T) {
repl := caddy.NewReplacer()
for i, tc := range []struct {
input *http.Request
expect map[string][]string
ops *queryOps
}{
{
// a keyed replace must only touch the named key, even when
// other keys hold the same value
ops: &queryOps{
Replace: []*queryOpsReplacement{{Key: "a", Search: "foo", Replace: "bar"}},
},
input: newRequest(t, "GET", "/?a=foo&b=foo"),
expect: map[string][]string{"a": {"bar"}, "b": {"foo"}},
},
{
// "*" still replaces across every key
ops: &queryOps{
Replace: []*queryOpsReplacement{{Key: "*", Search: "foo", Replace: "bar"}},
},
input: newRequest(t, "GET", "/?a=foo&b=foo"),
expect: map[string][]string{"a": {"bar"}, "b": {"bar"}},
},
{
// a keyed replace against a missing key is a no-op
ops: &queryOps{
Replace: []*queryOpsReplacement{{Key: "missing", Search: "foo", Replace: "bar"}},
},
input: newRequest(t, "GET", "/?a=foo&b=foo"),
expect: map[string][]string{"a": {"foo"}, "b": {"foo"}},
},
{
// the regexp branch must also stay scoped to the named key
ops: &queryOps{
Replace: []*queryOpsReplacement{{Key: "a", SearchRegexp: "f.o", Replace: "bar"}},
},
input: newRequest(t, "GET", "/?a=foo&b=foo"),
expect: map[string][]string{"a": {"bar"}, "b": {"foo"}},
},
} {
repl.Set("http.request.uri", tc.input.RequestURI)
repl.Set("http.request.uri.path", tc.input.URL.Path)
repl.Set("http.request.uri.query", tc.input.URL.RawQuery)
for _, rep := range tc.ops.Replace {
if err := rep.Provision(caddy.Context{}); err != nil {
t.Fatal(err)
}
}
tc.ops.do(tc.input, repl)
if actual := tc.input.URL.Query(); !reflect.DeepEqual(tc.expect, map[string][]string(actual)) {
t.Errorf("Test %d: Expected query=%v but got %v", i, tc.expect, actual)
}
}
}
func newRequest(t *testing.T, method, uri string) *http.Request {
req, err := http.NewRequest(method, uri, nil)
if err != nil {