@marcel91200 / caddy Mirror · read-only / commits / 094172da
Fork 0

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, 01:00 UTC

rewrite: escape file matcher remainder before rewriting (#8325)

Kévin Dunglas committed Nov 1, 2026 at 21:53 UTC 094172da84a0139a5566b4ab11bc12393dbaeebc
2 files changed +49
modules/caddyhttp/rewrite/rewrite.go
+9
@@ -352,6 +352,15 @@ func escapePathPlaceholders(path string, r *http.Request, repl *caddy.Replacer)
352 }
353 }
354
355 + fileMatchRemainderPlaceholder := "{http.matchers.file.remainder}"
356 + if strings.Contains(path, fileMatchRemainderPlaceholder) {
357 + if val, ok := repl.Get("http.matchers.file.remainder"); ok {
358 + if remainder, ok := val.(string); ok {
359 + path = strings.ReplaceAll(path, fileMatchRemainderPlaceholder, escapePathPreservingSlashes(remainder))
360 + }
361 + }
362 + }
363 +
364 return path
365 }
366
modules/caddyhttp/rewrite/rewrite_test.go
+40
@@ -569,6 +569,46 @@ func TestRewrite(t *testing.T) {
569 }
570 }
571
572 +func TestRewriteEscapesFileMatcherRemainder(t *testing.T) {
573 + for i, tc := range []struct {
574 + input string
575 + remainder string
576 + expect string
577 + }{
578 + {
579 + input: "/index.php/foo/bar?a=b",
580 + remainder: "/foo/bar",
581 + expect: "/index.php/foo/bar?a=b",
582 + },
583 + {
584 + input: "/index.php/..%252fprivate%252fsecret.txt",
585 + remainder: "/..%2fprivate%2fsecret.txt",
586 + expect: "/index.php/..%252fprivate%252fsecret.txt",
587 + },
588 + {
589 + input: "/index.php/x%3Fa=evil?a=b",
590 + remainder: "/x?a=evil",
591 + expect: "/index.php/x%3Fa=evil?a=b",
592 + },
593 + {
594 + input: "/index.php/x%23frag?a=b",
595 + remainder: "/x#frag",
596 + expect: "/index.php/x%23frag?a=b",
597 + },
598 + } {
599 + req := newRequest(t, "GET", tc.input)
600 + repl := caddy.NewReplacer()
601 + repl.Set("http.matchers.file.relative", "/index.php")
602 + repl.Set("http.matchers.file.remainder", tc.remainder)
603 +
604 + Rewrite{URI: "{http.matchers.file.relative}{http.matchers.file.remainder}"}.Rewrite(req, repl)
605 +
606 + if expected, actual := tc.expect, req.URL.RequestURI(); expected != actual {
607 + t.Errorf("Test %d: Expected URL.RequestURI()='%s' but got '%s'", i, expected, actual)
608 + }
609 + }
610 +}
611 +
612 func TestQueryOpsRenameNoOpCases(t *testing.T) {
613 repl := caddy.NewReplacer()
614