diff --git a/internal/server/server_test.go b/internal/server/server_test.go index 0e4ed29..1e94bc4 100644 --- a/internal/server/server_test.go +++ b/internal/server/server_test.go @@ -273,9 +273,6 @@ func TestMutationRejectsBadSectionAndName(t *testing.T) { {"/api/library/movies/Ghost/mark", http.StatusNotFound}, {"/api/library/movies/..%2f..%2fetc/mark", http.StatusBadRequest}, {`/api/library/movies/..\..\etc/mark`, http.StatusBadRequest}, - // A literal ../ never reaches a handler: net/http's mux normalises the - // path and redirects, so no mutation runs. - {"/api/library/movies/../mark", http.StatusTemporaryRedirect}, } for _, c := range cases { rec := e.do(t, http.MethodPost, c.path) @@ -283,6 +280,22 @@ func TestMutationRejectsBadSectionAndName(t *testing.T) { t.Errorf("POST %s = %d, want %d (%s)", c.path, rec.Code, c.want, rec.Body) } } + + // A literal ../ never reaches a handler at all: net/http's mux normalises + // the path and redirects instead. The exact redirect code has varied across + // Go releases, so assert the property that matters — it is a redirect and + // no mutation ran. + rec := e.do(t, http.MethodPost, "/api/library/movies/../mark") + if rec.Code < 300 || rec.Code >= 400 { + t.Errorf("POST with a literal ../ = %d, want a redirect (%s)", rec.Code, rec.Body) + } + if _, err := os.Stat(filepath.Join(e.root, library.KidsTree, "movies")); err != nil { + t.Fatalf("kids movies dir disturbed: %v", err) + } + entries, err := os.ReadDir(filepath.Join(e.root, library.KidsTree, "movies")) + if err != nil || len(entries) != 0 { + t.Fatalf("traversal attempt created kids entries: %v %v", entries, err) + } } func TestArtProxyStreamsAndHidesTheKey(t *testing.T) {