mirror of
https://github.com/caddyserver/caddy.git
synced 2024-12-26 13:43:47 +03:00
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:
parent
0c01547037
commit
29f57faa86
4 changed files with 263 additions and 1 deletions
|
@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in a new issue