gitea源码

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  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/httpcache"
  19. "code.gitea.io/gitea/modules/session"
  20. "code.gitea.io/gitea/modules/setting"
  21. "code.gitea.io/gitea/modules/templates"
  22. "code.gitea.io/gitea/modules/translation"
  23. "code.gitea.io/gitea/modules/util"
  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 templates.TplName, data any, templateCtx context.Context) error
  32. }
  33. // Context represents context of a web request.
  34. // ATTENTION: This struct should never be manually constructed in routes/services,
  35. // it has many internal details which should be carefully prepared by the framework.
  36. // If it is abused, it would cause strange bugs like panic/resource-leak.
  37. type Context struct {
  38. *Base
  39. TemplateContext TemplateContext
  40. Render Render
  41. PageData map[string]any // data used by JavaScript modules in one page, it's `window.config.pageData`
  42. Cache cache.StringCache
  43. Csrf CSRFProtector
  44. Flash *middleware.Flash
  45. Session session.Store
  46. Link string // current request URL (without query string)
  47. Doer *user_model.User // current signed-in user
  48. IsSigned bool
  49. IsBasicAuth bool
  50. ContextUser *user_model.User // the user which is being visited, in most cases it differs from Doer
  51. Repo *Repository
  52. Org *Organization
  53. Package *Package
  54. }
  55. type TemplateContext map[string]any
  56. func init() {
  57. web.RegisterResponseStatusProvider[*Base](func(req *http.Request) web_types.ResponseStatusProvider {
  58. return req.Context().Value(BaseContextKey).(*Base)
  59. })
  60. web.RegisterResponseStatusProvider[*Context](func(req *http.Request) web_types.ResponseStatusProvider {
  61. return req.Context().Value(WebContextKey).(*Context)
  62. })
  63. }
  64. type webContextKeyType struct{}
  65. var WebContextKey = webContextKeyType{}
  66. func GetWebContext(ctx context.Context) *Context {
  67. webCtx, _ := ctx.Value(WebContextKey).(*Context)
  68. return webCtx
  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. func NewTemplateContextForWeb(ctx *Context) TemplateContext {
  86. tmplCtx := NewTemplateContext(ctx)
  87. tmplCtx["Locale"] = ctx.Base.Locale
  88. tmplCtx["AvatarUtils"] = templates.NewAvatarUtils(ctx)
  89. tmplCtx["RenderUtils"] = templates.NewRenderUtils(ctx)
  90. tmplCtx["RootData"] = ctx.Data
  91. tmplCtx["Consts"] = map[string]any{
  92. "RepoUnitTypeCode": unit.TypeCode,
  93. "RepoUnitTypeIssues": unit.TypeIssues,
  94. "RepoUnitTypePullRequests": unit.TypePullRequests,
  95. "RepoUnitTypeReleases": unit.TypeReleases,
  96. "RepoUnitTypeWiki": unit.TypeWiki,
  97. "RepoUnitTypeExternalWiki": unit.TypeExternalWiki,
  98. "RepoUnitTypeExternalTracker": unit.TypeExternalTracker,
  99. "RepoUnitTypeProjects": unit.TypeProjects,
  100. "RepoUnitTypePackages": unit.TypePackages,
  101. "RepoUnitTypeActions": unit.TypeActions,
  102. }
  103. return tmplCtx
  104. }
  105. func NewWebContext(base *Base, render Render, session session.Store) *Context {
  106. ctx := &Context{
  107. Base: base,
  108. Render: render,
  109. Session: session,
  110. Cache: cache.GetCache(),
  111. Link: setting.AppSubURL + strings.TrimSuffix(base.Req.URL.EscapedPath(), "/"),
  112. Repo: &Repository{PullRequest: &PullRequest{}},
  113. Org: &Organization{},
  114. }
  115. ctx.TemplateContext = NewTemplateContextForWeb(ctx)
  116. ctx.Flash = &middleware.Flash{DataStore: ctx, Values: url.Values{}}
  117. ctx.SetContextValue(WebContextKey, ctx)
  118. return ctx
  119. }
  120. // Contexter initializes a classic context for a request.
  121. func Contexter() func(next http.Handler) http.Handler {
  122. rnd := templates.HTMLRenderer()
  123. csrfOpts := CsrfOptions{
  124. Secret: hex.EncodeToString(setting.GetGeneralTokenSigningSecret()),
  125. Cookie: setting.CSRFCookieName,
  126. Secure: setting.SessionConfig.Secure,
  127. CookieHTTPOnly: setting.CSRFCookieHTTPOnly,
  128. CookieDomain: setting.SessionConfig.Domain,
  129. CookiePath: setting.SessionConfig.CookiePath,
  130. SameSite: setting.SessionConfig.SameSite,
  131. }
  132. if !setting.IsProd {
  133. CsrfTokenRegenerationInterval = 5 * time.Second // in dev, re-generate the tokens more aggressively for debug purpose
  134. }
  135. return func(next http.Handler) http.Handler {
  136. return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
  137. base := NewBaseContext(resp, req)
  138. ctx := NewWebContext(base, rnd, session.GetContextSession(req))
  139. ctx.Data.MergeFrom(middleware.CommonTemplateContextData())
  140. ctx.Data["CurrentURL"] = setting.AppSubURL + req.URL.RequestURI()
  141. ctx.Data["Link"] = ctx.Link
  142. // PageData is passed by reference, and it will be rendered to `window.config.pageData` in `head.tmpl` for JavaScript modules
  143. ctx.PageData = map[string]any{}
  144. ctx.Data["PageData"] = ctx.PageData
  145. ctx.Csrf = NewCSRFProtector(csrfOpts)
  146. // get the last flash message from cookie
  147. lastFlashCookie, lastFlashMsg := middleware.GetSiteCookieFlashMessage(ctx, ctx.Req, CookieNameFlash)
  148. if vals, _ := url.ParseQuery(lastFlashCookie); len(vals) > 0 {
  149. ctx.Data["Flash"] = lastFlashMsg // store last Flash message into the template data, to render it
  150. }
  151. // if there are new messages in the ctx.Flash, write them into cookie
  152. ctx.Resp.Before(func(resp ResponseWriter) {
  153. if val := ctx.Flash.Encode(); val != "" {
  154. middleware.SetSiteCookie(ctx.Resp, CookieNameFlash, val, 0)
  155. } else if lastFlashCookie != "" {
  156. middleware.SetSiteCookie(ctx.Resp, CookieNameFlash, "", -1)
  157. }
  158. })
  159. // If request sends files, parse them here otherwise the Query() can't be parsed and the CsrfToken will be invalid.
  160. if ctx.Req.Method == http.MethodPost && strings.Contains(ctx.Req.Header.Get("Content-Type"), "multipart/form-data") {
  161. if err := ctx.Req.ParseMultipartForm(setting.Attachment.MaxSize << 20); err != nil && !strings.Contains(err.Error(), "EOF") { // 32MB max size
  162. ctx.ServerError("ParseMultipartForm", err)
  163. return
  164. }
  165. }
  166. httpcache.SetCacheControlInHeader(ctx.Resp.Header(), &httpcache.CacheControlOptions{NoTransform: true})
  167. ctx.Resp.Header().Set(`X-Frame-Options`, setting.CORSConfig.XFrameOptions)
  168. ctx.Data["SystemConfig"] = setting.Config()
  169. ctx.Data["ShowTwoFactorRequiredMessage"] = ctx.DoerNeedTwoFactorAuth()
  170. // FIXME: do we really always need these setting? There should be someway to have to avoid having to always set these
  171. ctx.Data["DisableMigrations"] = setting.Repository.DisableMigrations
  172. ctx.Data["DisableStars"] = setting.Repository.DisableStars
  173. ctx.Data["EnableActions"] = setting.Actions.Enabled && !unit.TypeActions.UnitGlobalDisabled()
  174. ctx.Data["ManifestData"] = setting.ManifestData
  175. ctx.Data["AllLangs"] = translation.AllLangs()
  176. next.ServeHTTP(ctx.Resp, ctx.Req)
  177. })
  178. }
  179. }
  180. func (ctx *Context) DoerNeedTwoFactorAuth() bool {
  181. if !setting.TwoFactorAuthEnforced {
  182. return false
  183. }
  184. return ctx.Session.Get(session.KeyUserHasTwoFactorAuth) == false
  185. }
  186. // HasError returns true if error occurs in form validation.
  187. // Attention: this function changes ctx.Data and ctx.Flash
  188. // If HasError is called, then before Redirect, the error message should be stored by ctx.Flash.Error(ctx.GetErrMsg()) again.
  189. func (ctx *Context) HasError() bool {
  190. hasErr, _ := ctx.Data["HasError"].(bool)
  191. hasErr = hasErr || ctx.Flash.ErrorMsg != ""
  192. if !hasErr {
  193. return false
  194. }
  195. if ctx.Flash.ErrorMsg == "" {
  196. ctx.Flash.ErrorMsg = ctx.GetErrMsg()
  197. }
  198. ctx.Data["Flash"] = ctx.Flash
  199. return hasErr
  200. }
  201. // GetErrMsg returns error message in form validation.
  202. func (ctx *Context) GetErrMsg() string {
  203. msg, _ := ctx.Data["ErrorMsg"].(string)
  204. if msg == "" {
  205. msg = "invalid form data"
  206. }
  207. return msg
  208. }
  209. func (ctx *Context) JSONRedirect(redirect string) {
  210. ctx.JSON(http.StatusOK, map[string]any{"redirect": redirect})
  211. }
  212. func (ctx *Context) JSONOK() {
  213. ctx.JSON(http.StatusOK, map[string]any{"ok": true}) // this is only a dummy response, frontend seldom uses it
  214. }
  215. func (ctx *Context) JSONError(msg any) {
  216. switch v := msg.(type) {
  217. case string:
  218. ctx.JSON(http.StatusBadRequest, map[string]any{"errorMessage": v, "renderFormat": "text"})
  219. case template.HTML:
  220. ctx.JSON(http.StatusBadRequest, map[string]any{"errorMessage": v, "renderFormat": "html"})
  221. default:
  222. panic(fmt.Sprintf("unsupported type: %T", msg))
  223. }
  224. }
  225. func (ctx *Context) JSONErrorNotFound(optMsg ...string) {
  226. msg := util.OptionalArg(optMsg)
  227. if msg == "" {
  228. msg = ctx.Locale.TrString("error.not_found")
  229. }
  230. ctx.JSON(http.StatusNotFound, map[string]any{"errorMessage": msg, "renderFormat": "text"})
  231. }