Skip to content

Commit 2ad1988

Browse files
committed
rewrite: Add option to force modifying the query
1 parent f145bce commit 2ad1988

File tree

4 files changed

+73
-8
lines changed

4 files changed

+73
-8
lines changed

caddytest/integration/caddyfile_adapt/reverse_proxy_handle_response.caddyfiletest

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ reverse_proxy 127.0.0.1:65535 {
1111

1212
@accel header X-Accel-Redirect *
1313
handle_response @accel {
14-
respond "Header X-Accel-Redirect!"
14+
rewrite * {rp.header.X-Accel-Redirect} {
15+
force_modify_query
16+
}
1517
}
1618

1719
@another {
@@ -104,10 +106,12 @@ reverse_proxy 127.0.0.1:65535 {
104106
},
105107
"routes": [
106108
{
109+
"group": "group0",
107110
"handle": [
108111
{
109-
"body": "Header X-Accel-Redirect!",
110-
"handler": "static_response"
112+
"force_modify_query": true,
113+
"handler": "rewrite",
114+
"uri": "{http.reverse_proxy.header.X-Accel-Redirect}"
111115
}
112116
]
113117
}

modules/caddyhttp/rewrite/caddyfile.go

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@ func init() {
3434

3535
// parseCaddyfileRewrite sets up a basic rewrite handler from Caddyfile tokens. Syntax:
3636
//
37-
// rewrite [<matcher>] <to>
37+
// rewrite [<matcher>] <to> {
38+
// force_modify_query
39+
// }
3840
//
3941
// Only URI components which are given in <to> will be set in the resulting URI.
4042
// See the docs for the rewrite handler for more information.
@@ -50,12 +52,30 @@ func parseCaddyfileRewrite(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue,
5052
return nil, h.Errf("too many arguments; should only be a matcher and a URI")
5153
}
5254

55+
parseBlock := func(rewr *Rewrite) error {
56+
for nesting := h.Nesting(); h.NextBlock(nesting); {
57+
switch h.Val() {
58+
case "force_modify_query":
59+
rewr.ForceModifyQuery = true
60+
61+
default:
62+
return h.Errf("unknown subdirective: %s", h.Val())
63+
}
64+
}
65+
return nil
66+
}
67+
5368
// with only one arg, assume it's a rewrite URI with no matcher token
5469
if argsCount == 1 {
5570
if !h.NextArg() {
5671
return nil, h.ArgErr()
5772
}
58-
return h.NewRoute(nil, Rewrite{URI: h.Val()}), nil
73+
rewr := Rewrite{URI: h.Val()}
74+
err := parseBlock(&rewr)
75+
if err != nil {
76+
return nil, err
77+
}
78+
return h.NewRoute(nil, rewr), nil
5979
}
6080

6181
// parse the matcher token into a matcher set
@@ -66,7 +86,12 @@ func parseCaddyfileRewrite(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue,
6686
h.Next() // consume directive name again, matcher parsing does a reset
6787
h.Next() // advance to the rewrite URI
6888

69-
return h.NewRoute(userMatcherSet, Rewrite{URI: h.Val()}), nil
89+
rewr := Rewrite{URI: h.Val()}
90+
err = parseBlock(&rewr)
91+
if err != nil {
92+
return nil, err
93+
}
94+
return h.NewRoute(userMatcherSet, rewr), nil
7095
}
7196

7297
// parseCaddyfileMethod sets up a basic method rewrite handler from Caddyfile tokens. Syntax:

modules/caddyhttp/rewrite/rewrite.go

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,17 @@ type Rewrite struct {
9292
// Mutates the query string of the URI.
9393
Query *queryOps `json:"query,omitempty"`
9494

95+
// If true, the rewrite will be forced to also apply to the
96+
// query part of the URL. This is only needed if the configured
97+
// URI does not include a '?' character which is normally used
98+
// to determine whether the query should be modified. In other
99+
// words, this allows rewriting both the path and query when
100+
// using a placeholder as the replacement value, whereas otherwise
101+
// only the path would be rewritten because the placeholder itself
102+
// does not contain a '?' character. Only use this if the placeholder
103+
// is trusted to not be vulnerable to query injections.
104+
ForceModifyQuery bool `json:"force_modify_query,omitempty"`
105+
95106
logger *zap.Logger
96107
}
97108

@@ -226,10 +237,15 @@ func (rewr Rewrite) Rewrite(r *http.Request, repl *caddy.Replacer) bool {
226237
// recompute; new path contains a query string
227238
var injectedQuery string
228239
newPath, injectedQuery = before, after
229-
// don't overwrite explicitly-configured query string
230-
if query == "" {
240+
241+
// don't overwrite explicitly-configured query string,
242+
// unless configured explicitly to do so
243+
if query == "" || rewr.ForceModifyQuery {
231244
query = injectedQuery
232245
}
246+
if rewr.ForceModifyQuery {
247+
qsStart = 0
248+
}
233249
}
234250

235251
if query != "" {

modules/caddyhttp/rewrite/rewrite_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,23 @@ func TestRewrite(t *testing.T) {
224224
input: newRequest(t, "GET", "/foo#fragFirst?c=d"),
225225
expect: newRequest(t, "GET", "/bar#fragFirst?c=d"),
226226
},
227+
{
228+
rule: Rewrite{URI: "{test.path_and_query}"},
229+
input: newRequest(t, "GET", "/"),
230+
expect: newRequest(t, "GET", "/foo"),
231+
},
232+
{
233+
// TODO: This might be an incorrect result, since it also replaces
234+
// the path with empty string when that might not be the intent.
235+
rule: Rewrite{URI: "{test.query}", ForceModifyQuery: true},
236+
input: newRequest(t, "GET", "/foo"),
237+
expect: newRequest(t, "GET", "?bar=1"),
238+
},
239+
{
240+
rule: Rewrite{URI: "{test.path_and_query}", ForceModifyQuery: true},
241+
input: newRequest(t, "GET", "/"),
242+
expect: newRequest(t, "GET", "/foo?bar=1"),
243+
},
227244

228245
{
229246
rule: Rewrite{StripPathPrefix: "/prefix"},
@@ -358,6 +375,9 @@ func TestRewrite(t *testing.T) {
358375
repl.Set("http.request.uri", tc.input.RequestURI)
359376
repl.Set("http.request.uri.path", tc.input.URL.Path)
360377
repl.Set("http.request.uri.query", tc.input.URL.RawQuery)
378+
repl.Set("test.path", "/foo")
379+
repl.Set("test.query", "?bar=1")
380+
repl.Set("test.path_and_query", "/foo?bar=1")
361381

362382
// we can't directly call Provision() without a valid caddy.Context
363383
// (TODO: fix that) so here we ad-hoc compile the regex

0 commit comments

Comments
 (0)