@marcel91200 / caddy Mirror · read-only / commits / 69290d23
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, 15:45 UTC

rewrite: Implement `uri query` operations (#6120)

* Implemented basic uri query operations * Added support for query operations block * Applied Replacer on all query keys and values * Implemented rename query key opration * Rewrite struct: Changed QueryOperations field to Query and comments cleanup * Cleaned up comments, changed the order of operations and added more tests * Changed order of fields in queryOps struct to match the operations order

Aziz Rmadi committed Mar 6, 2024 at 09:08 UTC 69290d232dde7306ddaa20172f03cb8860ea6adb
3 files changed +226 -1
caddytest/integration/caddyfile_test.go
+91
@@ -497,6 +497,97 @@ func TestUriReplace(t *testing.T) {
497 tester.AssertGetResponse("http://localhost:9080/endpoint?test={%20content%20}", 200, "test=%7B%20content%20%7D")
498 }
499
500 +func TestUriOps(t *testing.T) {
501 + tester := caddytest.NewTester(t)
502 +
503 + tester.InitServer(`
504 + {
505 + admin localhost:2999
506 + http_port 9080
507 + }
508 + :9080
509 + uri query +foo bar
510 + uri query -baz
511 + uri query taz test
512 + uri query key=value example
513 + uri query changethis>changed
514 +
515 + respond "{query}"`, "caddyfile")
516 +
517 + tester.AssertGetResponse("http://localhost:9080/endpoint?foo=bar0&baz=buz&taz=nottest&changethis=val", 200, "changed=val&foo=bar0&foo=bar&key%3Dvalue=example&taz=test")
518 +}
519 +
520 +func TestSetThenAddQueryParams(t *testing.T) {
521 + tester := caddytest.NewTester(t)
522 +
523 + tester.InitServer(`
524 + {
525 + admin localhost:2999
526 + http_port 9080
527 + }
528 + :9080
529 + uri query foo bar
530 + uri query +foo baz
531 +
532 + respond "{query}"`, "caddyfile")
533 +
534 + tester.AssertGetResponse("http://localhost:9080/endpoint", 200, "foo=bar&foo=baz")
535 +}
536 +
537 +func TestSetThenDeleteParams(t *testing.T) {
538 + tester := caddytest.NewTester(t)
539 +
540 + tester.InitServer(`
541 + {
542 + admin localhost:2999
543 + http_port 9080
544 + }
545 + :9080
546 + uri query bar foo{query.foo}
547 + uri query -foo
548 +
549 + respond "{query}"`, "caddyfile")
550 +
551 + tester.AssertGetResponse("http://localhost:9080/endpoint?foo=bar", 200, "bar=foobar")
552 +}
553 +
554 +func TestRenameAndOtherOps(t *testing.T) {
555 + tester := caddytest.NewTester(t)
556 +
557 + tester.InitServer(`
558 + {
559 + admin localhost:2999
560 + http_port 9080
561 + }
562 + :9080
563 + uri query foo>bar
564 + uri query bar taz
565 + uri query +bar baz
566 +
567 + respond "{query}"`, "caddyfile")
568 +
569 + tester.AssertGetResponse("http://localhost:9080/endpoint?foo=bar", 200, "bar=taz&bar=baz")
570 +}
571 +
572 +func TestUriOpsBlock(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 {
582 + +foo bar
583 + -baz
584 + taz test
585 + }
586 + respond "{query}"`, "caddyfile")
587 +
588 + tester.AssertGetResponse("http://localhost:9080/endpoint?foo=bar0&baz=buz&taz=nottest", 200, "foo=bar0&foo=bar&taz=test")
589 +}
590 +
591 func TestHandleErrorSimpleCodes(t *testing.T) {
592 tester := caddytest.NewTester(t)
593 tester.InitServer(`{
modules/caddyhttp/rewrite/caddyfile.go
+59 -1
@@ -98,7 +98,7 @@ func parseCaddyfileURI(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, err
98 h.Next() // consume directive name
99
100 args := h.RemainingArgs()
101 - if len(args) < 2 {
101 + if len(args) < 1 {
102 return nil, h.ArgErr()
103 }
104
@@ -158,12 +158,70 @@ func parseCaddyfileURI(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, err
158 Replace: replace,
159 })
160
161 + case "query":
162 + if len(args) > 4 {
163 + return nil, h.ArgErr()
164 + }
165 + rewr.Query = &queryOps{}
166 + var hasArgs bool
167 + if len(args) > 1 {
168 + hasArgs = true
169 + err := applyQueryOps(h, rewr.Query, args[1:])
170 + if err != nil {
171 + return nil, err
172 + }
173 + }
174 +
175 + for h.NextBlock(0) {
176 + if hasArgs {
177 + return nil, h.Err("Cannot specify uri query rewrites in both argument and block")
178 + }
179 + queryArgs := []string{h.Val()}
180 + queryArgs = append(queryArgs, h.RemainingArgs()...)
181 + err := applyQueryOps(h, rewr.Query, queryArgs)
182 + if err != nil {
183 + return nil, err
184 + }
185 + }
186 +
187 default:
188 return nil, h.Errf("unrecognized URI manipulation '%s'", args[0])
189 }
190 return rewr, nil
191 }
192
193 +func applyQueryOps(h httpcaddyfile.Helper, qo *queryOps, args []string) error {
194 + key := args[0]
195 + switch {
196 + case strings.HasPrefix(key, "-"):
197 + if len(args) != 1 {
198 + return h.ArgErr()
199 + }
200 + qo.Delete = append(qo.Delete, strings.TrimLeft(key, "-"))
201 +
202 + case strings.HasPrefix(key, "+"):
203 + if len(args) != 2 {
204 + return h.ArgErr()
205 + }
206 + param := strings.TrimLeft(key, "+")
207 + qo.Add = append(qo.Add, queryOpsArguments{Key: param, Val: args[1]})
208 +
209 + case strings.Contains(key, ">"):
210 + if len(args) != 1 {
211 + return h.ArgErr()
212 + }
213 + renameValKey := strings.Split(key, ">")
214 + qo.Rename = append(qo.Rename, queryOpsArguments{Key: renameValKey[0], Val: renameValKey[1]})
215 +
216 + default:
217 + if len(args) != 2 {
218 + return h.ArgErr()
219 + }
220 + qo.Set = append(qo.Set, queryOpsArguments{Key: key, Val: args[1]})
221 + }
222 + return nil
223 +}
224 +
225 // parseCaddyfileHandlePath parses the handle_path directive. Syntax:
226 //
227 // handle_path [<matcher>] {
modules/caddyhttp/rewrite/rewrite.go
+76
@@ -89,6 +89,9 @@ type Rewrite struct {
89 // Performs regular expression replacements on the URI path.
90 PathRegexp []*regexReplacer `json:"path_regexp,omitempty"`
91
92 + // Mutates the query string of the URI.
93 + Query *queryOps `json:"query,omitempty"`
94 +
95 logger *zap.Logger
96 }
97
@@ -269,6 +272,11 @@ func (rewr Rewrite) Rewrite(r *http.Request, repl *caddy.Replacer) bool {
272 rep.do(r, repl)
273 }
274
275 + // apply query operations
276 + if rewr.Query != nil {
277 + rewr.Query.do(r, repl)
278 + }
279 +
280 // update the encoded copy of the URI
281 r.RequestURI = r.URL.RequestURI()
282
@@ -470,5 +478,73 @@ func changePath(req *http.Request, newVal func(pathOrRawPath string) string) {
478 }
479 }
480
481 +// queryOps describes the operations to perform on query keys: add, set, rename and delete.
482 +type queryOps struct {
483 + // Renames a query key from Key to Val, without affecting the value.
484 + Rename []queryOpsArguments `json:"rename,omitempty"`
485 +
486 + // Sets query parameters; overwrites a query key with the given value.
487 + Set []queryOpsArguments `json:"set,omitempty"`
488 +
489 + // Adds query parameters; does not overwrite an existing query field,
490 + // and only appends an additional value for that key if any already exist.
491 + Add []queryOpsArguments `json:"add,omitempty"`
492 +
493 + // Deletes a given query key by name.
494 + Delete []string `json:"delete,omitempty"`
495 +}
496 +
497 +func (q *queryOps) do(r *http.Request, repl *caddy.Replacer) {
498 + query := r.URL.Query()
499 +
500 + for _, renameParam := range q.Rename {
501 + key := repl.ReplaceAll(renameParam.Key, "")
502 + val := repl.ReplaceAll(renameParam.Val, "")
503 + if key == "" || val == "" {
504 + continue
505 + }
506 + query[val] = query[key]
507 + delete(query, key)
508 + }
509 +
510 + for _, setParam := range q.Set {
511 + key := repl.ReplaceAll(setParam.Key, "")
512 + if key == "" {
513 + continue
514 + }
515 + val := repl.ReplaceAll(setParam.Val, "")
516 + query[key] = []string{val}
517 + }
518 +
519 + for _, addParam := range q.Add {
520 + key := repl.ReplaceAll(addParam.Key, "")
521 + if key == "" {
522 + continue
523 + }
524 + val := repl.ReplaceAll(addParam.Val, "")
525 + query[key] = append(query[key], val)
526 + }
527 +
528 + for _, deleteParam := range q.Delete {
529 + param := repl.ReplaceAll(deleteParam, "")
530 + if param == "" {
531 + continue
532 + }
533 + delete(query, param)
534 + }
535 +
536 + r.URL.RawQuery = query.Encode()
537 +}
538 +
539 +type queryOpsArguments struct {
540 + // A key in the query string. Note that query string keys may appear multiple times.
541 + Key string `json:"key,omitempty"`
542 +
543 + // The value for the given operation; for add and set, this is
544 + // simply the value of the query, and for rename this is the
545 + // query key to rename to.
546 + Val string `json:"val,omitempty"`
547 +}
548 +
549 // Interface guard
550 var _ caddyhttp.MiddlewareHandler = (*Rewrite)(nil)