2014-03-15 13:01:50 +02:00
|
|
|
// Copyright 2014 The Gogs Authors. All rights reserved.
|
2020-01-09 23:34:25 +02:00
|
|
|
// Copyright 2020 The Gitea Authors. All rights reserved.
|
2022-11-27 20:20:29 +02:00
|
|
|
// SPDX-License-Identifier: MIT
|
2014-03-15 13:01:50 +02:00
|
|
|
|
2016-03-11 18:56:52 +02:00
|
|
|
package context
|
2014-03-15 13:01:50 +02:00
|
|
|
|
|
|
|
import (
|
2023-08-08 04:22:47 +03:00
|
|
|
"context"
|
2024-02-18 19:39:04 +02:00
|
|
|
"encoding/hex"
|
2024-02-14 23:48:45 +02:00
|
|
|
"fmt"
|
2014-03-22 19:44:02 +02:00
|
|
|
"html/template"
|
2014-04-15 19:27:29 +03:00
|
|
|
"io"
|
2014-03-15 13:01:50 +02:00
|
|
|
"net/http"
|
2018-03-15 23:13:34 +02:00
|
|
|
"net/url"
|
2014-03-22 22:40:09 +02:00
|
|
|
"strings"
|
2014-03-19 15:57:55 +02:00
|
|
|
"time"
|
2014-03-15 13:01:50 +02:00
|
|
|
|
2021-11-09 21:57:58 +02:00
|
|
|
"code.gitea.io/gitea/models/unit"
|
2021-11-24 11:49:20 +02:00
|
|
|
user_model "code.gitea.io/gitea/models/user"
|
2024-04-13 11:38:44 +03:00
|
|
|
"code.gitea.io/gitea/modules/cache"
|
2022-07-23 09:38:03 +03:00
|
|
|
"code.gitea.io/gitea/modules/httpcache"
|
2024-04-25 14:22:32 +03:00
|
|
|
"code.gitea.io/gitea/modules/session"
|
2016-11-10 18:24:48 +02:00
|
|
|
"code.gitea.io/gitea/modules/setting"
|
2021-01-26 17:36:53 +02:00
|
|
|
"code.gitea.io/gitea/modules/templates"
|
|
|
|
"code.gitea.io/gitea/modules/translation"
|
2023-06-18 10:59:09 +03:00
|
|
|
"code.gitea.io/gitea/modules/web"
|
2021-01-30 10:55:53 +02:00
|
|
|
"code.gitea.io/gitea/modules/web/middleware"
|
2023-06-18 10:59:09 +03:00
|
|
|
web_types "code.gitea.io/gitea/modules/web/types"
|
2014-03-15 13:01:50 +02:00
|
|
|
)
|
|
|
|
|
2021-01-26 17:36:53 +02:00
|
|
|
// Render represents a template render
|
|
|
|
type Render interface {
|
2023-08-08 04:22:47 +03:00
|
|
|
TemplateLookup(tmpl string, templateCtx context.Context) (templates.TemplateExecutor, error)
|
2024-12-22 17:33:19 +02:00
|
|
|
HTML(w io.Writer, status int, name templates.TplName, data any, templateCtx context.Context) error
|
2021-01-26 17:36:53 +02:00
|
|
|
}
|
|
|
|
|
2014-03-15 15:17:16 +02:00
|
|
|
// Context represents context of a request.
|
2014-03-15 13:01:50 +02:00
|
|
|
type Context struct {
|
2023-05-21 04:50:53 +03:00
|
|
|
*Base
|
2023-05-09 02:30:14 +03:00
|
|
|
|
2023-08-08 04:22:47 +03:00
|
|
|
TemplateContext TemplateContext
|
|
|
|
|
2023-05-21 04:50:53 +03:00
|
|
|
Render Render
|
|
|
|
PageData map[string]any // data used by JavaScript modules in one page, it's `window.config.pageData`
|
2014-07-26 07:24:27 +03:00
|
|
|
|
2024-04-13 11:38:44 +03:00
|
|
|
Cache cache.StringCache
|
2023-05-09 02:30:14 +03:00
|
|
|
Csrf CSRFProtector
|
|
|
|
Flash *middleware.Flash
|
|
|
|
Session session.Store
|
|
|
|
|
2023-05-21 04:50:53 +03:00
|
|
|
Link string // current request URL (without query string)
|
|
|
|
|
|
|
|
Doer *user_model.User // current signed-in user
|
2014-11-18 18:07:16 +02:00
|
|
|
IsSigned bool
|
|
|
|
IsBasicAuth bool
|
2014-03-15 18:03:23 +02:00
|
|
|
|
2023-05-21 04:50:53 +03:00
|
|
|
ContextUser *user_model.User // the user which is being visited, in most cases it differs from Doer
|
2014-03-15 13:01:50 +02:00
|
|
|
|
2023-05-21 04:50:53 +03:00
|
|
|
Repo *Repository
|
|
|
|
Org *Organization
|
|
|
|
Package *Package
|
2022-05-05 17:13:23 +03:00
|
|
|
}
|
|
|
|
|
2023-08-08 04:22:47 +03:00
|
|
|
type TemplateContext map[string]any
|
|
|
|
|
2023-06-18 10:59:09 +03:00
|
|
|
func init() {
|
2024-11-13 10:58:09 +02:00
|
|
|
web.RegisterResponseStatusProvider[*Base](func(req *http.Request) web_types.ResponseStatusProvider {
|
|
|
|
return req.Context().Value(BaseContextKey).(*Base)
|
|
|
|
})
|
2023-06-18 10:59:09 +03:00
|
|
|
web.RegisterResponseStatusProvider[*Context](func(req *http.Request) web_types.ResponseStatusProvider {
|
|
|
|
return req.Context().Value(WebContextKey).(*Context)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2023-05-23 04:29:15 +03:00
|
|
|
type webContextKeyType struct{}
|
2021-12-15 08:59:57 +02:00
|
|
|
|
2023-05-23 04:29:15 +03:00
|
|
|
var WebContextKey = webContextKeyType{}
|
2021-01-26 17:36:53 +02:00
|
|
|
|
2023-05-23 04:29:15 +03:00
|
|
|
func GetWebContext(req *http.Request) *Context {
|
|
|
|
ctx, _ := req.Context().Value(WebContextKey).(*Context)
|
2023-05-21 04:50:53 +03:00
|
|
|
return ctx
|
2021-01-26 17:36:53 +02:00
|
|
|
}
|
|
|
|
|
2023-05-21 04:50:53 +03:00
|
|
|
// ValidateContext is a special context for form validation middleware. It may be different from other contexts.
|
|
|
|
type ValidateContext struct {
|
|
|
|
*Base
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetValidateContext gets a context for middleware form validation
|
|
|
|
func GetValidateContext(req *http.Request) (ctx *ValidateContext) {
|
|
|
|
if ctxAPI, ok := req.Context().Value(apiContextKey).(*APIContext); ok {
|
|
|
|
ctx = &ValidateContext{Base: ctxAPI.Base}
|
2023-05-23 04:29:15 +03:00
|
|
|
} else if ctxWeb, ok := req.Context().Value(WebContextKey).(*Context); ok {
|
2023-05-21 04:50:53 +03:00
|
|
|
ctx = &ValidateContext{Base: ctxWeb.Base}
|
|
|
|
} else {
|
|
|
|
panic("invalid context, expect either APIContext or Context")
|
2023-04-13 22:45:33 +03:00
|
|
|
}
|
2023-05-21 04:50:53 +03:00
|
|
|
return ctx
|
2021-01-26 17:36:53 +02:00
|
|
|
}
|
|
|
|
|
2023-08-25 14:07:42 +03:00
|
|
|
func NewTemplateContextForWeb(ctx *Context) TemplateContext {
|
|
|
|
tmplCtx := NewTemplateContext(ctx)
|
|
|
|
tmplCtx["Locale"] = ctx.Base.Locale
|
|
|
|
tmplCtx["AvatarUtils"] = templates.NewAvatarUtils(ctx)
|
2024-11-05 08:04:26 +02:00
|
|
|
tmplCtx["RenderUtils"] = templates.NewRenderUtils(ctx)
|
2024-04-14 13:44:11 +03:00
|
|
|
tmplCtx["RootData"] = ctx.Data
|
2024-04-17 11:31:37 +03:00
|
|
|
tmplCtx["Consts"] = map[string]any{
|
|
|
|
"RepoUnitTypeCode": unit.TypeCode,
|
|
|
|
"RepoUnitTypeIssues": unit.TypeIssues,
|
|
|
|
"RepoUnitTypePullRequests": unit.TypePullRequests,
|
|
|
|
"RepoUnitTypeReleases": unit.TypeReleases,
|
|
|
|
"RepoUnitTypeWiki": unit.TypeWiki,
|
|
|
|
"RepoUnitTypeExternalWiki": unit.TypeExternalWiki,
|
|
|
|
"RepoUnitTypeExternalTracker": unit.TypeExternalTracker,
|
|
|
|
"RepoUnitTypeProjects": unit.TypeProjects,
|
|
|
|
"RepoUnitTypePackages": unit.TypePackages,
|
|
|
|
"RepoUnitTypeActions": unit.TypeActions,
|
|
|
|
}
|
2023-08-25 14:07:42 +03:00
|
|
|
return tmplCtx
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewWebContext(base *Base, render Render, session session.Store) *Context {
|
|
|
|
ctx := &Context{
|
|
|
|
Base: base,
|
|
|
|
Render: render,
|
|
|
|
Session: session,
|
|
|
|
|
2024-04-13 11:38:44 +03:00
|
|
|
Cache: cache.GetCache(),
|
2023-08-25 14:07:42 +03:00
|
|
|
Link: setting.AppSubURL + strings.TrimSuffix(base.Req.URL.EscapedPath(), "/"),
|
|
|
|
Repo: &Repository{PullRequest: &PullRequest{}},
|
|
|
|
Org: &Organization{},
|
|
|
|
}
|
|
|
|
ctx.TemplateContext = NewTemplateContextForWeb(ctx)
|
|
|
|
ctx.Flash = &middleware.Flash{DataStore: ctx, Values: url.Values{}}
|
|
|
|
return ctx
|
|
|
|
}
|
|
|
|
|
2023-05-08 12:36:54 +03:00
|
|
|
// Contexter initializes a classic context for a request.
|
|
|
|
func Contexter() func(next http.Handler) http.Handler {
|
|
|
|
rnd := templates.HTMLRenderer()
|
|
|
|
csrfOpts := CsrfOptions{
|
2024-02-18 19:39:04 +02:00
|
|
|
Secret: hex.EncodeToString(setting.GetGeneralTokenSigningSecret()),
|
2021-01-26 17:36:53 +02:00
|
|
|
Cookie: setting.CSRFCookieName,
|
|
|
|
Secure: setting.SessionConfig.Secure,
|
|
|
|
CookieHTTPOnly: setting.CSRFCookieHTTPOnly,
|
|
|
|
CookieDomain: setting.SessionConfig.Domain,
|
|
|
|
CookiePath: setting.SessionConfig.CookiePath,
|
2021-03-07 10:12:43 +02:00
|
|
|
SameSite: setting.SessionConfig.SameSite,
|
2021-01-26 17:36:53 +02:00
|
|
|
}
|
2022-04-08 08:21:05 +03:00
|
|
|
if !setting.IsProd {
|
|
|
|
CsrfTokenRegenerationInterval = 5 * time.Second // in dev, re-generate the tokens more aggressively for debug purpose
|
|
|
|
}
|
2021-01-26 17:36:53 +02:00
|
|
|
return func(next http.Handler) http.Handler {
|
|
|
|
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
|
2024-12-24 05:43:57 +02:00
|
|
|
base := NewBaseContext(resp, req)
|
2024-04-25 14:22:32 +03:00
|
|
|
ctx := NewWebContext(base, rnd, session.GetContextSession(req))
|
2023-05-04 09:36:34 +03:00
|
|
|
ctx.Data.MergeFrom(middleware.CommonTemplateContextData())
|
|
|
|
ctx.Data["CurrentURL"] = setting.AppSubURL + req.URL.RequestURI()
|
|
|
|
ctx.Data["Link"] = ctx.Link
|
|
|
|
|
2021-10-15 05:35:26 +03:00
|
|
|
// PageData is passed by reference, and it will be rendered to `window.config.pageData` in `head.tmpl` for JavaScript modules
|
2023-05-04 09:36:34 +03:00
|
|
|
ctx.PageData = map[string]any{}
|
2021-10-12 21:11:35 +03:00
|
|
|
ctx.Data["PageData"] = ctx.PageData
|
2021-01-26 17:36:53 +02:00
|
|
|
|
2024-12-24 05:43:57 +02:00
|
|
|
ctx.Base.SetContextValue(WebContextKey, ctx)
|
2024-09-18 10:17:25 +03:00
|
|
|
ctx.Csrf = NewCSRFProtector(csrfOpts)
|
2021-01-26 17:36:53 +02:00
|
|
|
|
2025-01-25 16:36:47 +02:00
|
|
|
// get the last flash message from cookie
|
|
|
|
lastFlashCookie, lastFlashMsg := middleware.GetSiteCookieFlashMessage(ctx, ctx.Req, CookieNameFlash)
|
2023-04-13 22:45:33 +03:00
|
|
|
if vals, _ := url.ParseQuery(lastFlashCookie); len(vals) > 0 {
|
2025-01-25 16:36:47 +02:00
|
|
|
ctx.Data["Flash"] = lastFlashMsg // store last Flash message into the template data, to render it
|
2021-01-26 17:36:53 +02:00
|
|
|
}
|
|
|
|
|
2023-08-25 14:07:42 +03:00
|
|
|
// if there are new messages in the ctx.Flash, write them into cookie
|
2021-01-26 17:36:53 +02:00
|
|
|
ctx.Resp.Before(func(resp ResponseWriter) {
|
2023-04-13 22:45:33 +03:00
|
|
|
if val := ctx.Flash.Encode(); val != "" {
|
|
|
|
middleware.SetSiteCookie(ctx.Resp, CookieNameFlash, val, 0)
|
|
|
|
} else if lastFlashCookie != "" {
|
|
|
|
middleware.SetSiteCookie(ctx.Resp, CookieNameFlash, "", -1)
|
2021-01-26 17:36:53 +02:00
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
// If request sends files, parse them here otherwise the Query() can't be parsed and the CsrfToken will be invalid.
|
|
|
|
if ctx.Req.Method == "POST" && strings.Contains(ctx.Req.Header.Get("Content-Type"), "multipart/form-data") {
|
|
|
|
if err := ctx.Req.ParseMultipartForm(setting.Attachment.MaxSize << 20); err != nil && !strings.Contains(err.Error(), "EOF") { // 32MB max size
|
|
|
|
ctx.ServerError("ParseMultipartForm", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
2018-08-27 05:23:27 +03:00
|
|
|
|
2023-03-08 22:40:04 +02:00
|
|
|
httpcache.SetCacheControlInHeader(ctx.Resp.Header(), 0, "no-transform")
|
2021-08-06 23:47:10 +03:00
|
|
|
ctx.Resp.Header().Set(`X-Frame-Options`, setting.CORSConfig.XFrameOptions)
|
2021-01-26 17:36:53 +02:00
|
|
|
|
2024-02-24 15:12:17 +02:00
|
|
|
ctx.Data["SystemConfig"] = setting.Config()
|
2021-01-26 17:36:53 +02:00
|
|
|
|
2021-05-05 00:48:31 +03:00
|
|
|
// FIXME: do we really always need these setting? There should be someway to have to avoid having to always set these
|
2021-01-26 17:36:53 +02:00
|
|
|
ctx.Data["DisableMigrations"] = setting.Repository.DisableMigrations
|
2021-04-15 19:53:57 +03:00
|
|
|
ctx.Data["DisableStars"] = setting.Repository.DisableStars
|
2024-06-18 03:51:13 +03:00
|
|
|
ctx.Data["EnableActions"] = setting.Actions.Enabled && !unit.TypeActions.UnitGlobalDisabled()
|
2021-01-26 17:36:53 +02:00
|
|
|
|
|
|
|
ctx.Data["ManifestData"] = setting.ManifestData
|
|
|
|
ctx.Data["AllLangs"] = translation.AllLangs()
|
2020-12-22 13:13:50 +02:00
|
|
|
|
2021-01-26 17:36:53 +02:00
|
|
|
next.ServeHTTP(ctx.Resp, ctx.Req)
|
|
|
|
})
|
2014-03-15 13:01:50 +02:00
|
|
|
}
|
|
|
|
}
|
2023-05-21 04:50:53 +03:00
|
|
|
|
|
|
|
// HasError returns true if error occurs in form validation.
|
|
|
|
// Attention: this function changes ctx.Data and ctx.Flash
|
2024-04-23 19:18:41 +03:00
|
|
|
// If HasError is called, then before Redirect, the error message should be stored by ctx.Flash.Error(ctx.GetErrMsg()) again.
|
2023-05-21 04:50:53 +03:00
|
|
|
func (ctx *Context) HasError() bool {
|
|
|
|
hasErr, ok := ctx.Data["HasError"]
|
|
|
|
if !ok {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
ctx.Flash.ErrorMsg = ctx.GetErrMsg()
|
|
|
|
ctx.Data["Flash"] = ctx.Flash
|
|
|
|
return hasErr.(bool)
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetErrMsg returns error message in form validation.
|
|
|
|
func (ctx *Context) GetErrMsg() string {
|
|
|
|
msg, _ := ctx.Data["ErrorMsg"].(string)
|
|
|
|
if msg == "" {
|
|
|
|
msg = "invalid form data"
|
|
|
|
}
|
|
|
|
return msg
|
|
|
|
}
|
2023-07-26 09:04:01 +03:00
|
|
|
|
|
|
|
func (ctx *Context) JSONRedirect(redirect string) {
|
|
|
|
ctx.JSON(http.StatusOK, map[string]any{"redirect": redirect})
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ctx *Context) JSONOK() {
|
|
|
|
ctx.JSON(http.StatusOK, map[string]any{"ok": true}) // this is only a dummy response, frontend seldom uses it
|
|
|
|
}
|
|
|
|
|
2024-02-14 23:48:45 +02:00
|
|
|
func (ctx *Context) JSONError(msg any) {
|
|
|
|
switch v := msg.(type) {
|
|
|
|
case string:
|
|
|
|
ctx.JSON(http.StatusBadRequest, map[string]any{"errorMessage": v, "renderFormat": "text"})
|
|
|
|
case template.HTML:
|
|
|
|
ctx.JSON(http.StatusBadRequest, map[string]any{"errorMessage": v, "renderFormat": "html"})
|
|
|
|
default:
|
|
|
|
panic(fmt.Sprintf("unsupported type: %T", msg))
|
|
|
|
}
|
2023-07-26 09:04:01 +03:00
|
|
|
}
|