Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
Mirrored from https://github.com/Slazee/caddy.git
Synced Nov 10, 23:45 UTC
fileserver: test hide against Windows trailing dot and space aliases (#8377)
Kévin Dunglas committed
Nov 7, 2026 at 12:30 UTC
9b07062e9b0452b4e1fa9e7fec0a4c6d2d175e89
2 files changed
+62
filepath_windows.go
+1
@@ -22,6 +22,7 @@ import (
22
// are special file paths that require the use of syscall.FullPath
23
// to handle correctly.
24
// Just call stdlib's implementation which uses that function.
25
+// file_server's hide relies on it stripping trailing dots and spaces as Win32 opens do.
26
func FastAbs(path string) (string, error) {
27
return filepath.Abs(path)
28
}
modules/caddyhttp/fileserver/staticfiles_test.go
+61
@@ -142,6 +142,67 @@ func TestFileHidden(t *testing.T) {
142
}
143
}
144
145
+// Win32 path normalization strips trailing dots and spaces, so Windows opens
146
+// hidden files under these aliases; fileHidden relies on FastAbs normalizing alike.
147
+func TestHideWindowsTrailingDotSpace(t *testing.T) {
148
+ if runtime.GOOS != "windows" {
149
+ t.Skip("Win32 path normalization only")
150
+ }
151
+
152
+ root := t.TempDir()
153
+ for _, dir := range []string{"publicdir", "secretdir"} {
154
+ if err := os.Mkdir(filepath.Join(root, dir), 0o700); err != nil {
155
+ t.Fatal(err)
156
+ }
157
+ }
158
+ for _, name := range []string{"Caddyfile", "public.txt", filepath.Join("publicdir", "p.txt"), filepath.Join("secretdir", "p.txt")} {
159
+ if err := os.WriteFile(filepath.Join(root, name), []byte(name), 0o600); err != nil {
160
+ t.Fatal(err)
161
+ }
162
+ }
163
+
164
+ canonicalURIs := false
165
+ fsrv := FileServer{
166
+ Root: root,
167
+ Hide: []string{"Caddyfile", filepath.Join(root, "secretdir")},
168
+ CanonicalURIs: &canonicalURIs,
169
+ }
170
+ ctx, cancel := caddy.NewContext(caddy.Context{Context: context.Background()})
171
+ defer cancel()
172
+ if err := fsrv.Provision(ctx); err != nil {
173
+ t.Fatal(err)
174
+ }
175
+
176
+ for i, tc := range []struct {
177
+ path string
178
+ wantStatus int
179
+ }{
180
+ {path: "/public.txt.", wantStatus: http.StatusOK},
181
+ {path: "/public.txt%20", wantStatus: http.StatusOK},
182
+ {path: "/publicdir./p.txt", wantStatus: http.StatusOK},
183
+ {path: "/Caddyfile.", wantStatus: http.StatusNotFound},
184
+ {path: "/Caddyfile%20", wantStatus: http.StatusNotFound},
185
+ {path: "/Caddyfile.%20..", wantStatus: http.StatusNotFound},
186
+ {path: "/secretdir./p.txt", wantStatus: http.StatusNotFound},
187
+ } {
188
+ r := httptest.NewRequest(http.MethodGet, tc.path, nil)
189
+ r = r.WithContext(context.WithValue(r.Context(), caddy.ReplacerCtxKey, caddy.NewReplacer()))
190
+ w := httptest.NewRecorder()
191
+
192
+ status := http.StatusOK
193
+ if err := fsrv.ServeHTTP(w, r, nil); err != nil {
194
+ he, ok := errors.AsType[caddyhttp.HandlerError](err)
195
+ if !ok {
196
+ t.Fatalf("Test %d (%s): unexpected error: %v", i, tc.path, err)
197
+ }
198
+ status = he.StatusCode
199
+ }
200
+ if status != tc.wantStatus {
201
+ t.Errorf("Test %d (%s): expected status %d, got %d (body %q)", i, tc.path, tc.wantStatus, status, w.Body.String())
202
+ }
203
+ }
204
+}
205
+
206
// Check to make sure that we don't serve ETag and Last-Modified headers
207
// for files with invalid modification times
208
func TestModTimeHeaders(t *testing.T) {