Raw
 1package http
 2
 3import (
 4	"fmt"
 5	"log/slog"
 6	"net/http"
 7
 8	"tug/internal/platform/config"
 9)
10
11const (
12	CSP           = "default-src 'none'; style-src 'self'; img-src 'self'; form-action 'self'; frame-ancestors 'none'; base-uri 'none'"
13	CSPWithScript = "default-src 'none'; style-src 'self'; img-src 'self'; script-src 'self'; form-action 'self'; frame-ancestors 'none'; base-uri 'none'"
14)
15
16func SecurityHeaders(next http.Handler, enhancementJS bool) http.Handler {
17	csp := CSP
18	if enhancementJS {
19		csp = CSPWithScript
20	}
21	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
22		h := w.Header()
23		h.Set("Content-Security-Policy", csp)
24		h.Set("Referrer-Policy", "no-referrer")
25		h.Set("X-Content-Type-Options", "nosniff")
26		next.ServeHTTP(w, r)
27	})
28}
29
30func Recover(next http.Handler, page ErrorPage) http.Handler {
31	if page == nil {
32		panic(errNilPage)
33	}
34	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
35		tw := &startTracker{ResponseWriter: w}
36		defer func() {
37			v := recover()
38			if v == nil {
39				return
40			}
41			if v == http.ErrAbortHandler || tw.started {
42				panic(http.ErrAbortHandler)
43			}
44			slog.Error("handler panic before the response started", "panic", fmt.Sprint(v))
45			page(w, r, http.StatusInternalServerError)
46		}()
47		next.ServeHTTP(tw, r)
48	})
49}
50
51type startTracker struct {
52	http.ResponseWriter
53	started bool
54}
55
56func (t *startTracker) WriteHeader(code int) {
57	t.started = true
58	t.ResponseWriter.WriteHeader(code)
59}
60
61func (t *startTracker) Write(b []byte) (int, error) {
62	t.started = true
63	return t.ResponseWriter.Write(b)
64}
65
66func (t *startTracker) Flush() {
67	t.started = true
68	if f, ok := t.ResponseWriter.(http.Flusher); ok {
69		f.Flush()
70	}
71}
72
73func (t *startTracker) Unwrap() http.ResponseWriter {
74	t.started = true
75	return t.ResponseWriter
76}
77
78func Routed(srv config.Server, apps []config.App, h Handlers, page ErrorPage, enhancementJS bool, opts ...RouterOption) http.Handler {
79	if page == nil {
80		panic(errNilPage)
81	}
82	return SecurityHeaders(Recover(forwardedClient(NewRouter(srv, apps, h, page, opts...)), page), enhancementJS)
83}