Files
Atay-Makhzan/modules/context/context.go
T

259 lines
8.6 KiB
Go
Raw Normal View History

// Copyright 2014 The Gogs Authors. All rights reserved.
2020-01-09 21:34:25 +00:00
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
2016-03-11 11:56:52 -05:00
package context
import (
"context"
"html"
2014-03-23 01:44:02 +08:00
"html/template"
2014-04-16 00:27:29 +08:00
"io"
"net/http"
"net/url"
2014-03-22 16:40:09 -04:00
"strings"
2014-03-19 21:57:55 +08:00
"time"
2021-11-10 03:57:58 +08:00
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
2021-01-27 22:56:54 +08:00
mc "code.gitea.io/gitea/modules/cache"
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/httpcache"
"code.gitea.io/gitea/modules/setting"
2021-01-26 23:36:53 +08:00
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/translation"
"code.gitea.io/gitea/modules/web"
2021-01-30 16:55:53 +08:00
"code.gitea.io/gitea/modules/web/middleware"
web_types "code.gitea.io/gitea/modules/web/types"
2019-08-23 09:40:30 -07:00
2021-01-26 23:36:53 +08:00
"gitea.com/go-chi/cache"
"gitea.com/go-chi/session"
)
2021-01-26 23:36:53 +08:00
// Render represents a template render
type Render interface {
TemplateLookup(tmpl string, templateCtx context.Context) (templates.TemplateExecutor, error)
HTML(w io.Writer, status int, name string, data any, templateCtx context.Context) error
2021-01-26 23:36:53 +08:00
}
2014-03-15 09:17:16 -04:00
// Context represents context of a request.
type Context struct {
*Base
TemplateContext TemplateContext
Render Render
PageData map[string]any // data used by JavaScript modules in one page, it's `window.config.pageData`
2014-07-26 00:24:27 -04:00
Cache cache.Cache
Csrf CSRFProtector
Flash *middleware.Flash
Session session.Store
Link string // current request URL (without query string)
Doer *user_model.User // current signed-in user
2014-11-18 11:07:16 -05:00
IsSigned bool
IsBasicAuth bool
2014-03-16 00:03:23 +08:00
ContextUser *user_model.User // the user which is being visited, in most cases it differs from Doer
Repo *Repository
Org *Organization
Package *Package
}
type TemplateContext map[string]any
func init() {
web.RegisterResponseStatusProvider[*Context](func(req *http.Request) web_types.ResponseStatusProvider {
return req.Context().Value(WebContextKey).(*Context)
})
}
// TrHTMLEscapeArgs runs ".Locale.Tr()" but pre-escapes all arguments with html.EscapeString.
2021-11-16 18:18:25 +00:00
// This is useful if the locale message is intended to only produce HTML content.
func (ctx *Context) TrHTMLEscapeArgs(msg string, args ...string) string {
2023-07-04 20:36:08 +02:00
trArgs := make([]any, len(args))
2021-11-16 18:18:25 +00:00
for i, arg := range args {
trArgs[i] = html.EscapeString(arg)
}
return ctx.Locale.Tr(msg, trArgs...)
}
type webContextKeyType struct{}
2021-12-15 14:59:57 +08:00
var WebContextKey = webContextKeyType{}
2021-01-26 23:36:53 +08:00
func GetWebContext(req *http.Request) *Context {
ctx, _ := req.Context().Value(WebContextKey).(*Context)
return ctx
2021-01-26 23:36:53 +08: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}
} else if ctxWeb, ok := req.Context().Value(WebContextKey).(*Context); ok {
ctx = &ValidateContext{Base: ctxWeb.Base}
} else {
panic("invalid context, expect either APIContext or Context")
2023-04-14 03:45:33 +08:00
}
return ctx
2021-01-26 23:36:53 +08:00
}
func NewTemplateContextForWeb(ctx *Context) TemplateContext {
tmplCtx := NewTemplateContext(ctx)
tmplCtx["Locale"] = ctx.Base.Locale
tmplCtx["AvatarUtils"] = templates.NewAvatarUtils(ctx)
return tmplCtx
}
func NewWebContext(base *Base, render Render, session session.Store) *Context {
ctx := &Context{
Base: base,
Render: render,
Session: session,
Cache: mc.GetCache(),
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
}
// Contexter initializes a classic context for a request.
func Contexter() func(next http.Handler) http.Handler {
rnd := templates.HTMLRenderer()
csrfOpts := CsrfOptions{
2021-01-26 23:36:53 +08:00
Secret: setting.SecretKey,
Cookie: setting.CSRFCookieName,
SetCookie: true,
Secure: setting.SessionConfig.Secure,
CookieHTTPOnly: setting.CSRFCookieHTTPOnly,
Header: "X-Csrf-Token",
CookieDomain: setting.SessionConfig.Domain,
CookiePath: setting.SessionConfig.CookiePath,
2021-03-07 08:12:43 +00:00
SameSite: setting.SessionConfig.SameSite,
2021-01-26 23:36:53 +08:00
}
if !setting.IsProd {
CsrfTokenRegenerationInterval = 5 * time.Second // in dev, re-generate the tokens more aggressively for debug purpose
}
2021-01-26 23:36:53 +08:00
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
base, baseCleanUp := NewBaseContext(resp, req)
defer baseCleanUp()
ctx := NewWebContext(base, rnd, session.GetSession(req))
ctx.Data.MergeFrom(middleware.CommonTemplateContextData())
ctx.Data["Context"] = ctx // TODO: use "ctx" in template and remove this
ctx.Data["CurrentURL"] = setting.AppSubURL + req.URL.RequestURI()
ctx.Data["Link"] = ctx.Link
// PageData is passed by reference, and it will be rendered to `window.config.pageData` in `head.tmpl` for JavaScript modules
ctx.PageData = map[string]any{}
ctx.Data["PageData"] = ctx.PageData
2021-01-26 23:36:53 +08:00
ctx.Base.AppendContextValue(WebContextKey, ctx)
ctx.Base.AppendContextValueFunc(gitrepo.RepositoryContextKey, func() any { return ctx.Repo.GitRepo })
ctx.Csrf = PrepareCSRFProtector(csrfOpts, ctx)
2021-01-26 23:36:53 +08:00
2023-04-14 03:45:33 +08:00
// Get the last flash message from cookie
lastFlashCookie := middleware.GetSiteCookie(ctx.Req, CookieNameFlash)
if vals, _ := url.ParseQuery(lastFlashCookie); len(vals) > 0 {
// store last Flash message into the template data, to render it
ctx.Data["Flash"] = &middleware.Flash{
DataStore: ctx,
2021-01-26 23:36:53 +08:00
Values: vals,
ErrorMsg: vals.Get("error"),
SuccessMsg: vals.Get("success"),
InfoMsg: vals.Get("info"),
WarningMsg: vals.Get("warning"),
}
}
// if there are new messages in the ctx.Flash, write them into cookie
2021-01-26 23:36:53 +08:00
ctx.Resp.Before(func(resp ResponseWriter) {
2023-04-14 03:45:33 +08: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 23:36:53 +08: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
}
}
2023-03-08 22:40:04 +02:00
httpcache.SetCacheControlInHeader(ctx.Resp.Header(), 0, "no-transform")
2021-08-06 21:47:10 +01:00
ctx.Resp.Header().Set(`X-Frame-Options`, setting.CORSConfig.XFrameOptions)
2021-01-26 23:36:53 +08:00
2023-04-14 03:45:33 +08:00
ctx.Data["CsrfToken"] = ctx.Csrf.GetToken()
2021-01-26 23:36:53 +08:00
ctx.Data["CsrfTokenHtml"] = template.HTML(`<input type="hidden" name="_csrf" value="` + ctx.Data["CsrfToken"].(string) + `">`)
// FIXME: do we really always need these setting? There should be someway to have to avoid having to always set these
2021-01-26 23:36:53 +08:00
ctx.Data["DisableMigrations"] = setting.Repository.DisableMigrations
2021-04-15 10:53:57 -06:00
ctx.Data["DisableStars"] = setting.Repository.DisableStars
2023-01-31 09:45:19 +08:00
ctx.Data["EnableActions"] = setting.Actions.Enabled
2021-01-26 23:36:53 +08:00
ctx.Data["ManifestData"] = setting.ManifestData
2021-11-10 03:57:58 +08:00
ctx.Data["UnitWikiGlobalDisabled"] = unit.TypeWiki.UnitGlobalDisabled()
ctx.Data["UnitIssuesGlobalDisabled"] = unit.TypeIssues.UnitGlobalDisabled()
ctx.Data["UnitPullsGlobalDisabled"] = unit.TypePullRequests.UnitGlobalDisabled()
ctx.Data["UnitProjectsGlobalDisabled"] = unit.TypeProjects.UnitGlobalDisabled()
2023-01-31 09:45:19 +08:00
ctx.Data["UnitActionsGlobalDisabled"] = unit.TypeActions.UnitGlobalDisabled()
2021-01-26 23:36:53 +08:00
ctx.Data["AllLangs"] = translation.AllLangs()
2020-12-22 12:13:50 +01:00
2021-01-26 23:36:53 +08:00
next.ServeHTTP(ctx.Resp, ctx.Req)
})
}
}
// HasError returns true if error occurs in form validation.
// Attention: this function changes ctx.Data and ctx.Flash
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
}
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
}
func (ctx *Context) JSONError(msg string) {
ctx.JSON(http.StatusBadRequest, map[string]any{"errorMessage": msg})
}