Skip to content

Commit 29f57fa

Browse files
authored
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
1 parent 0c01547 commit 29f57fa

File tree

4 files changed

+263
-1
lines changed

4 files changed

+263
-1
lines changed
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
:9080
2+
uri query +foo bar
3+
uri query -baz
4+
uri query taz test
5+
uri query key=value example
6+
uri query changethis>changed
7+
uri query {
8+
findme value replacement
9+
+foo1 baz
10+
}
11+
12+
respond "{query}"
13+
----------
14+
{
15+
"apps": {
16+
"http": {
17+
"servers": {
18+
"srv0": {
19+
"listen": [
20+
":9080"
21+
],
22+
"routes": [
23+
{
24+
"handle": [
25+
{
26+
"handler": "rewrite",
27+
"query": {
28+
"add": [
29+
{
30+
"key": "foo",
31+
"val": "bar"
32+
}
33+
]
34+
}
35+
},
36+
{
37+
"handler": "rewrite",
38+
"query": {
39+
"delete": [
40+
"baz"
41+
]
42+
}
43+
},
44+
{
45+
"handler": "rewrite",
46+
"query": {
47+
"set": [
48+
{
49+
"key": "taz",
50+
"val": "test"
51+
}
52+
]
53+
}
54+
},
55+
{
56+
"handler": "rewrite",
57+
"query": {
58+
"set": [
59+
{
60+
"key": "key=value",
61+
"val": "example"
62+
}
63+
]
64+
}
65+
},
66+
{
67+
"handler": "rewrite",
68+
"query": {
69+
"rename": [
70+
{
71+
"key": "changethis",
72+
"val": "changed"
73+
}
74+
]
75+
}
76+
},
77+
{
78+
"handler": "rewrite",
79+
"query": {
80+
"add": [
81+
{
82+
"key": "foo1",
83+
"val": "baz"
84+
}
85+
],
86+
"replace": [
87+
{
88+
"key": "findme",
89+
"replace": "replacement",
90+
"search_regexp": "value"
91+
}
92+
]
93+
}
94+
},
95+
{
96+
"body": "{http.request.uri.query}",
97+
"handler": "static_response"
98+
}
99+
]
100+
}
101+
]
102+
}
103+
}
104+
}
105+
}
106+
}

caddytest/integration/caddyfile_test.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -569,6 +569,93 @@ func TestRenameAndOtherOps(t *testing.T) {
569569
tester.AssertGetResponse("http://localhost:9080/endpoint?foo=bar", 200, "bar=taz&bar=baz")
570570
}
571571

572+
func TestReplaceOps(t *testing.T) {
573+
tester := caddytest.NewTester(t)
574+
575+
tester.InitServer(`
576+
{
577+
admin localhost:2999
578+
http_port 9080
579+
}
580+
:9080
581+
uri query foo bar baz
582+
respond "{query}"`, "caddyfile")
583+
584+
tester.AssertGetResponse("http://localhost:9080/endpoint?foo=bar", 200, "foo=baz")
585+
}
586+
587+
func TestReplaceWithReplacementPlaceholder(t *testing.T) {
588+
tester := caddytest.NewTester(t)
589+
tester.InitServer(`
590+
{
591+
admin localhost:2999
592+
http_port 9080
593+
}
594+
:9080
595+
uri query foo bar {query.placeholder}
596+
respond "{query}"`, "caddyfile")
597+
598+
tester.AssertGetResponse("http://localhost:9080/endpoint?placeholder=baz&foo=bar", 200, "foo=baz&placeholder=baz")
599+
600+
}
601+
602+
func TestReplaceWithKeyPlaceholder(t *testing.T) {
603+
tester := caddytest.NewTester(t)
604+
tester.InitServer(`
605+
{
606+
admin localhost:2999
607+
http_port 9080
608+
}
609+
:9080
610+
uri query {query.placeholder} bar baz
611+
respond "{query}"`, "caddyfile")
612+
613+
tester.AssertGetResponse("http://localhost:9080/endpoint?placeholder=foo&foo=bar", 200, "foo=baz&placeholder=foo")
614+
}
615+
616+
func TestPartialReplacement(t *testing.T) {
617+
tester := caddytest.NewTester(t)
618+
tester.InitServer(`
619+
{
620+
admin localhost:2999
621+
http_port 9080
622+
}
623+
:9080
624+
uri query foo ar az
625+
respond "{query}"`, "caddyfile")
626+
627+
tester.AssertGetResponse("http://localhost:9080/endpoint?foo=bar", 200, "foo=baz")
628+
}
629+
630+
func TestNonExistingSearch(t *testing.T) {
631+
tester := caddytest.NewTester(t)
632+
tester.InitServer(`
633+
{
634+
admin localhost:2999
635+
http_port 9080
636+
}
637+
:9080
638+
uri query foo var baz
639+
respond "{query}"`, "caddyfile")
640+
641+
tester.AssertGetResponse("http://localhost:9080/endpoint?foo=bar", 200, "foo=bar")
642+
}
643+
644+
func TestReplaceAllOps(t *testing.T) {
645+
tester := caddytest.NewTester(t)
646+
647+
tester.InitServer(`
648+
{
649+
admin localhost:2999
650+
http_port 9080
651+
}
652+
:9080
653+
uri query * bar baz
654+
respond "{query}"`, "caddyfile")
655+
656+
tester.AssertGetResponse("http://localhost:9080/endpoint?foo=bar&baz=bar", 200, "baz=baz&foo=baz")
657+
}
658+
572659
func TestUriOpsBlock(t *testing.T) {
573660
tester := caddytest.NewTester(t)
574661

modules/caddyhttp/rewrite/caddyfile.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,9 @@ func applyQueryOps(h httpcaddyfile.Helper, qo *queryOps, args []string) error {
213213
renameValKey := strings.Split(key, ">")
214214
qo.Rename = append(qo.Rename, queryOpsArguments{Key: renameValKey[0], Val: renameValKey[1]})
215215

216+
case len(args) == 3:
217+
qo.Replace = append(qo.Replace, &queryOpsReplacement{Key: key, SearchRegexp: args[1], Replace: args[2]})
218+
216219
default:
217220
if len(args) != 2 {
218221
return h.ArgErr()

modules/caddyhttp/rewrite/rewrite.go

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,12 @@ func (rewr *Rewrite) Provision(ctx caddy.Context) error {
118118
rep.re = re
119119
}
120120

121+
for _, replacementOp := range rewr.Query.Replace {
122+
err := replacementOp.Provision(ctx)
123+
if err != nil {
124+
return fmt.Errorf("compiling regular expression %s in query rewrite replace operation: %v", replacementOp.SearchRegexp, err)
125+
}
126+
}
121127
return nil
122128
}
123129

@@ -490,13 +496,27 @@ type queryOps struct {
490496
// and only appends an additional value for that key if any already exist.
491497
Add []queryOpsArguments `json:"add,omitempty"`
492498

499+
// Replaces query parameters.
500+
Replace []*queryOpsReplacement `json:"replace,omitempty"`
501+
493502
// Deletes a given query key by name.
494503
Delete []string `json:"delete,omitempty"`
495504
}
496505

506+
// Provision compiles the query replace operation regex.
507+
func (replacement *queryOpsReplacement) Provision(_ caddy.Context) error {
508+
if replacement.SearchRegexp != "" {
509+
re, err := regexp.Compile(replacement.SearchRegexp)
510+
if err != nil {
511+
return fmt.Errorf("replacement for query field '%s': %v", replacement.Key, err)
512+
}
513+
replacement.re = re
514+
}
515+
return nil
516+
}
517+
497518
func (q *queryOps) do(r *http.Request, repl *caddy.Replacer) {
498519
query := r.URL.Query()
499-
500520
for _, renameParam := range q.Rename {
501521
key := repl.ReplaceAll(renameParam.Key, "")
502522
val := repl.ReplaceAll(renameParam.Val, "")
@@ -525,6 +545,36 @@ func (q *queryOps) do(r *http.Request, repl *caddy.Replacer) {
525545
query[key] = append(query[key], val)
526546
}
527547

548+
for _, replaceParam := range q.Replace {
549+
key := repl.ReplaceAll(replaceParam.Key, "")
550+
search := repl.ReplaceKnown(replaceParam.Search, "")
551+
replace := repl.ReplaceKnown(replaceParam.Replace, "")
552+
553+
// replace all query keys...
554+
if key == "*" {
555+
for fieldName, vals := range query {
556+
for i := range vals {
557+
if replaceParam.re != nil {
558+
query[fieldName][i] = replaceParam.re.ReplaceAllString(query[fieldName][i], replace)
559+
} else {
560+
query[fieldName][i] = strings.ReplaceAll(query[fieldName][i], search, replace)
561+
}
562+
}
563+
}
564+
continue
565+
}
566+
567+
for fieldName, vals := range query {
568+
for i := range vals {
569+
if replaceParam.re != nil {
570+
query[fieldName][i] = replaceParam.re.ReplaceAllString(query[fieldName][i], replace)
571+
} else {
572+
query[fieldName][i] = strings.ReplaceAll(query[fieldName][i], search, replace)
573+
}
574+
}
575+
}
576+
}
577+
528578
for _, deleteParam := range q.Delete {
529579
param := repl.ReplaceAll(deleteParam, "")
530580
if param == "" {
@@ -546,5 +596,21 @@ type queryOpsArguments struct {
546596
Val string `json:"val,omitempty"`
547597
}
548598

599+
type queryOpsReplacement struct {
600+
// The key to replace in the query string.
601+
Key string `json:"key,omitempty"`
602+
603+
// The substring to search for.
604+
Search string `json:"search,omitempty"`
605+
606+
// The regular expression to search with.
607+
SearchRegexp string `json:"search_regexp,omitempty"`
608+
609+
// The string with which to replace matches.
610+
Replace string `json:"replace,omitempty"`
611+
612+
re *regexp.Regexp
613+
}
614+
549615
// Interface guard
550616
var _ caddyhttp.MiddlewareHandler = (*Rewrite)(nil)

0 commit comments

Comments
 (0)