diff --git a/backend/handlers.go b/backend/handlers.go index fc0c07bd2..1f8e1966d 100644 --- a/backend/handlers.go +++ b/backend/handlers.go @@ -2,6 +2,7 @@ package main import ( "encoding/json" + "errors" "fmt" "html/template" "io" @@ -10,6 +11,7 @@ import ( "path" "path/filepath" "strings" + "syscall" "github.com/gorilla/mux" log "github.com/sirupsen/logrus" @@ -99,10 +101,15 @@ func serveFilesHandler(fs http.FileSystem) http.Handler { upath = path.Clean(upath) fileInfo, err := os.Stat(fmt.Sprintf("%v%s", fs, upath)) if err != nil { - if os.IsNotExist(err) { + // A path below a regular file (ENOTDIR, e.g. /index.html/foo) or with an + // overlong component (ENAMETOOLONG) cannot exist, so treat it as not found. + if os.IsNotExist(err) || errors.Is(err, syscall.ENOTDIR) || errors.Is(err, syscall.ENAMETOOLONG) { notFoundHandler(w, r) return } + // Let http.FileServer report any other error (e.g. 403 on permission denied). + fsh.ServeHTTP(w, r) + return } if fileInfo.IsDir() { diff --git a/backend/main_test.go b/backend/main_test.go index c03115264..2cc178c81 100644 --- a/backend/main_test.go +++ b/backend/main_test.go @@ -3,6 +3,9 @@ package main import ( "net/http" "net/http/httptest" + "os" + "path/filepath" + "strings" "testing" ) @@ -112,3 +115,35 @@ func TestStaticFileServer(t *testing.T) { t.Errorf("Wrong content type, expected %s, got %s", expectedContentType, contentType) } } + +func TestServeFilesHandler(t *testing.T) { + root := t.TempDir() + if err := os.WriteFile(filepath.Join(root, "page.html"), []byte("page"), 0o644); err != nil { + t.Fatal(err) + } + + h := serveFilesHandler(http.Dir(root)) + + cases := []struct { + name string + path string + want int + }{ + {name: "existing file", path: "/page.html", want: http.StatusOK}, + {name: "missing file", path: "/missing.html", want: http.StatusNotFound}, + {name: "path below a regular file", path: "/page.html/foo", want: http.StatusNotFound}, + {name: "overlong path component", path: "/" + strings.Repeat("a", 300), want: http.StatusNotFound}, + {name: "directory without index", path: "/", want: http.StatusNotFound}, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + rec := httptest.NewRecorder() + h.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, tc.path, nil)) + + if rec.Code != tc.want { + t.Fatalf("unexpected status for %s: got %d want %d", tc.path, rec.Code, tc.want) + } + }) + } +}