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") }