@marcel91200 / caddy Mirror · read-only / commits / 9b07062e
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 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) {