coverage~bigbes/sr-ht-compare9720ccc2web/router.go

Coverage
71.4% 15/21 statements
Δ
Blob
5c1f545
Uncovered L74-L81
1 package web
2
3 import (
4 "net/http"
5 "strings"
6
7 "github.com/go-chi/chi/v5"
8 "sourcecraft.dev/bigbes/sr-ht-ecore/assets"
9 "sourcecraft.dev/bigbes/sr-ht-ecore/chimw"
10 "sourcecraft.dev/bigbes/sr-ht-ecore/csrf"
11 "sourcecraft.dev/bigbes/sr-ht-ecore/middleware"
12 )
13
14 // Register mounts every diff.sr.ht route onto r, inside a group carrying the
15 // three middlewares that need this Server. The chain documented on the package
16 // (config + authz at a minimum) is still the caller's, and stays outside this
17 // group.
18 //
19 // - PrivateCache, because every page here is a page to its owner and a 404 to
20 // everybody else, at a URL that says nothing about the viewer. The static
21 // route opts back out per asset, from inside assets.Handler, and only once
22 // the bytes are committed.
23 // - RecoverPanics, so a panicking handler produces this service's own error
24 // page instead of a dropped connection — and, when the response has already
25 // started, a dropped connection instead of an error page appended to half a
26 // rendered diff.
27 // - csrf.Require, which guards a future rather than a present: every route
28 // below is a GET, so nothing is refused by it today. It is installed anyway
29 // because the day somebody adds the first POST is exactly the day nobody
30 // remembers to add the check, and these services have no CSRF token to fall
31 // back on — the session cookie is meta.sr.ht's, set on the parent domain,
32 // with a SameSite no individual service can choose.
33 //
34 // Both of chi's routing failures are pointed at this service's error page here,
35 // so a mistyped URL and a method this router does not serve land on a page with
36 // a nav rather than on chi's plain-text dead end. The 405 is new: until ecore
37 // grew chimw.RenderRefusals this service installed the 404 alone and left the
38 // other refusal to net/http.
39 //
40 // Every read route is registered under GET and HEAD both. chi's Get registers
41 // GET alone, so `curl -I` and every uptime probe used to be answered 405 by
42 // pages whose whole job is to be cheap to ask about.
43 24 func (s *Server) Register(r chi.Router) {
44 24 r.Group(func(r chi.Router) {
45 24 r.Use(middleware.PrivateCache)
46 24 r.Use(middleware.RecoverPanics(func(w http.ResponseWriter, r *http.Request, _ any) {
47 1 s.renderError(w, r, http.StatusInternalServerError, "")
48 1 }))
49 24 r.Use(csrf.Require(s.chromeSvc.SelfOrigin(), nil))
50 24
51 24 chimw.RenderRefusals(r, s.renderError)
52 24
53 24 chimw.GetHead(r, "/", s.handleIndex)
54 24 chimw.GetHead(r, "/jump", s.handleJump)
55 24 chimw.GetHead(r, "/healthz", s.handleHealthz)
56 24 r.Handle(assets.DefaultPrefix+"*", s.static)
57 24
58 24 chimw.GetHead(r, "/~{owner}/{repo}", s.handleRepo)
59 24 // A single wildcard route serves both the form target (empty wildcard ⇒
60 24 // redirect to the canonical URL) and the compare view itself.
61 24 chimw.GetHead(r, "/~{owner}/{repo}/compare/*", s.handleCompare)
62 24 chimw.GetHead(r, "/~{owner}/{repo}/commit/{rev}", s.handleCommit)
63 })
64 }
65
66 // handleHealthz is a dependency-free liveness probe.
67 2 func (s *Server) handleHealthz(w http.ResponseWriter, r *http.Request) {
68 2 w.Header().Set("Content-Type", "text/plain; charset=utf-8")
69 2 _, _ = w.Write([]byte("ok\n"))
70 2 }
71
72 // handleJump powers the owner/repo jump form: it redirects to the canonical repo
73 // URL. A leading "~" on the owner is tolerated.
74 0 func (s *Server) handleJump(w http.ResponseWriter, r *http.Request) {
75 0 owner := strings.TrimPrefix(strings.TrimSpace(r.URL.Query().Get("owner")), "~")
76 0 repo := strings.TrimSpace(r.URL.Query().Get("repo"))
77 0 if owner == "" || repo == "" {
78 0 s.renderError(w, r, http.StatusBadRequest, "both owner and repository are required")
79 0 return
80 0 }
81 0 http.Redirect(w, r, "/~"+owner+"/"+repo, http.StatusFound)
82 }