package main import ( "net/http" "net/http/httptest" "os" "path/filepath" "testing" "codeberg.org/VARASYS/ZDDC/zddc/internal/archive" "codeberg.org/VARASYS/ZDDC/zddc/internal/config" "codeberg.org/VARASYS/ZDDC/zddc/internal/handler" ) // TestDispatchHidesDotPrefixedSegments asserts the dispatch() guard that // rejects requests whose URL contains a dot-prefixed segment (other than // the recognized virtual prefixes .archive and /.admin handled separately). // // The guard exists so the in-image dev-shell can keep persistent state // (settings, source clones, Go module cache) under /srv/.devshell on the // same Azure Files PVC as served data without ever exposing those files // via direct HTTP fetch. func TestDispatchHidesDotPrefixedSegments(t *testing.T) { root := t.TempDir() // Realistic shape: a project dir, a hidden top-level dir, and a hidden // sibling of a normal file inside the project. mustMkdir(t, filepath.Join(root, "Project-A")) mustWrite(t, filepath.Join(root, "Project-A", "doc.txt"), "ok") mustMkdir(t, filepath.Join(root, ".devshell")) mustMkdir(t, filepath.Join(root, ".devshell", "coder")) mustWrite(t, filepath.Join(root, ".devshell", "coder", "settings.json"), "secret") mustMkdir(t, filepath.Join(root, "Project-A", ".internal")) mustWrite(t, filepath.Join(root, "Project-A", ".internal", "notes.md"), "secret") idx, err := archive.BuildIndex(root) if err != nil { t.Fatalf("BuildIndex: %v", err) } cfg := config.Config{ Root: root, IndexPath: ".archive", EmailHeader: "X-Auth-Request-Email", } ring := handler.NewLogRing(10) cases := []struct { name string path string wantStatus int }{ // Hidden top-level dir — every shape blocked. {"hidden top dir", "/.devshell/", http.StatusNotFound}, {"hidden top dir nested", "/.devshell/coder/settings.json", http.StatusNotFound}, // Hidden segment under a real project dir — also blocked. {"hidden segment mid path", "/Project-A/.internal/notes.md", http.StatusNotFound}, // Sanity: recognized virtual prefixes are NOT blocked. .archive falls // through to its own handler (which 404s on missing tracking number, // but importantly NOT via the dot-prefix guard); .admin is handled // by an earlier dispatch branch and hits the IsAdmin gate. {".archive prefix passes guard", "/.archive/UNKNOWN", http.StatusNotFound}, // unknown tracking → 404 from archive handler, status matches {".admin not blocked by guard", "/.admin/whoami", http.StatusNotFound}, // no admins configured → IsAdmin false → 404 from admin handler // Normal files unaffected. {"plain file", "/Project-A/doc.txt", http.StatusOK}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { req := httptest.NewRequest(http.MethodGet, tc.path, nil) rec := httptest.NewRecorder() dispatch(cfg, idx, ring, rec, req) if rec.Code != tc.wantStatus { t.Errorf("path=%q status=%d want=%d body=%q", tc.path, rec.Code, tc.wantStatus, rec.Body.String()) } }) } } func mustMkdir(t *testing.T, path string) { t.Helper() if err := os.MkdirAll(path, 0o755); err != nil { t.Fatalf("mkdir %s: %v", path, err) } } func mustWrite(t *testing.T, path, body string) { t.Helper() if err := os.WriteFile(path, []byte(body), 0o644); err != nil { t.Fatalf("write %s: %v", path, err) } }