code.gitea.io/gitea@v1.22.3/services/context/context.go (about) 1 // Copyright 2014 The Gogs Authors. All rights reserved. 2 // Copyright 2020 The Gitea Authors. All rights reserved. 3 // SPDX-License-Identifier: MIT 4 5 package context 6 7 import ( 8 "context" 9 "encoding/hex" 10 "fmt" 11 "html/template" 12 "io" 13 "net/http" 14 "net/url" 15 "strings" 16 "time" 17 18 "code.gitea.io/gitea/models/unit" 19 user_model "code.gitea.io/gitea/models/user" 20 "code.gitea.io/gitea/modules/cache" 21 "code.gitea.io/gitea/modules/gitrepo" 22 "code.gitea.io/gitea/modules/httpcache" 23 "code.gitea.io/gitea/modules/session" 24 "code.gitea.io/gitea/modules/setting" 25 "code.gitea.io/gitea/modules/templates" 26 "code.gitea.io/gitea/modules/translation" 27 "code.gitea.io/gitea/modules/web" 28 "code.gitea.io/gitea/modules/web/middleware" 29 web_types "code.gitea.io/gitea/modules/web/types" 30 ) 31 32 // Render represents a template render 33 type Render interface { 34 TemplateLookup(tmpl string, templateCtx context.Context) (templates.TemplateExecutor, error) 35 HTML(w io.Writer, status int, name string, data any, templateCtx context.Context) error 36 } 37 38 // Context represents context of a request. 39 type Context struct { 40 *Base 41 42 TemplateContext TemplateContext 43 44 Render Render 45 PageData map[string]any // data used by JavaScript modules in one page, it's `window.config.pageData` 46 47 Cache cache.StringCache 48 Csrf CSRFProtector 49 Flash *middleware.Flash 50 Session session.Store 51 52 Link string // current request URL (without query string) 53 54 Doer *user_model.User // current signed-in user 55 IsSigned bool 56 IsBasicAuth bool 57 58 ContextUser *user_model.User // the user which is being visited, in most cases it differs from Doer 59 60 Repo *Repository 61 Org *Organization 62 Package *Package 63 } 64 65 type TemplateContext map[string]any 66 67 func init() { 68 web.RegisterResponseStatusProvider[*Context](func(req *http.Request) web_types.ResponseStatusProvider { 69 return req.Context().Value(WebContextKey).(*Context) 70 }) 71 } 72 73 type webContextKeyType struct{} 74 75 var WebContextKey = webContextKeyType{} 76 77 func GetWebContext(req *http.Request) *Context { 78 ctx, _ := req.Context().Value(WebContextKey).(*Context) 79 return ctx 80 } 81 82 // ValidateContext is a special context for form validation middleware. It may be different from other contexts. 83 type ValidateContext struct { 84 *Base 85 } 86 87 // GetValidateContext gets a context for middleware form validation 88 func GetValidateContext(req *http.Request) (ctx *ValidateContext) { 89 if ctxAPI, ok := req.Context().Value(apiContextKey).(*APIContext); ok { 90 ctx = &ValidateContext{Base: ctxAPI.Base} 91 } else if ctxWeb, ok := req.Context().Value(WebContextKey).(*Context); ok { 92 ctx = &ValidateContext{Base: ctxWeb.Base} 93 } else { 94 panic("invalid context, expect either APIContext or Context") 95 } 96 return ctx 97 } 98 99 func NewTemplateContextForWeb(ctx *Context) TemplateContext { 100 tmplCtx := NewTemplateContext(ctx) 101 tmplCtx["Locale"] = ctx.Base.Locale 102 tmplCtx["AvatarUtils"] = templates.NewAvatarUtils(ctx) 103 tmplCtx["RootData"] = ctx.Data 104 tmplCtx["Consts"] = map[string]any{ 105 "RepoUnitTypeCode": unit.TypeCode, 106 "RepoUnitTypeIssues": unit.TypeIssues, 107 "RepoUnitTypePullRequests": unit.TypePullRequests, 108 "RepoUnitTypeReleases": unit.TypeReleases, 109 "RepoUnitTypeWiki": unit.TypeWiki, 110 "RepoUnitTypeExternalWiki": unit.TypeExternalWiki, 111 "RepoUnitTypeExternalTracker": unit.TypeExternalTracker, 112 "RepoUnitTypeProjects": unit.TypeProjects, 113 "RepoUnitTypePackages": unit.TypePackages, 114 "RepoUnitTypeActions": unit.TypeActions, 115 } 116 return tmplCtx 117 } 118 119 func NewWebContext(base *Base, render Render, session session.Store) *Context { 120 ctx := &Context{ 121 Base: base, 122 Render: render, 123 Session: session, 124 125 Cache: cache.GetCache(), 126 Link: setting.AppSubURL + strings.TrimSuffix(base.Req.URL.EscapedPath(), "/"), 127 Repo: &Repository{PullRequest: &PullRequest{}}, 128 Org: &Organization{}, 129 } 130 ctx.TemplateContext = NewTemplateContextForWeb(ctx) 131 ctx.Flash = &middleware.Flash{DataStore: ctx, Values: url.Values{}} 132 return ctx 133 } 134 135 // Contexter initializes a classic context for a request. 136 func Contexter() func(next http.Handler) http.Handler { 137 rnd := templates.HTMLRenderer() 138 csrfOpts := CsrfOptions{ 139 Secret: hex.EncodeToString(setting.GetGeneralTokenSigningSecret()), 140 Cookie: setting.CSRFCookieName, 141 Secure: setting.SessionConfig.Secure, 142 CookieHTTPOnly: setting.CSRFCookieHTTPOnly, 143 CookieDomain: setting.SessionConfig.Domain, 144 CookiePath: setting.SessionConfig.CookiePath, 145 SameSite: setting.SessionConfig.SameSite, 146 } 147 if !setting.IsProd { 148 CsrfTokenRegenerationInterval = 5 * time.Second // in dev, re-generate the tokens more aggressively for debug purpose 149 } 150 return func(next http.Handler) http.Handler { 151 return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { 152 base, baseCleanUp := NewBaseContext(resp, req) 153 defer baseCleanUp() 154 ctx := NewWebContext(base, rnd, session.GetContextSession(req)) 155 156 ctx.Data.MergeFrom(middleware.CommonTemplateContextData()) 157 ctx.Data["Context"] = ctx // TODO: use "ctx" in template and remove this 158 ctx.Data["CurrentURL"] = setting.AppSubURL + req.URL.RequestURI() 159 ctx.Data["Link"] = ctx.Link 160 161 // PageData is passed by reference, and it will be rendered to `window.config.pageData` in `head.tmpl` for JavaScript modules 162 ctx.PageData = map[string]any{} 163 ctx.Data["PageData"] = ctx.PageData 164 165 ctx.Base.AppendContextValue(WebContextKey, ctx) 166 ctx.Base.AppendContextValueFunc(gitrepo.RepositoryContextKey, func() any { return ctx.Repo.GitRepo }) 167 168 ctx.Csrf = NewCSRFProtector(csrfOpts) 169 170 // Get the last flash message from cookie 171 lastFlashCookie := middleware.GetSiteCookie(ctx.Req, CookieNameFlash) 172 if vals, _ := url.ParseQuery(lastFlashCookie); len(vals) > 0 { 173 // store last Flash message into the template data, to render it 174 ctx.Data["Flash"] = &middleware.Flash{ 175 DataStore: ctx, 176 Values: vals, 177 ErrorMsg: vals.Get("error"), 178 SuccessMsg: vals.Get("success"), 179 InfoMsg: vals.Get("info"), 180 WarningMsg: vals.Get("warning"), 181 } 182 } 183 184 // if there are new messages in the ctx.Flash, write them into cookie 185 ctx.Resp.Before(func(resp ResponseWriter) { 186 if val := ctx.Flash.Encode(); val != "" { 187 middleware.SetSiteCookie(ctx.Resp, CookieNameFlash, val, 0) 188 } else if lastFlashCookie != "" { 189 middleware.SetSiteCookie(ctx.Resp, CookieNameFlash, "", -1) 190 } 191 }) 192 193 // If request sends files, parse them here otherwise the Query() can't be parsed and the CsrfToken will be invalid. 194 if ctx.Req.Method == "POST" && strings.Contains(ctx.Req.Header.Get("Content-Type"), "multipart/form-data") { 195 if err := ctx.Req.ParseMultipartForm(setting.Attachment.MaxSize << 20); err != nil && !strings.Contains(err.Error(), "EOF") { // 32MB max size 196 ctx.ServerError("ParseMultipartForm", err) 197 return 198 } 199 } 200 201 httpcache.SetCacheControlInHeader(ctx.Resp.Header(), 0, "no-transform") 202 ctx.Resp.Header().Set(`X-Frame-Options`, setting.CORSConfig.XFrameOptions) 203 204 ctx.Data["SystemConfig"] = setting.Config() 205 206 // FIXME: do we really always need these setting? There should be someway to have to avoid having to always set these 207 ctx.Data["DisableMigrations"] = setting.Repository.DisableMigrations 208 ctx.Data["DisableStars"] = setting.Repository.DisableStars 209 ctx.Data["EnableActions"] = setting.Actions.Enabled 210 211 ctx.Data["ManifestData"] = setting.ManifestData 212 213 ctx.Data["UnitWikiGlobalDisabled"] = unit.TypeWiki.UnitGlobalDisabled() 214 ctx.Data["UnitIssuesGlobalDisabled"] = unit.TypeIssues.UnitGlobalDisabled() 215 ctx.Data["UnitPullsGlobalDisabled"] = unit.TypePullRequests.UnitGlobalDisabled() 216 ctx.Data["UnitProjectsGlobalDisabled"] = unit.TypeProjects.UnitGlobalDisabled() 217 ctx.Data["UnitActionsGlobalDisabled"] = unit.TypeActions.UnitGlobalDisabled() 218 219 ctx.Data["AllLangs"] = translation.AllLangs() 220 221 next.ServeHTTP(ctx.Resp, ctx.Req) 222 }) 223 } 224 } 225 226 // HasError returns true if error occurs in form validation. 227 // Attention: this function changes ctx.Data and ctx.Flash 228 // If HasError is called, then before Redirect, the error message should be stored by ctx.Flash.Error(ctx.GetErrMsg()) again. 229 func (ctx *Context) HasError() bool { 230 hasErr, ok := ctx.Data["HasError"] 231 if !ok { 232 return false 233 } 234 ctx.Flash.ErrorMsg = ctx.GetErrMsg() 235 ctx.Data["Flash"] = ctx.Flash 236 return hasErr.(bool) 237 } 238 239 // GetErrMsg returns error message in form validation. 240 func (ctx *Context) GetErrMsg() string { 241 msg, _ := ctx.Data["ErrorMsg"].(string) 242 if msg == "" { 243 msg = "invalid form data" 244 } 245 return msg 246 } 247 248 func (ctx *Context) JSONRedirect(redirect string) { 249 ctx.JSON(http.StatusOK, map[string]any{"redirect": redirect}) 250 } 251 252 func (ctx *Context) JSONOK() { 253 ctx.JSON(http.StatusOK, map[string]any{"ok": true}) // this is only a dummy response, frontend seldom uses it 254 } 255 256 func (ctx *Context) JSONError(msg any) { 257 switch v := msg.(type) { 258 case string: 259 ctx.JSON(http.StatusBadRequest, map[string]any{"errorMessage": v, "renderFormat": "text"}) 260 case template.HTML: 261 ctx.JSON(http.StatusBadRequest, map[string]any{"errorMessage": v, "renderFormat": "html"}) 262 default: 263 panic(fmt.Sprintf("unsupported type: %T", msg)) 264 } 265 }