rewrite: uri query replace operation (#6165)

* Implemented query replace oeration

* Modified replace operation to use regexes in caddyfile

* Added more tests to uri query operations
This commit is contained in:
Aziz Rmadi 2024-03-21 21:23:42 -05:00 committed by GitHub
parent 0c01547037
commit 29f57faa86
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 263 additions and 1 deletions

View file

@ -0,0 +1,106 @@
:9080
uri query +foo bar
uri query -baz
uri query taz test
uri query key=value example
uri query changethis>changed
uri query {
findme value replacement
+foo1 baz
}
respond "{query}"
----------
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":9080"
],
"routes": [
{
"handle": [
{
"handler": "rewrite",
"query": {
"add": [
{
"key": "foo",
"val": "bar"
}
]
}
},
{
"handler": "rewrite",
"query": {
"delete": [
"baz"
]
}
},
{
"handler": "rewrite",
"query": {
"set": [
{
"key": "taz",
"val": "test"
}
]
}
},
{
"handler": "rewrite",
"query": {
"set": [
{
"key": "key=value",
"val": "example"
}
]
}
},
{
"handler": "rewrite",
"query": {
"rename": [
{
"key": "changethis",
"val": "changed"
}
]
}
},
{
"handler": "rewrite",
"query": {
"add": [
{
"key": "foo1",
"val": "baz"
}
],
"replace": [
{
"key": "findme",
"replace": "replacement",
"search_regexp": "value"
}
]
}
},
{
"body": "{http.request.uri.query}",
"handler": "static_response"
}
]
}
]
}
}
}
}
}

View file

@ -569,6 +569,93 @@ func TestRenameAndOtherOps(t *testing.T) {
tester.AssertGetResponse("http://localhost:9080/endpoint?foo=bar", 200, "bar=taz&bar=baz") tester.AssertGetResponse("http://localhost:9080/endpoint?foo=bar", 200, "bar=taz&bar=baz")
} }
func TestReplaceOps(t *testing.T) {
tester := caddytest.NewTester(t)
tester.InitServer(`
{
admin localhost:2999
http_port 9080
}
:9080
uri query foo bar baz
respond "{query}"`, "caddyfile")
tester.AssertGetResponse("http://localhost:9080/endpoint?foo=bar", 200, "foo=baz")
}
func TestReplaceWithReplacementPlaceholder(t *testing.T) {
tester := caddytest.NewTester(t)
tester.InitServer(`
{
admin localhost:2999
http_port 9080
}
:9080
uri query foo bar {query.placeholder}
respond "{query}"`, "caddyfile")
tester.AssertGetResponse("http://localhost:9080/endpoint?placeholder=baz&foo=bar", 200, "foo=baz&placeholder=baz")
}
func TestReplaceWithKeyPlaceholder(t *testing.T) {
tester := caddytest.NewTester(t)
tester.InitServer(`
{
admin localhost:2999
http_port 9080
}
:9080
uri query {query.placeholder} bar baz
respond "{query}"`, "caddyfile")
tester.AssertGetResponse("http://localhost:9080/endpoint?placeholder=foo&foo=bar", 200, "foo=baz&placeholder=foo")
}
func TestPartialReplacement(t *testing.T) {
tester := caddytest.NewTester(t)
tester.InitServer(`
{
admin localhost:2999
http_port 9080
}
:9080
uri query foo ar az
respond "{query}"`, "caddyfile")
tester.AssertGetResponse("http://localhost:9080/endpoint?foo=bar", 200, "foo=baz")
}
func TestNonExistingSearch(t *testing.T) {
tester := caddytest.NewTester(t)
tester.InitServer(`
{
admin localhost:2999
http_port 9080
}
:9080
uri query foo var baz
respond "{query}"`, "caddyfile")
tester.AssertGetResponse("http://localhost:9080/endpoint?foo=bar", 200, "foo=bar")
}
func TestReplaceAllOps(t *testing.T) {
tester := caddytest.NewTester(t)
tester.InitServer(`
{
admin localhost:2999
http_port 9080
}
:9080
uri query * bar baz
respond "{query}"`, "caddyfile")
tester.AssertGetResponse("http://localhost:9080/endpoint?foo=bar&baz=bar", 200, "baz=baz&foo=baz")
}
func TestUriOpsBlock(t *testing.T) { func TestUriOpsBlock(t *testing.T) {
tester := caddytest.NewTester(t) tester := caddytest.NewTester(t)

View file

@ -213,6 +213,9 @@ func applyQueryOps(h httpcaddyfile.Helper, qo *queryOps, args []string) error {
renameValKey := strings.Split(key, ">") renameValKey := strings.Split(key, ">")
qo.Rename = append(qo.Rename, queryOpsArguments{Key: renameValKey[0], Val: renameValKey[1]}) qo.Rename = append(qo.Rename, queryOpsArguments{Key: renameValKey[0], Val: renameValKey[1]})
case len(args) == 3:
qo.Replace = append(qo.Replace, &queryOpsReplacement{Key: key, SearchRegexp: args[1], Replace: args[2]})
default: default:
if len(args) != 2 { if len(args) != 2 {
return h.ArgErr() return h.ArgErr()

View file

@ -118,6 +118,12 @@ func (rewr *Rewrite) Provision(ctx caddy.Context) error {
rep.re = re rep.re = re
} }
for _, replacementOp := range rewr.Query.Replace {
err := replacementOp.Provision(ctx)
if err != nil {
return fmt.Errorf("compiling regular expression %s in query rewrite replace operation: %v", replacementOp.SearchRegexp, err)
}
}
return nil return nil
} }
@ -490,13 +496,27 @@ type queryOps struct {
// and only appends an additional value for that key if any already exist. // and only appends an additional value for that key if any already exist.
Add []queryOpsArguments `json:"add,omitempty"` Add []queryOpsArguments `json:"add,omitempty"`
// Replaces query parameters.
Replace []*queryOpsReplacement `json:"replace,omitempty"`
// Deletes a given query key by name. // Deletes a given query key by name.
Delete []string `json:"delete,omitempty"` Delete []string `json:"delete,omitempty"`
} }
// Provision compiles the query replace operation regex.
func (replacement *queryOpsReplacement) Provision(_ caddy.Context) error {
if replacement.SearchRegexp != "" {
re, err := regexp.Compile(replacement.SearchRegexp)
if err != nil {
return fmt.Errorf("replacement for query field '%s': %v", replacement.Key, err)
}
replacement.re = re
}
return nil
}
func (q *queryOps) do(r *http.Request, repl *caddy.Replacer) { func (q *queryOps) do(r *http.Request, repl *caddy.Replacer) {
query := r.URL.Query() query := r.URL.Query()
for _, renameParam := range q.Rename { for _, renameParam := range q.Rename {
key := repl.ReplaceAll(renameParam.Key, "") key := repl.ReplaceAll(renameParam.Key, "")
val := repl.ReplaceAll(renameParam.Val, "") val := repl.ReplaceAll(renameParam.Val, "")
@ -525,6 +545,36 @@ func (q *queryOps) do(r *http.Request, repl *caddy.Replacer) {
query[key] = append(query[key], val) query[key] = append(query[key], val)
} }
for _, replaceParam := range q.Replace {
key := repl.ReplaceAll(replaceParam.Key, "")
search := repl.ReplaceKnown(replaceParam.Search, "")
replace := repl.ReplaceKnown(replaceParam.Replace, "")
// replace all query keys...
if key == "*" {
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)
}
}
}
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)
}
}
}
}
for _, deleteParam := range q.Delete { for _, deleteParam := range q.Delete {
param := repl.ReplaceAll(deleteParam, "") param := repl.ReplaceAll(deleteParam, "")
if param == "" { if param == "" {
@ -546,5 +596,21 @@ type queryOpsArguments struct {
Val string `json:"val,omitempty"` Val string `json:"val,omitempty"`
} }
type queryOpsReplacement struct {
// The key to replace in the query string.
Key string `json:"key,omitempty"`
// The substring to search for.
Search string `json:"search,omitempty"`
// The regular expression to search with.
SearchRegexp string `json:"search_regexp,omitempty"`
// The string with which to replace matches.
Replace string `json:"replace,omitempty"`
re *regexp.Regexp
}
// Interface guard // Interface guard
var _ caddyhttp.MiddlewareHandler = (*Rewrite)(nil) var _ caddyhttp.MiddlewareHandler = (*Rewrite)(nil)