package web import ( "net/http" "strings" "github.com/go-chi/chi/v5" "sourcecraft.dev/bigbes/sr-ht-ecore/assets" "sourcecraft.dev/bigbes/sr-ht-ecore/csrf" "sourcecraft.dev/bigbes/sr-ht-ecore/middleware" ) // Register mounts every compare.sr.ht route onto r, inside a group carrying the // three middlewares that need this Server. The chain documented on the package // (config + authz at a minimum) is still the caller's, and stays outside this // group. // // - PrivateCache, because every page here is a page to its owner and a 404 to // everybody else, at a URL that says nothing about the viewer. The static // route opts back out per asset, from inside assets.Handler, and only once // the bytes are committed. // - RecoverPanics, so a panicking handler produces this service's own error // page instead of a dropped connection — and, when the response has already // started, a dropped connection instead of an error page appended to half a // rendered diff. // - csrf.Require, which guards a future rather than a present: every route // below is a GET, so nothing is refused by it today. It is installed anyway // because the day somebody adds the first POST is exactly the day nobody // remembers to add the check, and these services have no CSRF token to fall // back on — the session cookie is meta.sr.ht's, set on the parent domain, // with a SameSite no individual service can choose. // // The router's own 404 is set here too, so a mistyped URL lands on a page with a // nav rather than on chi's plain-text dead end. func (s *Server) Register(r chi.Router) { r.Group(func(r chi.Router) { r.Use(middleware.PrivateCache) r.Use(middleware.RecoverPanics(func(w http.ResponseWriter, r *http.Request, _ any) { s.renderError(w, r, http.StatusInternalServerError, "") })) r.Use(csrf.Require(s.chromeSvc.SelfOrigin(), nil)) r.NotFound(s.handleNotFound) r.Get("/", s.handleIndex) r.Get("/jump", s.handleJump) r.Get("/healthz", s.handleHealthz) r.Handle(assets.DefaultPrefix+"*", s.static) r.Get("/~{owner}/{repo}", s.handleRepo) // A single wildcard route serves both the form target (empty wildcard ⇒ // redirect to the canonical URL) and the compare view itself. r.Get("/~{owner}/{repo}/compare/*", s.handleCompare) r.Get("/~{owner}/{repo}/commit/{rev}", s.handleCommit) }) } // handleHealthz is a dependency-free liveness probe. func (s *Server) handleHealthz(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/plain; charset=utf-8") _, _ = w.Write([]byte("ok\n")) } // handleJump powers the owner/repo jump form: it redirects to the canonical repo // URL. A leading "~" on the owner is tolerated. func (s *Server) handleJump(w http.ResponseWriter, r *http.Request) { owner := strings.TrimPrefix(strings.TrimSpace(r.URL.Query().Get("owner")), "~") repo := strings.TrimSpace(r.URL.Query().Get("repo")) if owner == "" || repo == "" { s.renderError(w, r, http.StatusBadRequest, "both owner and repository are required") return } http.Redirect(w, r, "/~"+owner+"/"+repo, http.StatusFound) }