mirror of
https://github.com/caddyserver/caddy.git
synced 2024-12-16 21:56:40 -05:00
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
This commit is contained in:
parent
277472d081
commit
69290d232d
3 changed files with 226 additions and 1 deletions
|
@ -497,6 +497,97 @@ func TestUriReplace(t *testing.T) {
|
||||||
tester.AssertGetResponse("http://localhost:9080/endpoint?test={%20content%20}", 200, "test=%7B%20content%20%7D")
|
tester.AssertGetResponse("http://localhost:9080/endpoint?test={%20content%20}", 200, "test=%7B%20content%20%7D")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUriOps(t *testing.T) {
|
||||||
|
tester := caddytest.NewTester(t)
|
||||||
|
|
||||||
|
tester.InitServer(`
|
||||||
|
{
|
||||||
|
admin localhost:2999
|
||||||
|
http_port 9080
|
||||||
|
}
|
||||||
|
:9080
|
||||||
|
uri query +foo bar
|
||||||
|
uri query -baz
|
||||||
|
uri query taz test
|
||||||
|
uri query key=value example
|
||||||
|
uri query changethis>changed
|
||||||
|
|
||||||
|
respond "{query}"`, "caddyfile")
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetThenAddQueryParams(t *testing.T) {
|
||||||
|
tester := caddytest.NewTester(t)
|
||||||
|
|
||||||
|
tester.InitServer(`
|
||||||
|
{
|
||||||
|
admin localhost:2999
|
||||||
|
http_port 9080
|
||||||
|
}
|
||||||
|
:9080
|
||||||
|
uri query foo bar
|
||||||
|
uri query +foo baz
|
||||||
|
|
||||||
|
respond "{query}"`, "caddyfile")
|
||||||
|
|
||||||
|
tester.AssertGetResponse("http://localhost:9080/endpoint", 200, "foo=bar&foo=baz")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetThenDeleteParams(t *testing.T) {
|
||||||
|
tester := caddytest.NewTester(t)
|
||||||
|
|
||||||
|
tester.InitServer(`
|
||||||
|
{
|
||||||
|
admin localhost:2999
|
||||||
|
http_port 9080
|
||||||
|
}
|
||||||
|
:9080
|
||||||
|
uri query bar foo{query.foo}
|
||||||
|
uri query -foo
|
||||||
|
|
||||||
|
respond "{query}"`, "caddyfile")
|
||||||
|
|
||||||
|
tester.AssertGetResponse("http://localhost:9080/endpoint?foo=bar", 200, "bar=foobar")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRenameAndOtherOps(t *testing.T) {
|
||||||
|
tester := caddytest.NewTester(t)
|
||||||
|
|
||||||
|
tester.InitServer(`
|
||||||
|
{
|
||||||
|
admin localhost:2999
|
||||||
|
http_port 9080
|
||||||
|
}
|
||||||
|
:9080
|
||||||
|
uri query foo>bar
|
||||||
|
uri query bar taz
|
||||||
|
uri query +bar baz
|
||||||
|
|
||||||
|
respond "{query}"`, "caddyfile")
|
||||||
|
|
||||||
|
tester.AssertGetResponse("http://localhost:9080/endpoint?foo=bar", 200, "bar=taz&bar=baz")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUriOpsBlock(t *testing.T) {
|
||||||
|
tester := caddytest.NewTester(t)
|
||||||
|
|
||||||
|
tester.InitServer(`
|
||||||
|
{
|
||||||
|
admin localhost:2999
|
||||||
|
http_port 9080
|
||||||
|
}
|
||||||
|
:9080
|
||||||
|
uri query {
|
||||||
|
+foo bar
|
||||||
|
-baz
|
||||||
|
taz test
|
||||||
|
}
|
||||||
|
respond "{query}"`, "caddyfile")
|
||||||
|
|
||||||
|
tester.AssertGetResponse("http://localhost:9080/endpoint?foo=bar0&baz=buz&taz=nottest", 200, "foo=bar0&foo=bar&taz=test")
|
||||||
|
}
|
||||||
|
|
||||||
func TestHandleErrorSimpleCodes(t *testing.T) {
|
func TestHandleErrorSimpleCodes(t *testing.T) {
|
||||||
tester := caddytest.NewTester(t)
|
tester := caddytest.NewTester(t)
|
||||||
tester.InitServer(`{
|
tester.InitServer(`{
|
||||||
|
|
|
@ -98,7 +98,7 @@ func parseCaddyfileURI(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, err
|
||||||
h.Next() // consume directive name
|
h.Next() // consume directive name
|
||||||
|
|
||||||
args := h.RemainingArgs()
|
args := h.RemainingArgs()
|
||||||
if len(args) < 2 {
|
if len(args) < 1 {
|
||||||
return nil, h.ArgErr()
|
return nil, h.ArgErr()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -158,12 +158,70 @@ func parseCaddyfileURI(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, err
|
||||||
Replace: replace,
|
Replace: replace,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
case "query":
|
||||||
|
if len(args) > 4 {
|
||||||
|
return nil, h.ArgErr()
|
||||||
|
}
|
||||||
|
rewr.Query = &queryOps{}
|
||||||
|
var hasArgs bool
|
||||||
|
if len(args) > 1 {
|
||||||
|
hasArgs = true
|
||||||
|
err := applyQueryOps(h, rewr.Query, args[1:])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for h.NextBlock(0) {
|
||||||
|
if hasArgs {
|
||||||
|
return nil, h.Err("Cannot specify uri query rewrites in both argument and block")
|
||||||
|
}
|
||||||
|
queryArgs := []string{h.Val()}
|
||||||
|
queryArgs = append(queryArgs, h.RemainingArgs()...)
|
||||||
|
err := applyQueryOps(h, rewr.Query, queryArgs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return nil, h.Errf("unrecognized URI manipulation '%s'", args[0])
|
return nil, h.Errf("unrecognized URI manipulation '%s'", args[0])
|
||||||
}
|
}
|
||||||
return rewr, nil
|
return rewr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func applyQueryOps(h httpcaddyfile.Helper, qo *queryOps, args []string) error {
|
||||||
|
key := args[0]
|
||||||
|
switch {
|
||||||
|
case strings.HasPrefix(key, "-"):
|
||||||
|
if len(args) != 1 {
|
||||||
|
return h.ArgErr()
|
||||||
|
}
|
||||||
|
qo.Delete = append(qo.Delete, strings.TrimLeft(key, "-"))
|
||||||
|
|
||||||
|
case strings.HasPrefix(key, "+"):
|
||||||
|
if len(args) != 2 {
|
||||||
|
return h.ArgErr()
|
||||||
|
}
|
||||||
|
param := strings.TrimLeft(key, "+")
|
||||||
|
qo.Add = append(qo.Add, queryOpsArguments{Key: param, Val: args[1]})
|
||||||
|
|
||||||
|
case strings.Contains(key, ">"):
|
||||||
|
if len(args) != 1 {
|
||||||
|
return h.ArgErr()
|
||||||
|
}
|
||||||
|
renameValKey := strings.Split(key, ">")
|
||||||
|
qo.Rename = append(qo.Rename, queryOpsArguments{Key: renameValKey[0], Val: renameValKey[1]})
|
||||||
|
|
||||||
|
default:
|
||||||
|
if len(args) != 2 {
|
||||||
|
return h.ArgErr()
|
||||||
|
}
|
||||||
|
qo.Set = append(qo.Set, queryOpsArguments{Key: key, Val: args[1]})
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// parseCaddyfileHandlePath parses the handle_path directive. Syntax:
|
// parseCaddyfileHandlePath parses the handle_path directive. Syntax:
|
||||||
//
|
//
|
||||||
// handle_path [<matcher>] {
|
// handle_path [<matcher>] {
|
||||||
|
|
|
@ -89,6 +89,9 @@ type Rewrite struct {
|
||||||
// Performs regular expression replacements on the URI path.
|
// Performs regular expression replacements on the URI path.
|
||||||
PathRegexp []*regexReplacer `json:"path_regexp,omitempty"`
|
PathRegexp []*regexReplacer `json:"path_regexp,omitempty"`
|
||||||
|
|
||||||
|
// Mutates the query string of the URI.
|
||||||
|
Query *queryOps `json:"query,omitempty"`
|
||||||
|
|
||||||
logger *zap.Logger
|
logger *zap.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -269,6 +272,11 @@ func (rewr Rewrite) Rewrite(r *http.Request, repl *caddy.Replacer) bool {
|
||||||
rep.do(r, repl)
|
rep.do(r, repl)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// apply query operations
|
||||||
|
if rewr.Query != nil {
|
||||||
|
rewr.Query.do(r, repl)
|
||||||
|
}
|
||||||
|
|
||||||
// update the encoded copy of the URI
|
// update the encoded copy of the URI
|
||||||
r.RequestURI = r.URL.RequestURI()
|
r.RequestURI = r.URL.RequestURI()
|
||||||
|
|
||||||
|
@ -470,5 +478,73 @@ func changePath(req *http.Request, newVal func(pathOrRawPath string) string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// queryOps describes the operations to perform on query keys: add, set, rename and delete.
|
||||||
|
type queryOps struct {
|
||||||
|
// Renames a query key from Key to Val, without affecting the value.
|
||||||
|
Rename []queryOpsArguments `json:"rename,omitempty"`
|
||||||
|
|
||||||
|
// Sets query parameters; overwrites a query key with the given value.
|
||||||
|
Set []queryOpsArguments `json:"set,omitempty"`
|
||||||
|
|
||||||
|
// Adds query parameters; does not overwrite an existing query field,
|
||||||
|
// and only appends an additional value for that key if any already exist.
|
||||||
|
Add []queryOpsArguments `json:"add,omitempty"`
|
||||||
|
|
||||||
|
// Deletes a given query key by name.
|
||||||
|
Delete []string `json:"delete,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *queryOps) do(r *http.Request, repl *caddy.Replacer) {
|
||||||
|
query := r.URL.Query()
|
||||||
|
|
||||||
|
for _, renameParam := range q.Rename {
|
||||||
|
key := repl.ReplaceAll(renameParam.Key, "")
|
||||||
|
val := repl.ReplaceAll(renameParam.Val, "")
|
||||||
|
if key == "" || val == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
query[val] = query[key]
|
||||||
|
delete(query, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, setParam := range q.Set {
|
||||||
|
key := repl.ReplaceAll(setParam.Key, "")
|
||||||
|
if key == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
val := repl.ReplaceAll(setParam.Val, "")
|
||||||
|
query[key] = []string{val}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, addParam := range q.Add {
|
||||||
|
key := repl.ReplaceAll(addParam.Key, "")
|
||||||
|
if key == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
val := repl.ReplaceAll(addParam.Val, "")
|
||||||
|
query[key] = append(query[key], val)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, deleteParam := range q.Delete {
|
||||||
|
param := repl.ReplaceAll(deleteParam, "")
|
||||||
|
if param == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
delete(query, param)
|
||||||
|
}
|
||||||
|
|
||||||
|
r.URL.RawQuery = query.Encode()
|
||||||
|
}
|
||||||
|
|
||||||
|
type queryOpsArguments struct {
|
||||||
|
// A key in the query string. Note that query string keys may appear multiple times.
|
||||||
|
Key string `json:"key,omitempty"`
|
||||||
|
|
||||||
|
// The value for the given operation; for add and set, this is
|
||||||
|
// simply the value of the query, and for rename this is the
|
||||||
|
// query key to rename to.
|
||||||
|
Val string `json:"val,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
// Interface guard
|
// Interface guard
|
||||||
var _ caddyhttp.MiddlewareHandler = (*Rewrite)(nil)
|
var _ caddyhttp.MiddlewareHandler = (*Rewrite)(nil)
|
||||||
|
|
Loading…
Reference in a new issue