You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

context.go 9.2KB

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