package http import ( "fmt" "log/slog" "net/http" "tug/internal/platform/config" ) const ( CSP = "default-src 'none'; style-src 'self'; img-src 'self'; form-action 'self'; frame-ancestors 'none'; base-uri 'none'" CSPWithScript = "default-src 'none'; style-src 'self'; img-src 'self'; script-src 'self'; form-action 'self'; frame-ancestors 'none'; base-uri 'none'" ) func SecurityHeaders(next http.Handler, enhancementJS bool) http.Handler { csp := CSP if enhancementJS { csp = CSPWithScript } return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { h := w.Header() h.Set("Content-Security-Policy", csp) h.Set("Referrer-Policy", "no-referrer") h.Set("X-Content-Type-Options", "nosniff") next.ServeHTTP(w, r) }) } func Recover(next http.Handler, page ErrorPage) http.Handler { if page == nil { panic(errNilPage) } return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { tw := &startTracker{ResponseWriter: w} defer func() { v := recover() if v == nil { return } if v == http.ErrAbortHandler || tw.started { panic(http.ErrAbortHandler) } slog.Error("handler panic before the response started", "panic", fmt.Sprint(v)) page(w, r, http.StatusInternalServerError) }() next.ServeHTTP(tw, r) }) } type startTracker struct { http.ResponseWriter started bool } func (t *startTracker) WriteHeader(code int) { t.started = true t.ResponseWriter.WriteHeader(code) } func (t *startTracker) Write(b []byte) (int, error) { t.started = true return t.ResponseWriter.Write(b) } func (t *startTracker) Flush() { t.started = true if f, ok := t.ResponseWriter.(http.Flusher); ok { f.Flush() } } func (t *startTracker) Unwrap() http.ResponseWriter { t.started = true return t.ResponseWriter } func Routed(srv config.Server, apps []config.App, h Handlers, page ErrorPage, enhancementJS bool, opts ...RouterOption) http.Handler { if page == nil { panic(errNilPage) } return SecurityHeaders(Recover(forwardedClient(NewRouter(srv, apps, h, page, opts...)), page), enhancementJS) }