// Copyright 2014 The Gogs Authors. All rights reserved. // Copyright 2020 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package context import ( "context" "crypto/sha256" "encoding/hex" "errors" "fmt" "html" "html/template" "io" "net" "net/http" "net/url" "path" "regexp" "strconv" "strings" texttemplate "text/template" "time" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/base" mc "code.gitea.io/gitea/modules/cache" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/httpcache" "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/templates" "code.gitea.io/gitea/modules/translation" "code.gitea.io/gitea/modules/typesniffer" "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/web/middleware" "gitea.com/go-chi/cache" "gitea.com/go-chi/session" chi "github.com/go-chi/chi/v5" "github.com/unrolled/render" "golang.org/x/crypto/pbkdf2" ) // Render represents a template render type Render interface { TemplateLookup(tmpl string) *template.Template HTML(w io.Writer, status int, name string, binding interface{}, htmlOpt ...render.HTMLOptions) error } // Context represents context of a request. type Context struct { Resp ResponseWriter Req *http.Request Data map[string]interface{} // data used by MVC templates PageData map[string]interface{} // data used by JavaScript modules in one page, it's `window.config.pageData` Render Render translation.Locale Cache cache.Cache csrf CSRFProtector Flash *middleware.Flash Session session.Store Link string // current request URL EscapedLink string Doer *user_model.User IsSigned bool IsBasicAuth bool ContextUser *user_model.User Repo *Repository Org *Organization Package *Package } // Close frees all resources hold by Context func (ctx *Context) Close() error { var err error if ctx.Req != nil && ctx.Req.MultipartForm != nil { err = ctx.Req.MultipartForm.RemoveAll() // remove the temp files buffered to tmp directory } // TODO: close opened repo, and more return err } // TrHTMLEscapeArgs runs Tr but pre-escapes all arguments with html.EscapeString. // This is useful if the locale message is intended to only produce HTML content. func (ctx *Context) TrHTMLEscapeArgs(msg string, args ...string) string { trArgs := make([]interface{}, len(args)) for i, arg := range args { trArgs[i] = html.EscapeString(arg) } return ctx.Tr(msg, trArgs...) } // GetData returns the data func (ctx *Context) GetData() map[string]interface{} { return ctx.Data } // IsUserSiteAdmin returns true if current user is a site admin func (ctx *Context) IsUserSiteAdmin() bool { return ctx.IsSigned && ctx.Doer.IsAdmin } // IsUserRepoOwner returns true if current user owns current repo func (ctx *Context) IsUserRepoOwner() bool { return ctx.Repo.IsOwner() } // IsUserRepoAdmin returns true if current user is admin in current repo func (ctx *Context) IsUserRepoAdmin() bool { return ctx.Repo.IsAdmin() } // IsUserRepoWriter returns true if current user has write privilege in current repo func (ctx *Context) IsUserRepoWriter(unitTypes []unit.Type) bool { for _, unitType := range unitTypes { if ctx.Repo.CanWrite(unitType) { return true } } return false } // IsUserRepoReaderSpecific returns true if current user can read current repo's specific part func (ctx *Context) IsUserRepoReaderSpecific(unitType unit.Type) bool { return ctx.Repo.CanRead(unitType) } // IsUserRepoReaderAny returns true if current user can read any part of current repo func (ctx *Context) IsUserRepoReaderAny() bool { return ctx.Repo.HasAccess() } // RedirectToUser redirect to a differently-named user func RedirectToUser(ctx *Context, userName string, redirectUserID int64) { user, err := user_model.GetUserByID(ctx, redirectUserID) if err != nil { ctx.ServerError("GetUserByID", err) return } redirectPath := strings.Replace( ctx.Req.URL.EscapedPath(), url.PathEscape(userName), url.PathEscape(user.Name), 1, ) if ctx.Req.URL.RawQuery != "" { redirectPath += "?" + ctx.Req.URL.RawQuery } ctx.Redirect(path.Join(setting.AppSubURL, redirectPath), http.StatusTemporaryRedirect) } // HasAPIError returns true if error occurs in form validation. func (ctx *Context) HasAPIError() bool { hasErr, ok := ctx.Data["HasError"] if !ok { return false } return hasErr.(bool) } // GetErrMsg returns error message func (ctx *Context) GetErrMsg() string { return ctx.Data["ErrorMsg"].(string) } // 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.Data["ErrorMsg"].(string) ctx.Data["Flash"] = ctx.Flash return hasErr.(bool) } // HasValue returns true if value of given name exists. func (ctx *Context) HasValue(name string) bool { _, ok := ctx.Data[name] return ok } // RedirectToFirst redirects to first not empty URL func (ctx *Context) RedirectToFirst(location ...string) { for _, loc := range location { if len(loc) == 0 { continue } // Unfortunately browsers consider a redirect Location with preceding "//" and "/\" as meaning redirect to "http(s)://REST_OF_PATH" // Therefore we should ignore these redirect locations to prevent open redirects if len(loc) > 1 && loc[0] == '/' && (loc[1] == '/' || loc[1] == '\\') { continue } u, err := url.Parse(loc) if err != nil || ((u.Scheme != "" || u.Host != "") && !strings.HasPrefix(strings.ToLower(loc), strings.ToLower(setting.AppURL))) { continue } ctx.Redirect(loc) return } ctx.Redirect(setting.AppSubURL + "/") } var templateExecutingErr = regexp.MustCompile(`^template: (.*):([1-9][0-9]*):([1-9][0-9]*): executing (?:"(.*)" at <(.*)>: )?`) // HTML calls Context.HTML and renders the template to HTTP response func (ctx *Context) HTML(status int, name base.TplName) { log.Debug("Template: %s", name) tmplStartTime := time.Now() if !setting.IsProd { ctx.Data["TemplateName"] = name } ctx.Data["TemplateLoadTimes"] = func() string { return strconv.FormatInt(time.Since(tmplStartTime).Nanoseconds()/1e6, 10) + "ms" } if err := ctx.Render.HTML(ctx.Resp, status, string(name), templates.BaseVars().Merge(ctx.Data)); err != nil { if status == http.StatusInternalServerError && name == base.TplName("status/500") { ctx.PlainText(http.StatusInternalServerError, "Unable to find status/500 template") return } if execErr, ok := err.(texttemplate.ExecError); ok { if groups := templateExecutingErr.FindStringSubmatch(err.Error()); len(groups) > 0 { errorTemplateName, lineStr, posStr := groups[1], groups[2], groups[3] target := "" if len(groups) == 6 { target = groups[5] } line, _ := strconv.Atoi(lineStr) // Cannot error out as groups[2] is [1-9][0-9]* pos, _ := strconv.Atoi(posStr) // Cannot error out as groups[3] is [1-9][0-9]* filename, filenameErr := templates.GetAssetFilename("templates/" + errorTemplateName + ".tmpl") if filenameErr != nil { filename = "(template) " + errorTemplateName } if errorTemplateName != string(name) { filename += " (subtemplate of " + string(name) + ")" } err = fmt.Errorf("%w\nin template file %s:\n%s", err, filename, templates.GetLineFromTemplate(errorTemplateName, line, target, pos)) } else { filename, filenameErr := templates.GetAssetFilename("templates/" + execErr.Name + ".tmpl") if filenameErr != nil { filename = "(template) " + execErr.Name } if execErr.Name != string(name) { filename += " (subtemplate of " + string(name) + ")" } err = fmt.Errorf("%w\nin template file %s", err, filename) } } ctx.ServerError("Render failed", err) } } // RenderToString renders the template content to a string func (ctx *Context) RenderToString(name base.TplName, data map[string]interface{}) (string, error) { var buf strings.Builder err := ctx.Render.HTML(&buf, http.StatusOK, string(name), data) return buf.String(), err } // RenderWithErr used for page has form validation but need to prompt error to users. func (ctx *Context) RenderWithErr(msg string, tpl base.TplName, form interface{}) { if form != nil { middleware.AssignForm(form, ctx.Data) } ctx.Flash.ErrorMsg = msg ctx.Data["Flash"] = ctx.Flash ctx.HTML(http.StatusOK, tpl) } // NotFound displays a 404 (Not Found) page and prints the given error, if any. func (ctx *Context) NotFound(logMsg string, logErr error) { ctx.notFoundInternal(logMsg, logErr) } func (ctx *Context) notFoundInternal(logMsg string, logErr error) { if logErr != nil { log.Log(2, log.DEBUG, "%s: %v", logMsg, logErr) if !setting.IsProd { ctx.Data["ErrorMsg"] = logErr } } // response simple message if Accept isn't text/html showHTML := false for _, part := range ctx.Req.Header["Accept"] { if strings.Contains(part, "text/html") { showHTML = true break } } if !showHTML { ctx.plainTextInternal(3, http.StatusNotFound, []byte("Not found.\n")) return } ctx.Data["IsRepo"] = ctx.Repo.Repository != nil ctx.Data["Title"] = "Page Not Found" ctx.HTML(http.StatusNotFound, base.TplName("status/404")) } // ServerError displays a 500 (Internal Server Error) page and prints the given error, if any. func (ctx *Context) ServerError(logMsg string, logErr error) { ctx.serverErrorInternal(logMsg, logErr) } func (ctx *Context) serverErrorInternal(logMsg string, logErr error) { if logErr != nil { log.ErrorWithSkip(2, "%s: %v", logMsg, logErr) if _, ok := logErr.(*net.OpError); ok || errors.Is(logErr, &net.OpError{}) { // This is an error within the underlying connection // and further rendering will not work so just return return } if !setting.IsProd { ctx.Data["ErrorMsg"] = logErr } } ctx.Data["Title"] = "Internal Server Error" ctx.HTML(http.StatusInternalServerError, base.TplName("status/500")) } // NotFoundOrServerError use error check function to determine if the error // is about not found. It responds with 404 status code for not found error, // or error context description for logging purpose of 500 server error. func (ctx *Context) NotFoundOrServerError(logMsg string, errCheck func(error) bool, err error) { if errCheck(err) { ctx.notFoundInternal(logMsg, err) return } ctx.serverErrorInternal(logMsg, err) } // PlainTextBytes renders bytes as plain text func (ctx *Context) plainTextInternal(skip, status int, bs []byte) { statusPrefix := status / 100 if statusPrefix == 4 || statusPrefix == 5 { log.Log(skip, log.TRACE, "plainTextInternal (status=%d): %s", status, string(bs)) } ctx.Resp.Header().Set("Content-Type", "text/plain;charset=utf-8") ctx.Resp.Header().Set("X-Content-Type-Options", "nosniff") ctx.Resp.WriteHeader(status) if _, err := ctx.Resp.Write(bs); err != nil { log.ErrorWithSkip(skip, "plainTextInternal (status=%d): write bytes failed: %v", status, err) } } // PlainTextBytes renders bytes as plain text func (ctx *Context) PlainTextBytes(status int, bs []byte) { ctx.plainTextInternal(2, status, bs) } // PlainText renders content as plain text func (ctx *Context) PlainText(status int, text string) { ctx.plainTextInternal(2, status, []byte(text)) } // RespHeader returns the response header func (ctx *Context) RespHeader() http.Header { return ctx.Resp.Header() } type ServeHeaderOptions struct { ContentType string // defaults to "application/octet-stream" ContentTypeCharset string ContentLength *int64 Disposition string // defaults to "attachment" Filename string CacheDuration time.Duration // defaults to 5 minutes LastModified time.Time } // SetServeHeaders sets necessary content serve headers func (ctx *Context) SetServeHeaders(opts *ServeHeaderOptions) { header := ctx.Resp.Header() contentType := typesniffer.ApplicationOctetStream if opts.ContentType != "" { if opts.ContentTypeCharset != "" { contentType = opts.ContentType + "; charset=" + strings.ToLower(opts.ContentTypeCharset) } else { contentType = opts.ContentType } } header.Set("Content-Type", contentType) header.Set("X-Content-Type-Options", "nosniff") if opts.ContentLength != nil { header.Set("Content-Length", strconv.FormatInt(*opts.ContentLength, 10)) } if opts.Filename != "" { disposition := opts.Disposition if disposition == "" { disposition = "attachment" } backslashEscapedName := strings.ReplaceAll(strings.ReplaceAll(opts.Filename, `\`, `\\`), `"`, `\"`) // \ -> \\, " -> \" header.Set("Content-Disposition", fmt.Sprintf(`%s; filename="%s"; filename*=UTF-8''%s`, disposition, backslashEscapedName, url.PathEscape(opts.Filename))) header.Set("Access-Control-Expose-Headers", "Content-Disposition") } duration := opts.CacheDuration if duration == 0 { duration = 5 * time.Minute } httpcache.SetCacheControlInHeader(header, duration) if !opts.LastModified.IsZero() { header.Set("Last-Modified", opts.LastModified.UTC().Format(http.TimeFormat)) } } // ServeContent serves content to http request func (ctx *Context) ServeContent(r io.ReadSeeker, opts *ServeHeaderOptions) { ctx.SetServeHeaders(opts) http.ServeContent(ctx.Resp, ctx.Req, opts.Filename, opts.LastModified, r) } // UploadStream returns the request body or the first form file // Only form files need to get closed. func (ctx *Context) UploadStream() (rd io.ReadCloser, needToClose bool, err error) { contentType := strings.ToLower(ctx.Req.Header.Get("Content-Type")) if strings.HasPrefix(contentType, "application/x-www-form-urlencoded") || strings.HasPrefix(contentType, "multipart/form-data") { if err := ctx.Req.ParseMultipartForm(32 << 20); err != nil { return nil, false, err } if ctx.Req.MultipartForm.File == nil { return nil, false, http.ErrMissingFile } for _, files := range ctx.Req.MultipartForm.File { if len(files) > 0 { r, err := files[0].Open() return r, true, err } } return nil, false, http.ErrMissingFile } return ctx.Req.Body, false, nil } // Error returned an error to web browser func (ctx *Context) Error(status int, contents ...string) { v := http.StatusText(status) if len(contents) > 0 { v = contents[0] } http.Error(ctx.Resp, v, status) } // JSON render content as JSON func (ctx *Context) JSON(status int, content interface{}) { ctx.Resp.Header().Set("Content-Type", "application/json;charset=utf-8") ctx.Resp.WriteHeader(status) if err := json.NewEncoder(ctx.Resp).Encode(content); err != nil { ctx.ServerError("Render JSON failed", err) } } func removeSessionCookieHeader(w http.ResponseWriter) { cookies := w.Header()["Set-Cookie"] w.Header().Del("Set-Cookie") for _, cookie := range cookies { if strings.HasPrefix(cookie, setting.SessionConfig.CookieName+"=") { continue } w.Header().Add("Set-Cookie", cookie) } } // Redirect redirects the request func (ctx *Context) Redirect(location string, status ...int) { code := http.StatusSeeOther if len(status) == 1 { code = status[0] } if strings.Contains(location, "://") || strings.HasPrefix(location, "//") { // Some browsers (Safari) have buggy behavior for Cookie + Cache + External Redirection, eg: /my-path => https://other/path // 1. the first request to "/my-path" contains cookie // 2. some time later, the request to "/my-path" doesn't contain cookie (caused by Prevent web tracking) // 3. Gitea's Sessioner doesn't see the session cookie, so it generates a new session id, and returns it to browser // 4. then the browser accepts the empty session, then the user is logged out // So in this case, we should remove the session cookie from the response header removeSessionCookieHeader(ctx.Resp) } http.Redirect(ctx.Resp, ctx.Req, location, code) } // SetCookie convenience function to set most cookies consistently // CSRF and a few others are the exception here func (ctx *Context) SetCookie(name, value string, expiry int) { middleware.SetCookie(ctx.Resp, name, value, expiry, setting.AppSubURL, setting.SessionConfig.Domain, setting.SessionConfig.Secure, true, middleware.SameSite(setting.SessionConfig.SameSite)) } // DeleteCookie convenience function to delete most cookies consistently // CSRF and a few others are the exception here func (ctx *Context) DeleteCookie(name string) { middleware.SetCookie(ctx.Resp, name, "", -1, setting.AppSubURL, setting.SessionConfig.Domain, setting.SessionConfig.Secure, true, middleware.SameSite(setting.SessionConfig.SameSite)) } // GetCookie returns given cookie value from request header. func (ctx *Context) GetCookie(name string) string { return middleware.GetCookie(ctx.Req, name) } // GetSuperSecureCookie returns given cookie value from request header with secret string. func (ctx *Context) GetSuperSecureCookie(secret, name string) (string, bool) { val := ctx.GetCookie(name) return ctx.CookieDecrypt(secret, val) } // CookieDecrypt returns given value from with secret string. func (ctx *Context) CookieDecrypt(secret, val string) (string, bool) { if val == "" { return "", false } text, err := hex.DecodeString(val) if err != nil { return "", false } key := pbkdf2.Key([]byte(secret), []byte(secret), 1000, 16, sha256.New) text, err = util.AESGCMDecrypt(key, text) return string(text), err == nil } // SetSuperSecureCookie sets given cookie value to response header with secret string. func (ctx *Context) SetSuperSecureCookie(secret, name, value string, expiry int) { text := ctx.CookieEncrypt(secret, value) ctx.SetCookie(name, text, expiry) } // CookieEncrypt encrypts a given value using the provided secret func (ctx *Context) CookieEncrypt(secret, value string) string { key := pbkdf2.Key([]byte(secret), []byte(secret), 1000, 16, sha256.New) text, err := util.AESGCMEncrypt(key, []byte(value)) if err != nil { panic("error encrypting cookie: " + err.Error()) } return hex.EncodeToString(text) } // GetCookieInt returns cookie result in int type. func (ctx *Context) GetCookieInt(name string) int { r, _ := strconv.Atoi(ctx.GetCookie(name)) return r } // GetCookieInt64 returns cookie result in int64 type. func (ctx *Context) GetCookieInt64(name string) int64 { r, _ := strconv.ParseInt(ctx.GetCookie(name), 10, 64) return r } // GetCookieFloat64 returns cookie result in float64 type. func (ctx *Context) GetCookieFloat64(name string) float64 { v, _ := strconv.ParseFloat(ctx.GetCookie(name), 64) return v } // RemoteAddr returns the client machie ip address func (ctx *Context) RemoteAddr() string { return ctx.Req.RemoteAddr } // Params returns the param on route func (ctx *Context) Params(p string) string { s, _ := url.PathUnescape(chi.URLParam(ctx.Req, strings.TrimPrefix(p, ":"))) return s } // ParamsInt64 returns the param on route as int64 func (ctx *Context) ParamsInt64(p string) int64 { v, _ := strconv.ParseInt(ctx.Params(p), 10, 64) return v } // SetParams set params into routes func (ctx *Context) SetParams(k, v string) { chiCtx := chi.RouteContext(ctx) chiCtx.URLParams.Add(strings.TrimPrefix(k, ":"), url.PathEscape(v)) } // Write writes data to web browser func (ctx *Context) Write(bs []byte) (int, error) { return ctx.Resp.Write(bs) } // Written returns true if there are something sent to web browser func (ctx *Context) Written() bool { return ctx.Resp.Status() > 0 } // Status writes status code func (ctx *Context) Status(status int) { ctx.Resp.WriteHeader(status) } // Deadline is part of the interface for context.Context and we pass this to the request context func (ctx *Context) Deadline() (deadline time.Time, ok bool) { return ctx.Req.Context().Deadline() } // Done is part of the interface for context.Context and we pass this to the request context func (ctx *Context) Done() <-chan struct{} { return ctx.Req.Context().Done() } // Err is part of the interface for context.Context and we pass this to the request context func (ctx *Context) Err() error { return ctx.Req.Context().Err() } // Value is part of the interface for context.Context and we pass this to the request context func (ctx *Context) Value(key interface{}) interface{} { if key == git.RepositoryContextKey && ctx.Repo != nil { return ctx.Repo.GitRepo } return ctx.Req.Context().Value(key) } // SetTotalCountHeader set "X-Total-Count" header func (ctx *Context) SetTotalCountHeader(total int64) { ctx.RespHeader().Set("X-Total-Count", fmt.Sprint(total)) ctx.AppendAccessControlExposeHeaders("X-Total-Count") } // AppendAccessControlExposeHeaders append headers by name to "Access-Control-Expose-Headers" header func (ctx *Context) AppendAccessControlExposeHeaders(names ...string) { val := ctx.RespHeader().Get("Access-Control-Expose-Headers") if len(val) != 0 { ctx.RespHeader().Set("Access-Control-Expose-Headers", fmt.Sprintf("%s, %s", val, strings.Join(names, ", "))) } else { ctx.RespHeader().Set("Access-Control-Expose-Headers", strings.Join(names, ", ")) } } // Handler represents a custom handler type Handler func(*Context) type contextKeyType struct{} var contextKey interface{} = contextKeyType{} // WithContext set up install context in request func WithContext(req *http.Request, ctx *Context) *http.Request { return req.WithContext(context.WithValue(req.Context(), contextKey, ctx)) } // GetContext retrieves install context from request func GetContext(req *http.Request) *Context { return req.Context().Value(contextKey).(*Context) } // GetContextUser returns context user func GetContextUser(req *http.Request) *user_model.User { if apiContext, ok := req.Context().Value(apiContextKey).(*APIContext); ok { return apiContext.Doer } if ctx, ok := req.Context().Value(contextKey).(*Context); ok { return ctx.Doer } return nil } func getCsrfOpts() CsrfOptions { return CsrfOptions{ 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, SameSite: setting.SessionConfig.SameSite, } } // Contexter initializes a classic context for a request. func Contexter(ctx context.Context) func(next http.Handler) http.Handler { _, rnd := templates.HTMLRenderer(ctx) csrfOpts := getCsrfOpts() if !setting.IsProd { CsrfTokenRegenerationInterval = 5 * time.Second // in dev, re-generate the tokens more aggressively for debug purpose } return func(next http.Handler) http.Handler { return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { locale := middleware.Locale(resp, req) startTime := time.Now() link := setting.AppSubURL + strings.TrimSuffix(req.URL.EscapedPath(), "/") ctx := Context{ Resp: NewResponse(resp), Cache: mc.GetCache(), Locale: locale, Link: link, Render: rnd, Session: session.GetSession(req), Repo: &Repository{ PullRequest: &PullRequest{}, }, Org: &Organization{}, Data: map[string]interface{}{ "CurrentURL": setting.AppSubURL + req.URL.RequestURI(), "PageStartTime": startTime, "Link": link, "RunModeIsProd": setting.IsProd, }, } defer ctx.Close() // PageData is passed by reference, and it will be rendered to `window.config.pageData` in `head.tmpl` for JavaScript modules ctx.PageData = map[string]interface{}{} ctx.Data["PageData"] = ctx.PageData ctx.Data["Context"] = &ctx ctx.Req = WithContext(req, &ctx) ctx.csrf = PrepareCSRFProtector(csrfOpts, &ctx) // Get flash. flashCookie := ctx.GetCookie("macaron_flash") vals, _ := url.ParseQuery(flashCookie) if len(vals) > 0 { f := &middleware.Flash{ DataStore: &ctx, Values: vals, ErrorMsg: vals.Get("error"), SuccessMsg: vals.Get("success"), InfoMsg: vals.Get("info"), WarningMsg: vals.Get("warning"), } ctx.Data["Flash"] = f } f := &middleware.Flash{ DataStore: &ctx, Values: url.Values{}, ErrorMsg: "", WarningMsg: "", InfoMsg: "", SuccessMsg: "", } ctx.Resp.Before(func(resp ResponseWriter) { if flash := f.Encode(); len(flash) > 0 { middleware.SetCookie(resp, "macaron_flash", flash, 0, setting.SessionConfig.CookiePath, middleware.Domain(setting.SessionConfig.Domain), middleware.HTTPOnly(true), middleware.Secure(setting.SessionConfig.Secure), middleware.SameSite(setting.SessionConfig.SameSite), ) return } middleware.SetCookie(ctx.Resp, "macaron_flash", "", -1, setting.SessionConfig.CookiePath, middleware.Domain(setting.SessionConfig.Domain), middleware.HTTPOnly(true), middleware.Secure(setting.SessionConfig.Secure), middleware.SameSite(setting.SessionConfig.SameSite), ) }) ctx.Flash = f // 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 } } httpcache.SetCacheControlInHeader(ctx.Resp.Header(), 0, "no-transform") ctx.Resp.Header().Set(`X-Frame-Options`, setting.CORSConfig.XFrameOptions) ctx.Data["CsrfToken"] = ctx.csrf.GetToken() ctx.Data["CsrfTokenHtml"] = template.HTML(``) // FIXME: do we really always need these setting? There should be someway to have to avoid having to always set these ctx.Data["IsLandingPageHome"] = setting.LandingPageURL == setting.LandingPageHome ctx.Data["IsLandingPageExplore"] = setting.LandingPageURL == setting.LandingPageExplore ctx.Data["IsLandingPageOrganizations"] = setting.LandingPageURL == setting.LandingPageOrganizations ctx.Data["ShowRegistrationButton"] = setting.Service.ShowRegistrationButton ctx.Data["ShowMilestonesDashboardPage"] = setting.Service.ShowMilestonesDashboardPage ctx.Data["ShowFooterBranding"] = setting.ShowFooterBranding ctx.Data["ShowFooterVersion"] = setting.ShowFooterVersion ctx.Data["EnableSwagger"] = setting.API.EnableSwagger ctx.Data["EnableOpenIDSignIn"] = setting.Service.EnableOpenIDSignIn ctx.Data["DisableMigrations"] = setting.Repository.DisableMigrations ctx.Data["DisableStars"] = setting.Repository.DisableStars ctx.Data["EnableActions"] = setting.Actions.Enabled ctx.Data["ManifestData"] = setting.ManifestData ctx.Data["UnitWikiGlobalDisabled"] = unit.TypeWiki.UnitGlobalDisabled() ctx.Data["UnitIssuesGlobalDisabled"] = unit.TypeIssues.UnitGlobalDisabled() ctx.Data["UnitPullsGlobalDisabled"] = unit.TypePullRequests.UnitGlobalDisabled() ctx.Data["UnitProjectsGlobalDisabled"] = unit.TypeProjects.UnitGlobalDisabled() ctx.Data["UnitActionsGlobalDisabled"] = unit.TypeActions.UnitGlobalDisabled() ctx.Data["locale"] = locale ctx.Data["AllLangs"] = translation.AllLangs() next.ServeHTTP(ctx.Resp, ctx.Req) // Handle adding signedUserName to the context for the AccessLogger usernameInterface := ctx.Data["SignedUserName"] identityPtrInterface := ctx.Req.Context().Value(signedUserNameStringPointerKey) if usernameInterface != nil && identityPtrInterface != nil { username := usernameInterface.(string) identityPtr := identityPtrInterface.(*string) if identityPtr != nil && username != "" { *identityPtr = username } } }) } } // SearchOrderByMap represents all possible search order var SearchOrderByMap = map[string]map[string]db.SearchOrderBy{ "asc": { "alpha": db.SearchOrderByAlphabetically, "created": db.SearchOrderByOldest, "updated": db.SearchOrderByLeastUpdated, "size": db.SearchOrderBySize, "id": db.SearchOrderByID, }, "desc": { "alpha": db.SearchOrderByAlphabeticallyReverse, "created": db.SearchOrderByNewest, "updated": db.SearchOrderByRecentUpdated, "size": db.SearchOrderBySizeReverse, "id": db.SearchOrderByIDReverse, }, }