diff --git a/caddyhttp/staticfiles/fileserver.go b/caddyhttp/staticfiles/fileserver.go index be4da60a..f6ca23ab 100644 --- a/caddyhttp/staticfiles/fileserver.go +++ b/caddyhttp/staticfiles/fileserver.go @@ -56,6 +56,9 @@ func (fs FileServer) serveFile(w http.ResponseWriter, r *http.Request, name stri f, err := fs.Root.Open(name) if err != nil { + // TODO: remove when http.Dir handles this + // Go issue #18984 + err = mapFSRootOpenErr(err) if os.IsNotExist(err) { return http.StatusNotFound, nil } else if os.IsPermission(err) { @@ -230,3 +233,35 @@ var staticEncodingPriority = []string{ "br", "gzip", } + +// mapFSRootOpenErr maps the provided non-nil error +// to a possibly better non-nil error. In particular, it turns OS-specific errors +// about opening files in non-directories into os.ErrNotExist. +// +// TODO: remove when http.Dir handles this +// Go issue #18984 +func mapFSRootOpenErr(originalErr error) error { + if os.IsNotExist(originalErr) || os.IsPermission(originalErr) { + return originalErr + } + + perr, ok := originalErr.(*os.PathError) + if !ok { + return originalErr + } + name := perr.Path + parts := strings.Split(name, string(filepath.Separator)) + for i := range parts { + if parts[i] == "" { + continue + } + fi, err := os.Stat(strings.Join(parts[:i+1], string(filepath.Separator))) + if err != nil { + return originalErr + } + if !fi.IsDir() { + return os.ErrNotExist + } + } + return originalErr +} diff --git a/caddyhttp/staticfiles/fileserver_test.go b/caddyhttp/staticfiles/fileserver_test.go index 346a1d15..6753ecac 100644 --- a/caddyhttp/staticfiles/fileserver_test.go +++ b/caddyhttp/staticfiles/fileserver_test.go @@ -178,6 +178,11 @@ func TestServeHTTP(t *testing.T) { expectedBodyContent: testFiles[filepath.Join("webroot", "sub", "brotli.html.br")], expectedEtag: `W/"1e240-e"`, }, + // Test 20 - treat existing file as a directory. + { + url: "https://foo/file1.html/other", + expectedStatus: http.StatusNotFound, + }, } for i, test := range tests {