Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
Mirrored from https://github.com/Slazee/caddy.git
Synced Nov 11, 08:30 UTC
rewrite: preserve non-canonical path encoding after uri replace (#7907)
changePath cleared RawPath whenever it equalled url.Path, which discarded a valid non-canonical percent-encoding produced by a replacement. This compare `RawPath` against the default escaping of Path instead.
Saleh committed
Jul 26, 2026 at 01:49 UTC
c96bca12693419ac5be751a0cbbae2c3cbf89c58
2 files changed
+24
-2
modules/caddyhttp/rewrite/rewrite.go
+11
-2
@@ -513,12 +513,21 @@ func changePath(req *http.Request, newVal func(pathOrRawPath string) string) {
513
} else {
514
req.URL.Path = newVal(req.URL.Path)
515
}
516
- // RawPath is only set if it's different from the normalized Path (std lib)
517
- if req.URL.RawPath == req.URL.Path {
516
+ // RawPath is only needed if it is a valid, non-canonical encoding of Path;
517
+ // (see #6578). Mirror net/url.URL.setPath by comparing against the default
518
+ // escaping of Path instead.
519
+ if req.URL.RawPath == defaultEscapedPath(req.URL.Path) {
520
req.URL.RawPath = ""
521
}
522
}
523
524
+// defaultEscapedPath returns the canonical percent-encoding of p, matching
525
+// what net/url.URL.EscapedPath() produces when RawPath is empty. It mirrors
526
+// the comparison net/url.URL.setPath uses to decide whether RawPath is needed.
527
+func defaultEscapedPath(p string) string {
528
+ return (&url.URL{Path: p}).EscapedPath()
529
+}
530
+
531
// queryOps describes the operations to perform on query keys: add, set, rename and delete.
532
type queryOps struct {
533
// Renames a query key from Key to Val, without affecting the value.
modules/caddyhttp/rewrite/rewrite_test.go
+13
@@ -351,6 +351,19 @@ func TestRewrite(t *testing.T) {
351
input: newRequest(t, "GET", "/foo/findme%2Fbar"),
352
expect: newRequest(t, "GET", "/foo/replaced%2Fbar"),
353
},
354
+ {
355
+ rule: Rewrite{URISubstring: []substrReplacer{{Find: "%28", Replace: "("}}},
356
+ input: newRequest(t, "GET", "/hello/%28%29"),
357
+ expect: newRequest(t, "GET", "/hello/(%29"),
358
+ },
359
+ {
360
+ rule: Rewrite{URISubstring: []substrReplacer{
361
+ {Find: "%28", Replace: "("},
362
+ {Find: "%29", Replace: ")"},
363
+ }},
364
+ input: newRequest(t, "GET", "/hello/%28%29"),
365
+ expect: newRequest(t, "GET", "/hello/()"),
366
+ },
367
368
{
369
rule: Rewrite{PathRegexp: []*regexReplacer{{Find: "/{2,}", Replace: "/"}}},