homepage/server/csp/csp.go
2026-01-13 21:31:43 +01:00

76 lines
1.9 KiB
Go

package csp
import (
"crypto/rand"
"encoding/base64"
"log"
"net/http"
)
type cspKeyword uint
//go:generate stringer -type=cspKeyword -linecomment
const (
Self cspKeyword = iota // self
TrustedTypesEval // trusted-types-eval
InlineSpeculationRules // inline-speculation-rules
StrictDynamic // strict-dynamic
ReportSample // report-sample
)
type cspDirective struct {
values []string
keywords map[cspKeyword]struct{}
}
type Builder struct {
directives map[string]*cspDirective
nonce string
request *http.Request
}
func (csp *Builder) getDirective(directive string) *cspDirective {
d, ok := csp.directives[directive]
if !ok {
d = &cspDirective{
values: []string{},
keywords: map[cspKeyword]struct{}{},
}
csp.directives[directive] = d
}
return d
}
// WithValue adds a directive to the policy.
func (csp *Builder) WithValue(directive string, values ...string) *Builder {
csp.getDirective(directive).withValue(values...)
return csp
}
func (d *cspDirective) withValue(values ...string) *cspDirective {
d.values = append(d.values, values...)
return d
}
// WithKeyword adds a directive to the policy.
func (csp *Builder) WithKeyword(directive string, keyword cspKeyword) *Builder {
csp.getDirective(directive).keywords[keyword] = struct{}{}
return csp
}
// WithNonce adds a `nonce-…` value to the directive. The actual nonce value is
// generated when the policy is built.
func (csp *Builder) WithNonce(directive string) *Builder {
if csp.nonce != "" {
var b [16]byte
if _, err := rand.Read(b[:]); err != nil {
log.Fatalf("Failed to generate nonce: %v", err)
}
csp.nonce = base64.RawURLEncoding.EncodeToString(b[:])
}
return csp.WithValue(directive, "nonce-"+csp.nonce)
}
func (csp *Builder) Build() string {
panic("TODO")
}