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 7.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  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. "html"
  7. "html/template"
  8. "io"
  9. "net/http"
  10. "net/url"
  11. "strings"
  12. "time"
  13. "code.gitea.io/gitea/models/unit"
  14. user_model "code.gitea.io/gitea/models/user"
  15. mc "code.gitea.io/gitea/modules/cache"
  16. "code.gitea.io/gitea/modules/git"
  17. "code.gitea.io/gitea/modules/httpcache"
  18. "code.gitea.io/gitea/modules/setting"
  19. "code.gitea.io/gitea/modules/templates"
  20. "code.gitea.io/gitea/modules/translation"
  21. "code.gitea.io/gitea/modules/web"
  22. "code.gitea.io/gitea/modules/web/middleware"
  23. web_types "code.gitea.io/gitea/modules/web/types"
  24. "gitea.com/go-chi/cache"
  25. "gitea.com/go-chi/session"
  26. )
  27. // Render represents a template render
  28. type Render interface {
  29. TemplateLookup(tmpl string) (templates.TemplateExecutor, error)
  30. HTML(w io.Writer, status int, name string, data any) error
  31. }
  32. // Context represents context of a request.
  33. type Context struct {
  34. *Base
  35. Render Render
  36. PageData map[string]any // data used by JavaScript modules in one page, it's `window.config.pageData`
  37. Cache cache.Cache
  38. Csrf CSRFProtector
  39. Flash *middleware.Flash
  40. Session session.Store
  41. Link string // current request URL (without query string)
  42. Doer *user_model.User // current signed-in user
  43. IsSigned bool
  44. IsBasicAuth bool
  45. ContextUser *user_model.User // the user which is being visited, in most cases it differs from Doer
  46. Repo *Repository
  47. Org *Organization
  48. Package *Package
  49. }
  50. func init() {
  51. web.RegisterResponseStatusProvider[*Context](func(req *http.Request) web_types.ResponseStatusProvider {
  52. return req.Context().Value(WebContextKey).(*Context)
  53. })
  54. }
  55. // TrHTMLEscapeArgs runs ".Locale.Tr()" but pre-escapes all arguments with html.EscapeString.
  56. // This is useful if the locale message is intended to only produce HTML content.
  57. func (ctx *Context) TrHTMLEscapeArgs(msg string, args ...string) string {
  58. trArgs := make([]any, len(args))
  59. for i, arg := range args {
  60. trArgs[i] = html.EscapeString(arg)
  61. }
  62. return ctx.Locale.Tr(msg, trArgs...)
  63. }
  64. type webContextKeyType struct{}
  65. var WebContextKey = webContextKeyType{}
  66. func GetWebContext(req *http.Request) *Context {
  67. ctx, _ := req.Context().Value(WebContextKey).(*Context)
  68. return ctx
  69. }
  70. // ValidateContext is a special context for form validation middleware. It may be different from other contexts.
  71. type ValidateContext struct {
  72. *Base
  73. }
  74. // GetValidateContext gets a context for middleware form validation
  75. func GetValidateContext(req *http.Request) (ctx *ValidateContext) {
  76. if ctxAPI, ok := req.Context().Value(apiContextKey).(*APIContext); ok {
  77. ctx = &ValidateContext{Base: ctxAPI.Base}
  78. } else if ctxWeb, ok := req.Context().Value(WebContextKey).(*Context); ok {
  79. ctx = &ValidateContext{Base: ctxWeb.Base}
  80. } else {
  81. panic("invalid context, expect either APIContext or Context")
  82. }
  83. return ctx
  84. }
  85. // Contexter initializes a classic context for a request.
  86. func Contexter() func(next http.Handler) http.Handler {
  87. rnd := templates.HTMLRenderer()
  88. csrfOpts := CsrfOptions{
  89. Secret: setting.SecretKey,
  90. Cookie: setting.CSRFCookieName,
  91. SetCookie: true,
  92. Secure: setting.SessionConfig.Secure,
  93. CookieHTTPOnly: setting.CSRFCookieHTTPOnly,
  94. Header: "X-Csrf-Token",
  95. CookieDomain: setting.SessionConfig.Domain,
  96. CookiePath: setting.SessionConfig.CookiePath,
  97. SameSite: setting.SessionConfig.SameSite,
  98. }
  99. if !setting.IsProd {
  100. CsrfTokenRegenerationInterval = 5 * time.Second // in dev, re-generate the tokens more aggressively for debug purpose
  101. }
  102. return func(next http.Handler) http.Handler {
  103. return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
  104. base, baseCleanUp := NewBaseContext(resp, req)
  105. ctx := &Context{
  106. Base: base,
  107. Cache: mc.GetCache(),
  108. Link: setting.AppSubURL + strings.TrimSuffix(req.URL.EscapedPath(), "/"),
  109. Render: rnd,
  110. Session: session.GetSession(req),
  111. Repo: &Repository{PullRequest: &PullRequest{}},
  112. Org: &Organization{},
  113. }
  114. defer baseCleanUp()
  115. ctx.Data.MergeFrom(middleware.CommonTemplateContextData())
  116. ctx.Data["Context"] = &ctx
  117. ctx.Data["CurrentURL"] = setting.AppSubURL + req.URL.RequestURI()
  118. ctx.Data["Link"] = ctx.Link
  119. ctx.Data["locale"] = ctx.Locale
  120. // PageData is passed by reference, and it will be rendered to `window.config.pageData` in `head.tmpl` for JavaScript modules
  121. ctx.PageData = map[string]any{}
  122. ctx.Data["PageData"] = ctx.PageData
  123. ctx.Base.AppendContextValue(WebContextKey, ctx)
  124. ctx.Base.AppendContextValueFunc(git.RepositoryContextKey, func() any { return ctx.Repo.GitRepo })
  125. ctx.Csrf = PrepareCSRFProtector(csrfOpts, ctx)
  126. // Get the last flash message from cookie
  127. lastFlashCookie := middleware.GetSiteCookie(ctx.Req, CookieNameFlash)
  128. if vals, _ := url.ParseQuery(lastFlashCookie); len(vals) > 0 {
  129. // store last Flash message into the template data, to render it
  130. ctx.Data["Flash"] = &middleware.Flash{
  131. DataStore: ctx,
  132. Values: vals,
  133. ErrorMsg: vals.Get("error"),
  134. SuccessMsg: vals.Get("success"),
  135. InfoMsg: vals.Get("info"),
  136. WarningMsg: vals.Get("warning"),
  137. }
  138. }
  139. // prepare an empty Flash message for current request
  140. ctx.Flash = &middleware.Flash{DataStore: ctx, Values: url.Values{}}
  141. ctx.Resp.Before(func(resp ResponseWriter) {
  142. if val := ctx.Flash.Encode(); val != "" {
  143. middleware.SetSiteCookie(ctx.Resp, CookieNameFlash, val, 0)
  144. } else if lastFlashCookie != "" {
  145. middleware.SetSiteCookie(ctx.Resp, CookieNameFlash, "", -1)
  146. }
  147. })
  148. // If request sends files, parse them here otherwise the Query() can't be parsed and the CsrfToken will be invalid.
  149. if ctx.Req.Method == "POST" && strings.Contains(ctx.Req.Header.Get("Content-Type"), "multipart/form-data") {
  150. if err := ctx.Req.ParseMultipartForm(setting.Attachment.MaxSize << 20); err != nil && !strings.Contains(err.Error(), "EOF") { // 32MB max size
  151. ctx.ServerError("ParseMultipartForm", err)
  152. return
  153. }
  154. }
  155. httpcache.SetCacheControlInHeader(ctx.Resp.Header(), 0, "no-transform")
  156. ctx.Resp.Header().Set(`X-Frame-Options`, setting.CORSConfig.XFrameOptions)
  157. ctx.Data["CsrfToken"] = ctx.Csrf.GetToken()
  158. ctx.Data["CsrfTokenHtml"] = template.HTML(`<input type="hidden" name="_csrf" value="` + ctx.Data["CsrfToken"].(string) + `">`)
  159. // FIXME: do we really always need these setting? There should be someway to have to avoid having to always set these
  160. ctx.Data["DisableMigrations"] = setting.Repository.DisableMigrations
  161. ctx.Data["DisableStars"] = setting.Repository.DisableStars
  162. ctx.Data["EnableActions"] = setting.Actions.Enabled
  163. ctx.Data["ManifestData"] = setting.ManifestData
  164. ctx.Data["UnitWikiGlobalDisabled"] = unit.TypeWiki.UnitGlobalDisabled()
  165. ctx.Data["UnitIssuesGlobalDisabled"] = unit.TypeIssues.UnitGlobalDisabled()
  166. ctx.Data["UnitPullsGlobalDisabled"] = unit.TypePullRequests.UnitGlobalDisabled()
  167. ctx.Data["UnitProjectsGlobalDisabled"] = unit.TypeProjects.UnitGlobalDisabled()
  168. ctx.Data["UnitActionsGlobalDisabled"] = unit.TypeActions.UnitGlobalDisabled()
  169. ctx.Data["AllLangs"] = translation.AllLangs()
  170. next.ServeHTTP(ctx.Resp, ctx.Req)
  171. })
  172. }
  173. }
  174. // HasError returns true if error occurs in form validation.
  175. // Attention: this function changes ctx.Data and ctx.Flash
  176. func (ctx *Context) HasError() bool {
  177. hasErr, ok := ctx.Data["HasError"]
  178. if !ok {
  179. return false
  180. }
  181. ctx.Flash.ErrorMsg = ctx.GetErrMsg()
  182. ctx.Data["Flash"] = ctx.Flash
  183. return hasErr.(bool)
  184. }
  185. // GetErrMsg returns error message in form validation.
  186. func (ctx *Context) GetErrMsg() string {
  187. msg, _ := ctx.Data["ErrorMsg"].(string)
  188. if msg == "" {
  189. msg = "invalid form data"
  190. }
  191. return msg
  192. }