@marcel91200 / caddy Mirror · read-only / commits / 29f57faa
Fork 0

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS

Mirrored from https://github.com/Slazee/caddy.git

Synced Nov 12, 14:30 UTC

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

Aziz Rmadi committed Mar 21, 2024 at 21:23 UTC 29f57faa8679344fa40ea9b317d724f0604d5b40
4 files changed +263 -1
caddytest/integration/caddyfile_adapt/uri_query_operations.caddyfiletest new
+106
@@ -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 +}
\ No newline at end of file
caddytest/integration/caddyfile_test.go
+87
@@ -569,6 +569,93 @@ func TestRenameAndOtherOps(t *testing.T) {
569 tester.AssertGetResponse("http://localhost:9080/endpoint?foo=bar", 200, "bar=taz&bar=baz")
570 }
571
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 +
659 func TestUriOpsBlock(t *testing.T) {
660 tester := caddytest.NewTester(t)
661
modules/caddyhttp/rewrite/caddyfile.go
+3
@@ -213,6 +213,9 @@ func applyQueryOps(h httpcaddyfile.Helper, qo *queryOps, args []string) error {
213 renameValKey := strings.Split(key, ">")
214 qo.Rename = append(qo.Rename, queryOpsArguments{Key: renameValKey[0], Val: renameValKey[1]})
215
216 + case len(args) == 3:
217 + qo.Replace = append(qo.Replace, &queryOpsReplacement{Key: key, SearchRegexp: args[1], Replace: args[2]})
218 +
219 default:
220 if len(args) != 2 {
221 return h.ArgErr()
modules/caddyhttp/rewrite/rewrite.go
+67 -1
@@ -118,6 +118,12 @@ func (rewr *Rewrite) Provision(ctx caddy.Context) error {
118 rep.re = re
119 }
120
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 + }
127 return nil
128 }
129
@@ -490,13 +496,27 @@ type queryOps struct {
496 // and only appends an additional value for that key if any already exist.
497 Add []queryOpsArguments `json:"add,omitempty"`
498
499 + // Replaces query parameters.
500 + Replace []*queryOpsReplacement `json:"replace,omitempty"`
501 +
502 // Deletes a given query key by name.
503 Delete []string `json:"delete,omitempty"`
504 }
505
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 +
518 func (q *queryOps) do(r *http.Request, repl *caddy.Replacer) {
519 query := r.URL.Query()
499 -
520 for _, renameParam := range q.Rename {
521 key := repl.ReplaceAll(renameParam.Key, "")
522 val := repl.ReplaceAll(renameParam.Val, "")
@@ -525,6 +545,36 @@ func (q *queryOps) do(r *http.Request, repl *caddy.Replacer) {
545 query[key] = append(query[key], val)
546 }
547
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 +
578 for _, deleteParam := range q.Delete {
579 param := repl.ReplaceAll(deleteParam, "")
580 if param == "" {
@@ -546,5 +596,21 @@ type queryOpsArguments struct {
596 Val string `json:"val,omitempty"`
597 }
598
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 +
615 // Interface guard
616 var _ caddyhttp.MiddlewareHandler = (*Rewrite)(nil)