GoDoxy/internal/net/gphttp/httpheaders/csp.go
2025-05-04 17:21:12 +08:00

69 lines
1.7 KiB
Go

package httpheaders
import (
"net/http"
"strings"
)
// AppendCSP appends a CSP header to specific directives in the response writer.
//
// Directives other than the ones in cspDirectives will be kept as is.
//
// It will replace 'none' with the sources.
//
// It will append 'self' to the sources if it's not already present.
func AppendCSP(w http.ResponseWriter, r *http.Request, cspDirectives []string, sources []string) {
csp := make(map[string]string)
cspValues := r.Header.Values("Content-Security-Policy")
if len(cspValues) == 1 {
cspValues = strings.Split(cspValues[0], ";")
for i, cspString := range cspValues {
cspValues[i] = strings.TrimSpace(cspString)
}
}
for _, cspString := range cspValues {
parts := strings.SplitN(cspString, " ", 2)
if len(parts) == 2 {
csp[parts[0]] = parts[1]
}
}
for _, directive := range cspDirectives {
value, ok := csp[directive]
if !ok {
value = "'self'"
}
switch value {
case "'self'":
csp[directive] = value + " " + strings.Join(sources, " ")
case "'none'":
csp[directive] = strings.Join(sources, " ")
default:
for _, source := range sources {
if !strings.Contains(value, source) {
value += " " + source
}
}
if !strings.Contains(value, "'self'") {
value = "'self' " + value
}
csp[directive] = value
}
}
values := make([]string, 0, len(csp))
for directive, value := range csp {
values = append(values, directive+" "+value)
}
// Remove existing CSP header, case insensitive
for k := range w.Header() {
if strings.EqualFold(k, "Content-Security-Policy") {
delete(w.Header(), k)
}
}
// Set new CSP header
w.Header()["Content-Security-Policy"] = values
}