mirror of
https://github.com/caddyserver/caddy.git
synced 2025-01-27 23:03:37 -05:00
rewrite: Fix query string logic
This commit is contained in:
parent
8be1f0ea66
commit
d876de61e5
2 changed files with 34 additions and 22 deletions
|
@ -171,23 +171,31 @@ func (rewr Rewrite) rewrite(r *http.Request, repl *caddy.Replacer, logger *zap.L
|
||||||
|
|
||||||
// buildQueryString takes an input query string and
|
// buildQueryString takes an input query string and
|
||||||
// performs replacements on each component, returning
|
// performs replacements on each component, returning
|
||||||
// the resulting query string.
|
// the resulting query string. This function appends
|
||||||
|
// duplicate keys rather than replaces.
|
||||||
func buildQueryString(qs string, repl *caddy.Replacer) string {
|
func buildQueryString(qs string, repl *caddy.Replacer) string {
|
||||||
var sb strings.Builder
|
var sb strings.Builder
|
||||||
var wroteKey bool
|
|
||||||
|
// first component must be key, which is the same
|
||||||
|
// as if we just wrote a value in previous iteration
|
||||||
|
wroteVal := true
|
||||||
|
|
||||||
for len(qs) > 0 {
|
for len(qs) > 0 {
|
||||||
// determine the end of this component
|
// determine the end of this component, which will be at
|
||||||
|
// the next equal sign or ampersand, whichever comes first
|
||||||
nextEq, nextAmp := strings.Index(qs, "="), strings.Index(qs, "&")
|
nextEq, nextAmp := strings.Index(qs, "="), strings.Index(qs, "&")
|
||||||
end := min(nextEq, nextAmp)
|
ampIsNext := nextAmp >= 0 && (nextAmp < nextEq || nextEq < 0)
|
||||||
if end == -1 {
|
end := len(qs) // assume no delimiter remains...
|
||||||
end = len(qs) // if there is nothing left, go to end of string
|
if ampIsNext {
|
||||||
|
end = nextAmp // ...unless ampersand is first...
|
||||||
|
} else if nextEq >= 0 && (nextEq < nextAmp || nextAmp < 0) {
|
||||||
|
end = nextEq // ...or unless equal is first.
|
||||||
}
|
}
|
||||||
|
|
||||||
// consume the component and write the result
|
// consume the component and write the result
|
||||||
comp := qs[:end]
|
comp := qs[:end]
|
||||||
comp, _ = repl.ReplaceFunc(comp, func(name, val string) (string, error) {
|
comp, _ = repl.ReplaceFunc(comp, func(name, val string) (string, error) {
|
||||||
if name == "http.request.uri.query" {
|
if name == "http.request.uri.query" && wroteVal {
|
||||||
return val, nil // already escaped
|
return val, nil // already escaped
|
||||||
}
|
}
|
||||||
return url.QueryEscape(val), nil
|
return url.QueryEscape(val), nil
|
||||||
|
@ -197,29 +205,25 @@ func buildQueryString(qs string, repl *caddy.Replacer) string {
|
||||||
}
|
}
|
||||||
qs = qs[end:]
|
qs = qs[end:]
|
||||||
|
|
||||||
if wroteKey {
|
// if previous iteration wrote a value,
|
||||||
|
// that means we are writing a key
|
||||||
|
if wroteVal {
|
||||||
|
if sb.Len() > 0 {
|
||||||
|
sb.WriteRune('&')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
sb.WriteRune('=')
|
sb.WriteRune('=')
|
||||||
} else if sb.Len() > 0 {
|
|
||||||
sb.WriteRune('&')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// remember that we just wrote a key, which is if the next
|
|
||||||
// delimiter is an equals sign or if there is no ampersand
|
|
||||||
wroteKey = nextEq < nextAmp || nextAmp < 0
|
|
||||||
|
|
||||||
sb.WriteString(comp)
|
sb.WriteString(comp)
|
||||||
|
|
||||||
|
// remember for the next iteration that we just wrote a value,
|
||||||
|
// which means the next iteration MUST write a key
|
||||||
|
wroteVal = ampIsNext
|
||||||
}
|
}
|
||||||
|
|
||||||
return sb.String()
|
return sb.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func min(a, b int) int {
|
|
||||||
if b < a {
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
|
|
||||||
// replacer describes a simple and fast substring replacement.
|
// replacer describes a simple and fast substring replacement.
|
||||||
type replacer struct {
|
type replacer struct {
|
||||||
// The substring to find. Supports placeholders.
|
// The substring to find. Supports placeholders.
|
||||||
|
|
|
@ -138,6 +138,11 @@ func TestRewrite(t *testing.T) {
|
||||||
input: newRequest(t, "GET", "/foo/bar?a=b&c=d"),
|
input: newRequest(t, "GET", "/foo/bar?a=b&c=d"),
|
||||||
expect: newRequest(t, "GET", "/foo/bar"),
|
expect: newRequest(t, "GET", "/foo/bar"),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
rule: Rewrite{URI: "?qs={http.request.uri.query}"},
|
||||||
|
input: newRequest(t, "GET", "/foo?a=b&c=d"),
|
||||||
|
expect: newRequest(t, "GET", "/foo?qs=a%3Db%26c%3Dd"),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
rule: Rewrite{URI: "/foo?{http.request.uri.query}#frag"},
|
rule: Rewrite{URI: "/foo?{http.request.uri.query}#frag"},
|
||||||
input: newRequest(t, "GET", "/foo/bar?a=b"),
|
input: newRequest(t, "GET", "/foo/bar?a=b"),
|
||||||
|
@ -216,6 +221,9 @@ func TestRewrite(t *testing.T) {
|
||||||
if expected, actual := tc.expect.URL.RequestURI(), tc.input.URL.RequestURI(); expected != actual {
|
if expected, actual := tc.expect.URL.RequestURI(), tc.input.URL.RequestURI(); expected != actual {
|
||||||
t.Errorf("Test %d: Expected URL.RequestURI()='%s' but got '%s'", i, expected, actual)
|
t.Errorf("Test %d: Expected URL.RequestURI()='%s' but got '%s'", i, expected, actual)
|
||||||
}
|
}
|
||||||
|
if expected, actual := tc.expect.URL.Fragment, tc.input.URL.Fragment; expected != actual {
|
||||||
|
t.Errorf("Test %d: Expected URL.Fragment='%s' but got '%s'", i, expected, actual)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue