@marcel91200 / caddy Mirror · read-only / commits / aea046c9
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

fileserver: don't match regular file when path ends by slash (#8326)

Kévin Dunglas committed Nov 1, 2026 at 21:57 UTC aea046c97833b7aaf1006db2f4796681733411e6
2 files changed +53 -8
modules/caddyhttp/fileserver/matcher.go
+5 -3
@@ -381,10 +381,12 @@ func (m MatchFile) selectFile(r *http.Request) (bool, error) {
381 // clean the path and split, if configured -- we must split before
382 // globbing so that the file system doesn't include the remainder
383 // ("afterSplit") in the filename; be sure to restore trailing slash
384 - beforeSplit, afterSplit := m.firstSplit(path.Clean(expandedFile))
385 - if strings.HasSuffix(file, "/") {
386 - beforeSplit += "/"
384 + // so that "/file/" or "/file/./" cannot match a regular file
385 + cleanedFile := path.Clean(expandedFile)
386 + if strings.HasSuffix(expandedFile, "/") && !strings.HasSuffix(cleanedFile, "/") {
387 + cleanedFile += "/"
388 }
389 + beforeSplit, afterSplit := m.firstSplit(cleanedFile)
390
391 // create the full path to the file by prepending the site root
392 fullPattern := caddyhttp.SanitizedPathJoin(root, beforeSplit)
modules/caddyhttp/fileserver/matcher_test.go
+48 -5
@@ -55,19 +55,52 @@ func TestFileMatcher(t *testing.T) {
55 f.Close()
56 }
57
58 + if err := os.WriteFile("./testdata/.env", []byte("SECRET=1"), 0o644); err != nil {
59 + t.Fatal(err)
60 + }
61 + t.Cleanup(func() {
62 + os.Remove("./testdata/.env")
63 + })
64 +
65 for i, tc := range []testCase{
66 {
60 - path: "/foo.txt",
61 - expectedPath: "/foo.txt",
67 + path: "/.env",
68 + expectedPath: "/.env",
69 expectedType: "file",
70 matched: true,
71 },
72 {
66 - path: "/foo.txt/",
73 + path: "/.env/",
74 + matched: false,
75 + },
76 + {
77 + path: "/.env/./",
78 + matched: false,
79 + },
80 + {
81 + path: "/foo.txt",
82 expectedPath: "/foo.txt",
83 expectedType: "file",
84 matched: true,
85 },
86 + {
87 + path: "/foo.txt/",
88 + matched: false,
89 + },
90 + {
91 + path: "/foo.txt/./",
92 + matched: false,
93 + },
94 + {
95 + path: "/foo.txt//",
96 + matched: false,
97 + },
98 + {
99 + path: "/foodir/./",
100 + expectedPath: "/foodir/",
101 + expectedType: "directory",
102 + matched: true,
103 + },
104 {
105 path: "/foo.txt?a=b",
106 expectedPath: "/foo.txt",
@@ -330,8 +363,18 @@ func TestPHPFileMatcher(t *testing.T) {
363 matched: true,
364 },
365 {
333 - path: "/notphp.php.txt/",
334 - expectedPath: "/notphp.php.txt",
366 + path: "/notphp.php.txt/",
367 + matched: false,
368 + },
369 + {
370 + path: "/index.php/",
371 + expectedPath: "/index.php",
372 + expectedType: "file",
373 + matched: true,
374 + },
375 + {
376 + path: "/index.php/somewhere/",
377 + expectedPath: "/index.php",
378 expectedType: "file",
379 matched: true,
380 },