@@ -5,18 +5,25 @@ package context
55
66import (
77 "context"
8+ "fmt"
9+ "html"
810 "html/template"
911 "net/http"
1012 "strconv"
1113 "strings"
14+ "sync"
1215 "time"
1316
1417 "code.gitea.io/gitea/modules/httplib"
18+ "code.gitea.io/gitea/modules/public"
1519 "code.gitea.io/gitea/modules/setting"
20+ "code.gitea.io/gitea/modules/util"
1621 "code.gitea.io/gitea/modules/web/middleware"
1722 "code.gitea.io/gitea/services/webtheme"
1823)
1924
25+ type TemplateContext map [string ]any
26+
2027var _ context.Context = TemplateContext (nil )
2128
2229func NewTemplateContext (ctx context.Context , req * http.Request ) TemplateContext {
@@ -83,3 +90,46 @@ func (c TemplateContext) AppFullLink(link ...string) template.URL {
8390 }
8491 return template .URL (s + strings .TrimPrefix (link [0 ], "/" ))
8592}
93+
94+ var globalVars = sync .OnceValue (func () (ret struct {
95+ scriptImportRemainingPart string
96+ },
97+ ) {
98+ // add onerror handler to alert users when the script fails to load:
99+ // * for end users: there were many users reporting that "UI doesn't work", actually they made mistakes in their config
100+ // * for developers: help them to remember to run "make watch-frontend" to build frontend assets
101+ // the message will be directly put in the onerror JS code's string
102+ onScriptErrorPrompt := `Please make sure the asset files can be accessed.`
103+ if ! setting .IsProd {
104+ onScriptErrorPrompt += `\n\nFor development, run: make watch-frontend.`
105+ }
106+ onScriptErrorJS := fmt .Sprintf (`alert('Failed to load asset file from ' + this.src + '. %s')` , onScriptErrorPrompt )
107+ ret .scriptImportRemainingPart = `onerror="` + html .EscapeString (onScriptErrorJS ) + `"></script>`
108+ return ret
109+ })
110+
111+ func (c TemplateContext ) ScriptImport (path string , typ ... string ) template.HTML {
112+ if len (typ ) > 0 {
113+ if typ [0 ] == "module" {
114+ return template .HTML (`<script nonce="` + c .CspScriptNonce () + `" type="module" src="` + html .EscapeString (public .AssetURI (path )) + `" ` + globalVars ().scriptImportRemainingPart )
115+ }
116+ panic ("unsupported script type: " + typ [0 ])
117+ }
118+ return template .HTML (`<script nonce="` + c .CspScriptNonce () + `" src="` + html .EscapeString (public .AssetURI (path )) + `" ` + globalVars ().scriptImportRemainingPart )
119+ }
120+
121+ func (c TemplateContext ) CspScriptNonce () (ret string ) {
122+ ret , _ = c ["_cspScriptNonce" ].(string )
123+ if ret == "" {
124+ ret = util .FastCryptoRandomHex (32 ) // 16 bytes / 128 bits entropy
125+ c ["_cspScriptNonce" ] = ret
126+ }
127+ return ret
128+ }
129+
130+ func (c TemplateContext ) HeadMetaContentSecurityPolicy () template.HTML {
131+ return template .HTML (`<meta http-equiv="Content-Security-Policy" content="` +
132+ `script-src * 'nonce-` + c .CspScriptNonce () + `';` +
133+ `style-src * 'unsafe-inline';` +
134+ `">` )
135+ }
0 commit comments