123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853 |
- // 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"
- "strconv"
- "strings"
- "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"
- "code.gitea.io/gitea/services/auth"
-
- "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 + "/")
- }
-
- // 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
- }
- 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.AddCacheControlToHeader(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)
- }
- }
-
- // Redirect redirects the request
- func (ctx *Context) Redirect(location string, status ...int) {
- code := http.StatusSeeOther
- if len(status) == 1 {
- code = status[0]
- }
-
- 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,
- }
- }
-
- // Auth converts auth.Auth as a middleware
- func Auth(authMethod auth.Method) func(*Context) {
- return func(ctx *Context) {
- var err error
- ctx.Doer, err = authMethod.Verify(ctx.Req, ctx.Resp, ctx, ctx.Session)
- if err != nil {
- log.Error("Failed to verify user %v: %v", ctx.Req.RemoteAddr, err)
- ctx.Error(http.StatusUnauthorized, "Verify")
- return
- }
- if ctx.Doer != nil {
- if ctx.Locale.Language() != ctx.Doer.Language {
- ctx.Locale = middleware.Locale(ctx.Resp, ctx.Req)
- }
- ctx.IsBasicAuth = ctx.Data["AuthedMethod"].(string) == auth.BasicMethodName
- ctx.IsSigned = true
- ctx.Data["IsSigned"] = ctx.IsSigned
- ctx.Data["SignedUser"] = ctx.Doer
- ctx.Data["SignedUserID"] = ctx.Doer.ID
- ctx.Data["SignedUserName"] = ctx.Doer.Name
- ctx.Data["IsAdmin"] = ctx.Doer.IsAdmin
- } else {
- ctx.Data["SignedUserID"] = int64(0)
- ctx.Data["SignedUserName"] = ""
-
- // ensure the session uid is deleted
- _ = ctx.Session.Delete("uid")
- }
- }
- }
-
- // 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.AddCacheControlToHeader(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(`<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
- 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,
- },
- }
|