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