diff --git a/internal/template/functions.go b/internal/template/functions.go index 59184ded..2836b38c 100644 --- a/internal/template/functions.go +++ b/internal/template/functions.go @@ -33,6 +33,7 @@ type funcMap struct { func (f *funcMap) Map() template.FuncMap { return template.FuncMap{ "contains": strings.Contains, + "csp": csp, "startsWith": strings.HasPrefix, "formatFileSize": formatFileSize, "dict": dict, @@ -116,6 +117,39 @@ func (f *funcMap) Map() template.FuncMap { } } +func csp(user *model.User, nonce string) string { + policies := map[string]string{ + "default-src": "'none'", + "frame-src": "*", + "img-src": "* data:", + "manifest-src": "'self'", + "media-src": "*", + "require-trusted-types-for": "'script'", + "script-src": "'nonce-" + nonce + "' 'strict-dynamic'", + "style-src": "'nonce-" + nonce + "'", + "trusted-types": "html url", + } + + if user != nil { + if user.ExternalFontHosts != "" { + policies["font-src"] = user.ExternalFontHosts + if user.Stylesheet != "" { + policies["style-src"] += " " + user.ExternalFontHosts + } + } + } + + var policy strings.Builder + for key, value := range policies { + policy.WriteString(key) + policy.WriteString(" ") + policy.WriteString(value) + policy.WriteString("; ") + } + + return `` +} + func dict(values ...any) (map[string]any, error) { if len(values)%2 != 0 { return nil, fmt.Errorf("dict expects an even number of arguments") diff --git a/internal/template/functions_test.go b/internal/template/functions_test.go index dd71f28f..4a45276c 100644 --- a/internal/template/functions_test.go +++ b/internal/template/functions_test.go @@ -4,10 +4,12 @@ package template // import "miniflux.app/v2/internal/template" import ( + "strings" "testing" "time" "miniflux.app/v2/internal/locale" + "miniflux.app/v2/internal/model" ) func TestDict(t *testing.T) { @@ -159,3 +161,26 @@ func TestFormatFileSize(t *testing.T) { } } } + +func TestCSP(t *testing.T) { + want := []string{ + `default-src 'none';`, + `img-src * data:;`, + `media-src *;`, + `frame-src *;`, + `style-src 'nonce-1234';`, + `script-src 'nonce-1234'`, + `'strict-dynamic';`, + `font-src test.com;`, + `require-trusted-types-for 'script';`, + `trusted-types html url;`, + `manifest-src 'self';`, + } + got := csp(&model.User{ExternalFontHosts: "test.com"}, "1234") + + for _, value := range want { + if !strings.Contains(got, value) { + t.Errorf(`Unexpected result, didn't find %q in %q`, value, got) + } + } +} diff --git a/internal/template/templates/common/layout.html b/internal/template/templates/common/layout.html index 8c4f069d..ca3529c0 100644 --- a/internal/template/templates/common/layout.html +++ b/internal/template/templates/common/layout.html @@ -25,24 +25,18 @@ - - - {{ if .user }} - {{ $cspNonce := nonce }} - - + {{ $cspNonce := nonce }} + {{ csp .user $cspNonce | safeHTML }} + + + {{ if .user -}} {{ if .user.Stylesheet -}} {{ end -}} - {{ if .user.CustomJS -}} {{ end -}} - {{ else -}} - {{ end -}} - -