package handler import ( "net/http" "os" "path/filepath" "strings" "codeberg.org/VARASYS/ZDDC/zddc/internal/config" ) // profileCustomCSSName is the preferred on-disk filename for operator-supplied // profile / editor theming. The legacy `.admin.css` is honored as a fallback // so an operator who already deployed the older name does not see their // styling vanish on upgrade; new deployments should use the `.profile.css` // name. const ( profileCustomCSSName = ".profile.css" adminCustomCSSName = ".admin.css" // legacy fallback ) // hasCustomProfileCSS reports whether /.profile.css (or the legacy // .admin.css) exists. The editor and profile templates use this to decide // whether to inject the tag. func hasCustomProfileCSS(fsRoot string) bool { if _, err := os.Stat(filepath.Join(fsRoot, profileCustomCSSName)); err == nil { return true } if _, err := os.Stat(filepath.Join(fsRoot, adminCustomCSSName)); err == nil { return true } return false } // zddcAssetsPathPrefix is the URL prefix for admin-only static assets. // They sit under /.profile/zddc/assets/ rather than /.profile/assets/ so // they share the editor's broader auth gate (subtree-or-super-admin) // instead of /.profile/'s super-admin-only diagnostics gate — otherwise a // subtree admin would 404 on the custom CSS link emitted by the editor. const zddcAssetsPathPrefix = ZddcProfilePathPrefix + "/assets" // serveZddcAssets handles /.profile/zddc/assets/. V1 only ships // `custom.css` (passthrough of /.profile.css when present, falling // back to /.admin.css); other paths return 404 so we don't // accidentally expose arbitrary files. hasAnyAdminScope has already gated // the request via ServeZddc. func serveZddcAssets(cfg config.Config, w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { w.Header().Set("Allow", "GET") http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) return } rest := strings.TrimPrefix(r.URL.Path, zddcAssetsPathPrefix+"/") switch rest { case "custom.css": path := filepath.Join(cfg.Root, profileCustomCSSName) if fi, err := os.Stat(path); err != nil || fi.IsDir() { path = filepath.Join(cfg.Root, adminCustomCSSName) if fi, err := os.Stat(path); err != nil || fi.IsDir() { http.NotFound(w, r) return } } w.Header().Set("Content-Type", "text/css; charset=utf-8") w.Header().Set("Cache-Control", "no-cache") http.ServeFile(w, r, path) default: http.NotFound(w, r) } }