code.gitea.io/gitea@v1.19.3/modules/context/context.go (about)

     1  // Copyright 2014 The Gogs Authors. All rights reserved.
     2  // Copyright 2020 The Gitea Authors. All rights reserved.
     3  // SPDX-License-Identifier: MIT
     4  
     5  package context
     6  
     7  import (
     8  	"context"
     9  	"crypto/sha256"
    10  	"encoding/hex"
    11  	"errors"
    12  	"fmt"
    13  	"html"
    14  	"html/template"
    15  	"io"
    16  	"net"
    17  	"net/http"
    18  	"net/url"
    19  	"path"
    20  	"regexp"
    21  	"strconv"
    22  	"strings"
    23  	texttemplate "text/template"
    24  	"time"
    25  
    26  	"code.gitea.io/gitea/models/db"
    27  	"code.gitea.io/gitea/models/unit"
    28  	user_model "code.gitea.io/gitea/models/user"
    29  	"code.gitea.io/gitea/modules/base"
    30  	mc "code.gitea.io/gitea/modules/cache"
    31  	"code.gitea.io/gitea/modules/git"
    32  	"code.gitea.io/gitea/modules/httpcache"
    33  	"code.gitea.io/gitea/modules/json"
    34  	"code.gitea.io/gitea/modules/log"
    35  	"code.gitea.io/gitea/modules/setting"
    36  	"code.gitea.io/gitea/modules/templates"
    37  	"code.gitea.io/gitea/modules/translation"
    38  	"code.gitea.io/gitea/modules/typesniffer"
    39  	"code.gitea.io/gitea/modules/util"
    40  	"code.gitea.io/gitea/modules/web/middleware"
    41  
    42  	"gitea.com/go-chi/cache"
    43  	"gitea.com/go-chi/session"
    44  	chi "github.com/go-chi/chi/v5"
    45  	"github.com/unrolled/render"
    46  	"golang.org/x/crypto/pbkdf2"
    47  )
    48  
    49  // Render represents a template render
    50  type Render interface {
    51  	TemplateLookup(tmpl string) *template.Template
    52  	HTML(w io.Writer, status int, name string, binding interface{}, htmlOpt ...render.HTMLOptions) error
    53  }
    54  
    55  // Context represents context of a request.
    56  type Context struct {
    57  	Resp     ResponseWriter
    58  	Req      *http.Request
    59  	Data     map[string]interface{} // data used by MVC templates
    60  	PageData map[string]interface{} // data used by JavaScript modules in one page, it's `window.config.pageData`
    61  	Render   Render
    62  	translation.Locale
    63  	Cache   cache.Cache
    64  	csrf    CSRFProtector
    65  	Flash   *middleware.Flash
    66  	Session session.Store
    67  
    68  	Link        string // current request URL
    69  	EscapedLink string
    70  	Doer        *user_model.User
    71  	IsSigned    bool
    72  	IsBasicAuth bool
    73  
    74  	ContextUser *user_model.User
    75  	Repo        *Repository
    76  	Org         *Organization
    77  	Package     *Package
    78  }
    79  
    80  // Close frees all resources hold by Context
    81  func (ctx *Context) Close() error {
    82  	var err error
    83  	if ctx.Req != nil && ctx.Req.MultipartForm != nil {
    84  		err = ctx.Req.MultipartForm.RemoveAll() // remove the temp files buffered to tmp directory
    85  	}
    86  	// TODO: close opened repo, and more
    87  	return err
    88  }
    89  
    90  // TrHTMLEscapeArgs runs Tr but pre-escapes all arguments with html.EscapeString.
    91  // This is useful if the locale message is intended to only produce HTML content.
    92  func (ctx *Context) TrHTMLEscapeArgs(msg string, args ...string) string {
    93  	trArgs := make([]interface{}, len(args))
    94  	for i, arg := range args {
    95  		trArgs[i] = html.EscapeString(arg)
    96  	}
    97  	return ctx.Tr(msg, trArgs...)
    98  }
    99  
   100  // GetData returns the data
   101  func (ctx *Context) GetData() map[string]interface{} {
   102  	return ctx.Data
   103  }
   104  
   105  // IsUserSiteAdmin returns true if current user is a site admin
   106  func (ctx *Context) IsUserSiteAdmin() bool {
   107  	return ctx.IsSigned && ctx.Doer.IsAdmin
   108  }
   109  
   110  // IsUserRepoOwner returns true if current user owns current repo
   111  func (ctx *Context) IsUserRepoOwner() bool {
   112  	return ctx.Repo.IsOwner()
   113  }
   114  
   115  // IsUserRepoAdmin returns true if current user is admin in current repo
   116  func (ctx *Context) IsUserRepoAdmin() bool {
   117  	return ctx.Repo.IsAdmin()
   118  }
   119  
   120  // IsUserRepoWriter returns true if current user has write privilege in current repo
   121  func (ctx *Context) IsUserRepoWriter(unitTypes []unit.Type) bool {
   122  	for _, unitType := range unitTypes {
   123  		if ctx.Repo.CanWrite(unitType) {
   124  			return true
   125  		}
   126  	}
   127  
   128  	return false
   129  }
   130  
   131  // IsUserRepoReaderSpecific returns true if current user can read current repo's specific part
   132  func (ctx *Context) IsUserRepoReaderSpecific(unitType unit.Type) bool {
   133  	return ctx.Repo.CanRead(unitType)
   134  }
   135  
   136  // IsUserRepoReaderAny returns true if current user can read any part of current repo
   137  func (ctx *Context) IsUserRepoReaderAny() bool {
   138  	return ctx.Repo.HasAccess()
   139  }
   140  
   141  // RedirectToUser redirect to a differently-named user
   142  func RedirectToUser(ctx *Context, userName string, redirectUserID int64) {
   143  	user, err := user_model.GetUserByID(ctx, redirectUserID)
   144  	if err != nil {
   145  		ctx.ServerError("GetUserByID", err)
   146  		return
   147  	}
   148  
   149  	redirectPath := strings.Replace(
   150  		ctx.Req.URL.EscapedPath(),
   151  		url.PathEscape(userName),
   152  		url.PathEscape(user.Name),
   153  		1,
   154  	)
   155  	if ctx.Req.URL.RawQuery != "" {
   156  		redirectPath += "?" + ctx.Req.URL.RawQuery
   157  	}
   158  	ctx.Redirect(path.Join(setting.AppSubURL, redirectPath), http.StatusTemporaryRedirect)
   159  }
   160  
   161  // HasAPIError returns true if error occurs in form validation.
   162  func (ctx *Context) HasAPIError() bool {
   163  	hasErr, ok := ctx.Data["HasError"]
   164  	if !ok {
   165  		return false
   166  	}
   167  	return hasErr.(bool)
   168  }
   169  
   170  // GetErrMsg returns error message
   171  func (ctx *Context) GetErrMsg() string {
   172  	return ctx.Data["ErrorMsg"].(string)
   173  }
   174  
   175  // HasError returns true if error occurs in form validation.
   176  // Attention: this function changes ctx.Data and ctx.Flash
   177  func (ctx *Context) HasError() bool {
   178  	hasErr, ok := ctx.Data["HasError"]
   179  	if !ok {
   180  		return false
   181  	}
   182  	ctx.Flash.ErrorMsg = ctx.Data["ErrorMsg"].(string)
   183  	ctx.Data["Flash"] = ctx.Flash
   184  	return hasErr.(bool)
   185  }
   186  
   187  // HasValue returns true if value of given name exists.
   188  func (ctx *Context) HasValue(name string) bool {
   189  	_, ok := ctx.Data[name]
   190  	return ok
   191  }
   192  
   193  // RedirectToFirst redirects to first not empty URL
   194  func (ctx *Context) RedirectToFirst(location ...string) {
   195  	for _, loc := range location {
   196  		if len(loc) == 0 {
   197  			continue
   198  		}
   199  
   200  		// Unfortunately browsers consider a redirect Location with preceding "//" and "/\" as meaning redirect to "http(s)://REST_OF_PATH"
   201  		// Therefore we should ignore these redirect locations to prevent open redirects
   202  		if len(loc) > 1 && loc[0] == '/' && (loc[1] == '/' || loc[1] == '\\') {
   203  			continue
   204  		}
   205  
   206  		u, err := url.Parse(loc)
   207  		if err != nil || ((u.Scheme != "" || u.Host != "") && !strings.HasPrefix(strings.ToLower(loc), strings.ToLower(setting.AppURL))) {
   208  			continue
   209  		}
   210  
   211  		ctx.Redirect(loc)
   212  		return
   213  	}
   214  
   215  	ctx.Redirect(setting.AppSubURL + "/")
   216  }
   217  
   218  var templateExecutingErr = regexp.MustCompile(`^template: (.*):([1-9][0-9]*):([1-9][0-9]*): executing (?:"(.*)" at <(.*)>: )?`)
   219  
   220  // HTML calls Context.HTML and renders the template to HTTP response
   221  func (ctx *Context) HTML(status int, name base.TplName) {
   222  	log.Debug("Template: %s", name)
   223  	tmplStartTime := time.Now()
   224  	if !setting.IsProd {
   225  		ctx.Data["TemplateName"] = name
   226  	}
   227  	ctx.Data["TemplateLoadTimes"] = func() string {
   228  		return strconv.FormatInt(time.Since(tmplStartTime).Nanoseconds()/1e6, 10) + "ms"
   229  	}
   230  	if err := ctx.Render.HTML(ctx.Resp, status, string(name), templates.BaseVars().Merge(ctx.Data)); err != nil {
   231  		if status == http.StatusInternalServerError && name == base.TplName("status/500") {
   232  			ctx.PlainText(http.StatusInternalServerError, "Unable to find status/500 template")
   233  			return
   234  		}
   235  		if execErr, ok := err.(texttemplate.ExecError); ok {
   236  			if groups := templateExecutingErr.FindStringSubmatch(err.Error()); len(groups) > 0 {
   237  				errorTemplateName, lineStr, posStr := groups[1], groups[2], groups[3]
   238  				target := ""
   239  				if len(groups) == 6 {
   240  					target = groups[5]
   241  				}
   242  				line, _ := strconv.Atoi(lineStr) // Cannot error out as groups[2] is [1-9][0-9]*
   243  				pos, _ := strconv.Atoi(posStr)   // Cannot error out as groups[3] is [1-9][0-9]*
   244  				filename, filenameErr := templates.GetAssetFilename("templates/" + errorTemplateName + ".tmpl")
   245  				if filenameErr != nil {
   246  					filename = "(template) " + errorTemplateName
   247  				}
   248  				if errorTemplateName != string(name) {
   249  					filename += " (subtemplate of " + string(name) + ")"
   250  				}
   251  				err = fmt.Errorf("%w\nin template file %s:\n%s", err, filename, templates.GetLineFromTemplate(errorTemplateName, line, target, pos))
   252  			} else {
   253  				filename, filenameErr := templates.GetAssetFilename("templates/" + execErr.Name + ".tmpl")
   254  				if filenameErr != nil {
   255  					filename = "(template) " + execErr.Name
   256  				}
   257  				if execErr.Name != string(name) {
   258  					filename += " (subtemplate of " + string(name) + ")"
   259  				}
   260  				err = fmt.Errorf("%w\nin template file %s", err, filename)
   261  			}
   262  		}
   263  		ctx.ServerError("Render failed", err)
   264  	}
   265  }
   266  
   267  // RenderToString renders the template content to a string
   268  func (ctx *Context) RenderToString(name base.TplName, data map[string]interface{}) (string, error) {
   269  	var buf strings.Builder
   270  	err := ctx.Render.HTML(&buf, http.StatusOK, string(name), data)
   271  	return buf.String(), err
   272  }
   273  
   274  // RenderWithErr used for page has form validation but need to prompt error to users.
   275  func (ctx *Context) RenderWithErr(msg string, tpl base.TplName, form interface{}) {
   276  	if form != nil {
   277  		middleware.AssignForm(form, ctx.Data)
   278  	}
   279  	ctx.Flash.ErrorMsg = msg
   280  	ctx.Data["Flash"] = ctx.Flash
   281  	ctx.HTML(http.StatusOK, tpl)
   282  }
   283  
   284  // NotFound displays a 404 (Not Found) page and prints the given error, if any.
   285  func (ctx *Context) NotFound(logMsg string, logErr error) {
   286  	ctx.notFoundInternal(logMsg, logErr)
   287  }
   288  
   289  func (ctx *Context) notFoundInternal(logMsg string, logErr error) {
   290  	if logErr != nil {
   291  		log.Log(2, log.DEBUG, "%s: %v", logMsg, logErr)
   292  		if !setting.IsProd {
   293  			ctx.Data["ErrorMsg"] = logErr
   294  		}
   295  	}
   296  
   297  	// response simple message if Accept isn't text/html
   298  	showHTML := false
   299  	for _, part := range ctx.Req.Header["Accept"] {
   300  		if strings.Contains(part, "text/html") {
   301  			showHTML = true
   302  			break
   303  		}
   304  	}
   305  
   306  	if !showHTML {
   307  		ctx.plainTextInternal(3, http.StatusNotFound, []byte("Not found.\n"))
   308  		return
   309  	}
   310  
   311  	ctx.Data["IsRepo"] = ctx.Repo.Repository != nil
   312  	ctx.Data["Title"] = "Page Not Found"
   313  	ctx.HTML(http.StatusNotFound, base.TplName("status/404"))
   314  }
   315  
   316  // ServerError displays a 500 (Internal Server Error) page and prints the given error, if any.
   317  func (ctx *Context) ServerError(logMsg string, logErr error) {
   318  	ctx.serverErrorInternal(logMsg, logErr)
   319  }
   320  
   321  func (ctx *Context) serverErrorInternal(logMsg string, logErr error) {
   322  	if logErr != nil {
   323  		log.ErrorWithSkip(2, "%s: %v", logMsg, logErr)
   324  		if _, ok := logErr.(*net.OpError); ok || errors.Is(logErr, &net.OpError{}) {
   325  			// This is an error within the underlying connection
   326  			// and further rendering will not work so just return
   327  			return
   328  		}
   329  
   330  		if !setting.IsProd {
   331  			ctx.Data["ErrorMsg"] = logErr
   332  		}
   333  	}
   334  
   335  	ctx.Data["Title"] = "Internal Server Error"
   336  	ctx.HTML(http.StatusInternalServerError, base.TplName("status/500"))
   337  }
   338  
   339  // NotFoundOrServerError use error check function to determine if the error
   340  // is about not found. It responds with 404 status code for not found error,
   341  // or error context description for logging purpose of 500 server error.
   342  func (ctx *Context) NotFoundOrServerError(logMsg string, errCheck func(error) bool, err error) {
   343  	if errCheck(err) {
   344  		ctx.notFoundInternal(logMsg, err)
   345  		return
   346  	}
   347  	ctx.serverErrorInternal(logMsg, err)
   348  }
   349  
   350  // PlainTextBytes renders bytes as plain text
   351  func (ctx *Context) plainTextInternal(skip, status int, bs []byte) {
   352  	statusPrefix := status / 100
   353  	if statusPrefix == 4 || statusPrefix == 5 {
   354  		log.Log(skip, log.TRACE, "plainTextInternal (status=%d): %s", status, string(bs))
   355  	}
   356  	ctx.Resp.Header().Set("Content-Type", "text/plain;charset=utf-8")
   357  	ctx.Resp.Header().Set("X-Content-Type-Options", "nosniff")
   358  	ctx.Resp.WriteHeader(status)
   359  	if _, err := ctx.Resp.Write(bs); err != nil {
   360  		log.ErrorWithSkip(skip, "plainTextInternal (status=%d): write bytes failed: %v", status, err)
   361  	}
   362  }
   363  
   364  // PlainTextBytes renders bytes as plain text
   365  func (ctx *Context) PlainTextBytes(status int, bs []byte) {
   366  	ctx.plainTextInternal(2, status, bs)
   367  }
   368  
   369  // PlainText renders content as plain text
   370  func (ctx *Context) PlainText(status int, text string) {
   371  	ctx.plainTextInternal(2, status, []byte(text))
   372  }
   373  
   374  // RespHeader returns the response header
   375  func (ctx *Context) RespHeader() http.Header {
   376  	return ctx.Resp.Header()
   377  }
   378  
   379  type ServeHeaderOptions struct {
   380  	ContentType        string // defaults to "application/octet-stream"
   381  	ContentTypeCharset string
   382  	ContentLength      *int64
   383  	Disposition        string // defaults to "attachment"
   384  	Filename           string
   385  	CacheDuration      time.Duration // defaults to 5 minutes
   386  	LastModified       time.Time
   387  }
   388  
   389  // SetServeHeaders sets necessary content serve headers
   390  func (ctx *Context) SetServeHeaders(opts *ServeHeaderOptions) {
   391  	header := ctx.Resp.Header()
   392  
   393  	contentType := typesniffer.ApplicationOctetStream
   394  	if opts.ContentType != "" {
   395  		if opts.ContentTypeCharset != "" {
   396  			contentType = opts.ContentType + "; charset=" + strings.ToLower(opts.ContentTypeCharset)
   397  		} else {
   398  			contentType = opts.ContentType
   399  		}
   400  	}
   401  	header.Set("Content-Type", contentType)
   402  	header.Set("X-Content-Type-Options", "nosniff")
   403  
   404  	if opts.ContentLength != nil {
   405  		header.Set("Content-Length", strconv.FormatInt(*opts.ContentLength, 10))
   406  	}
   407  
   408  	if opts.Filename != "" {
   409  		disposition := opts.Disposition
   410  		if disposition == "" {
   411  			disposition = "attachment"
   412  		}
   413  
   414  		backslashEscapedName := strings.ReplaceAll(strings.ReplaceAll(opts.Filename, `\`, `\\`), `"`, `\"`) // \ -> \\, " -> \"
   415  		header.Set("Content-Disposition", fmt.Sprintf(`%s; filename="%s"; filename*=UTF-8''%s`, disposition, backslashEscapedName, url.PathEscape(opts.Filename)))
   416  		header.Set("Access-Control-Expose-Headers", "Content-Disposition")
   417  	}
   418  
   419  	duration := opts.CacheDuration
   420  	if duration == 0 {
   421  		duration = 5 * time.Minute
   422  	}
   423  	httpcache.SetCacheControlInHeader(header, duration)
   424  
   425  	if !opts.LastModified.IsZero() {
   426  		header.Set("Last-Modified", opts.LastModified.UTC().Format(http.TimeFormat))
   427  	}
   428  }
   429  
   430  // ServeContent serves content to http request
   431  func (ctx *Context) ServeContent(r io.ReadSeeker, opts *ServeHeaderOptions) {
   432  	ctx.SetServeHeaders(opts)
   433  	http.ServeContent(ctx.Resp, ctx.Req, opts.Filename, opts.LastModified, r)
   434  }
   435  
   436  // UploadStream returns the request body or the first form file
   437  // Only form files need to get closed.
   438  func (ctx *Context) UploadStream() (rd io.ReadCloser, needToClose bool, err error) {
   439  	contentType := strings.ToLower(ctx.Req.Header.Get("Content-Type"))
   440  	if strings.HasPrefix(contentType, "application/x-www-form-urlencoded") || strings.HasPrefix(contentType, "multipart/form-data") {
   441  		if err := ctx.Req.ParseMultipartForm(32 << 20); err != nil {
   442  			return nil, false, err
   443  		}
   444  		if ctx.Req.MultipartForm.File == nil {
   445  			return nil, false, http.ErrMissingFile
   446  		}
   447  		for _, files := range ctx.Req.MultipartForm.File {
   448  			if len(files) > 0 {
   449  				r, err := files[0].Open()
   450  				return r, true, err
   451  			}
   452  		}
   453  		return nil, false, http.ErrMissingFile
   454  	}
   455  	return ctx.Req.Body, false, nil
   456  }
   457  
   458  // Error returned an error to web browser
   459  func (ctx *Context) Error(status int, contents ...string) {
   460  	v := http.StatusText(status)
   461  	if len(contents) > 0 {
   462  		v = contents[0]
   463  	}
   464  	http.Error(ctx.Resp, v, status)
   465  }
   466  
   467  // JSON render content as JSON
   468  func (ctx *Context) JSON(status int, content interface{}) {
   469  	ctx.Resp.Header().Set("Content-Type", "application/json;charset=utf-8")
   470  	ctx.Resp.WriteHeader(status)
   471  	if err := json.NewEncoder(ctx.Resp).Encode(content); err != nil {
   472  		ctx.ServerError("Render JSON failed", err)
   473  	}
   474  }
   475  
   476  // Redirect redirects the request
   477  func (ctx *Context) Redirect(location string, status ...int) {
   478  	code := http.StatusSeeOther
   479  	if len(status) == 1 {
   480  		code = status[0]
   481  	}
   482  
   483  	http.Redirect(ctx.Resp, ctx.Req, location, code)
   484  }
   485  
   486  // SetCookie convenience function to set most cookies consistently
   487  // CSRF and a few others are the exception here
   488  func (ctx *Context) SetCookie(name, value string, expiry int) {
   489  	middleware.SetCookie(ctx.Resp, name, value,
   490  		expiry,
   491  		setting.AppSubURL,
   492  		setting.SessionConfig.Domain,
   493  		setting.SessionConfig.Secure,
   494  		true,
   495  		middleware.SameSite(setting.SessionConfig.SameSite))
   496  }
   497  
   498  // DeleteCookie convenience function to delete most cookies consistently
   499  // CSRF and a few others are the exception here
   500  func (ctx *Context) DeleteCookie(name string) {
   501  	middleware.SetCookie(ctx.Resp, name, "",
   502  		-1,
   503  		setting.AppSubURL,
   504  		setting.SessionConfig.Domain,
   505  		setting.SessionConfig.Secure,
   506  		true,
   507  		middleware.SameSite(setting.SessionConfig.SameSite))
   508  }
   509  
   510  // GetCookie returns given cookie value from request header.
   511  func (ctx *Context) GetCookie(name string) string {
   512  	return middleware.GetCookie(ctx.Req, name)
   513  }
   514  
   515  // GetSuperSecureCookie returns given cookie value from request header with secret string.
   516  func (ctx *Context) GetSuperSecureCookie(secret, name string) (string, bool) {
   517  	val := ctx.GetCookie(name)
   518  	return ctx.CookieDecrypt(secret, val)
   519  }
   520  
   521  // CookieDecrypt returns given value from with secret string.
   522  func (ctx *Context) CookieDecrypt(secret, val string) (string, bool) {
   523  	if val == "" {
   524  		return "", false
   525  	}
   526  
   527  	text, err := hex.DecodeString(val)
   528  	if err != nil {
   529  		return "", false
   530  	}
   531  
   532  	key := pbkdf2.Key([]byte(secret), []byte(secret), 1000, 16, sha256.New)
   533  	text, err = util.AESGCMDecrypt(key, text)
   534  	return string(text), err == nil
   535  }
   536  
   537  // SetSuperSecureCookie sets given cookie value to response header with secret string.
   538  func (ctx *Context) SetSuperSecureCookie(secret, name, value string, expiry int) {
   539  	text := ctx.CookieEncrypt(secret, value)
   540  
   541  	ctx.SetCookie(name, text, expiry)
   542  }
   543  
   544  // CookieEncrypt encrypts a given value using the provided secret
   545  func (ctx *Context) CookieEncrypt(secret, value string) string {
   546  	key := pbkdf2.Key([]byte(secret), []byte(secret), 1000, 16, sha256.New)
   547  	text, err := util.AESGCMEncrypt(key, []byte(value))
   548  	if err != nil {
   549  		panic("error encrypting cookie: " + err.Error())
   550  	}
   551  
   552  	return hex.EncodeToString(text)
   553  }
   554  
   555  // GetCookieInt returns cookie result in int type.
   556  func (ctx *Context) GetCookieInt(name string) int {
   557  	r, _ := strconv.Atoi(ctx.GetCookie(name))
   558  	return r
   559  }
   560  
   561  // GetCookieInt64 returns cookie result in int64 type.
   562  func (ctx *Context) GetCookieInt64(name string) int64 {
   563  	r, _ := strconv.ParseInt(ctx.GetCookie(name), 10, 64)
   564  	return r
   565  }
   566  
   567  // GetCookieFloat64 returns cookie result in float64 type.
   568  func (ctx *Context) GetCookieFloat64(name string) float64 {
   569  	v, _ := strconv.ParseFloat(ctx.GetCookie(name), 64)
   570  	return v
   571  }
   572  
   573  // RemoteAddr returns the client machie ip address
   574  func (ctx *Context) RemoteAddr() string {
   575  	return ctx.Req.RemoteAddr
   576  }
   577  
   578  // Params returns the param on route
   579  func (ctx *Context) Params(p string) string {
   580  	s, _ := url.PathUnescape(chi.URLParam(ctx.Req, strings.TrimPrefix(p, ":")))
   581  	return s
   582  }
   583  
   584  // ParamsInt64 returns the param on route as int64
   585  func (ctx *Context) ParamsInt64(p string) int64 {
   586  	v, _ := strconv.ParseInt(ctx.Params(p), 10, 64)
   587  	return v
   588  }
   589  
   590  // SetParams set params into routes
   591  func (ctx *Context) SetParams(k, v string) {
   592  	chiCtx := chi.RouteContext(ctx)
   593  	chiCtx.URLParams.Add(strings.TrimPrefix(k, ":"), url.PathEscape(v))
   594  }
   595  
   596  // Write writes data to web browser
   597  func (ctx *Context) Write(bs []byte) (int, error) {
   598  	return ctx.Resp.Write(bs)
   599  }
   600  
   601  // Written returns true if there are something sent to web browser
   602  func (ctx *Context) Written() bool {
   603  	return ctx.Resp.Status() > 0
   604  }
   605  
   606  // Status writes status code
   607  func (ctx *Context) Status(status int) {
   608  	ctx.Resp.WriteHeader(status)
   609  }
   610  
   611  // Deadline is part of the interface for context.Context and we pass this to the request context
   612  func (ctx *Context) Deadline() (deadline time.Time, ok bool) {
   613  	return ctx.Req.Context().Deadline()
   614  }
   615  
   616  // Done is part of the interface for context.Context and we pass this to the request context
   617  func (ctx *Context) Done() <-chan struct{} {
   618  	return ctx.Req.Context().Done()
   619  }
   620  
   621  // Err is part of the interface for context.Context and we pass this to the request context
   622  func (ctx *Context) Err() error {
   623  	return ctx.Req.Context().Err()
   624  }
   625  
   626  // Value is part of the interface for context.Context and we pass this to the request context
   627  func (ctx *Context) Value(key interface{}) interface{} {
   628  	if key == git.RepositoryContextKey && ctx.Repo != nil {
   629  		return ctx.Repo.GitRepo
   630  	}
   631  
   632  	return ctx.Req.Context().Value(key)
   633  }
   634  
   635  // SetTotalCountHeader set "X-Total-Count" header
   636  func (ctx *Context) SetTotalCountHeader(total int64) {
   637  	ctx.RespHeader().Set("X-Total-Count", fmt.Sprint(total))
   638  	ctx.AppendAccessControlExposeHeaders("X-Total-Count")
   639  }
   640  
   641  // AppendAccessControlExposeHeaders append headers by name to "Access-Control-Expose-Headers" header
   642  func (ctx *Context) AppendAccessControlExposeHeaders(names ...string) {
   643  	val := ctx.RespHeader().Get("Access-Control-Expose-Headers")
   644  	if len(val) != 0 {
   645  		ctx.RespHeader().Set("Access-Control-Expose-Headers", fmt.Sprintf("%s, %s", val, strings.Join(names, ", ")))
   646  	} else {
   647  		ctx.RespHeader().Set("Access-Control-Expose-Headers", strings.Join(names, ", "))
   648  	}
   649  }
   650  
   651  // Handler represents a custom handler
   652  type Handler func(*Context)
   653  
   654  type contextKeyType struct{}
   655  
   656  var contextKey interface{} = contextKeyType{}
   657  
   658  // WithContext set up install context in request
   659  func WithContext(req *http.Request, ctx *Context) *http.Request {
   660  	return req.WithContext(context.WithValue(req.Context(), contextKey, ctx))
   661  }
   662  
   663  // GetContext retrieves install context from request
   664  func GetContext(req *http.Request) *Context {
   665  	return req.Context().Value(contextKey).(*Context)
   666  }
   667  
   668  // GetContextUser returns context user
   669  func GetContextUser(req *http.Request) *user_model.User {
   670  	if apiContext, ok := req.Context().Value(apiContextKey).(*APIContext); ok {
   671  		return apiContext.Doer
   672  	}
   673  	if ctx, ok := req.Context().Value(contextKey).(*Context); ok {
   674  		return ctx.Doer
   675  	}
   676  	return nil
   677  }
   678  
   679  func getCsrfOpts() CsrfOptions {
   680  	return CsrfOptions{
   681  		Secret:         setting.SecretKey,
   682  		Cookie:         setting.CSRFCookieName,
   683  		SetCookie:      true,
   684  		Secure:         setting.SessionConfig.Secure,
   685  		CookieHTTPOnly: setting.CSRFCookieHTTPOnly,
   686  		Header:         "X-Csrf-Token",
   687  		CookieDomain:   setting.SessionConfig.Domain,
   688  		CookiePath:     setting.SessionConfig.CookiePath,
   689  		SameSite:       setting.SessionConfig.SameSite,
   690  	}
   691  }
   692  
   693  // Contexter initializes a classic context for a request.
   694  func Contexter(ctx context.Context) func(next http.Handler) http.Handler {
   695  	_, rnd := templates.HTMLRenderer(ctx)
   696  	csrfOpts := getCsrfOpts()
   697  	if !setting.IsProd {
   698  		CsrfTokenRegenerationInterval = 5 * time.Second // in dev, re-generate the tokens more aggressively for debug purpose
   699  	}
   700  	return func(next http.Handler) http.Handler {
   701  		return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
   702  			locale := middleware.Locale(resp, req)
   703  			startTime := time.Now()
   704  			link := setting.AppSubURL + strings.TrimSuffix(req.URL.EscapedPath(), "/")
   705  
   706  			ctx := Context{
   707  				Resp:    NewResponse(resp),
   708  				Cache:   mc.GetCache(),
   709  				Locale:  locale,
   710  				Link:    link,
   711  				Render:  rnd,
   712  				Session: session.GetSession(req),
   713  				Repo: &Repository{
   714  					PullRequest: &PullRequest{},
   715  				},
   716  				Org: &Organization{},
   717  				Data: map[string]interface{}{
   718  					"CurrentURL":    setting.AppSubURL + req.URL.RequestURI(),
   719  					"PageStartTime": startTime,
   720  					"Link":          link,
   721  					"RunModeIsProd": setting.IsProd,
   722  				},
   723  			}
   724  			defer ctx.Close()
   725  
   726  			// PageData is passed by reference, and it will be rendered to `window.config.pageData` in `head.tmpl` for JavaScript modules
   727  			ctx.PageData = map[string]interface{}{}
   728  			ctx.Data["PageData"] = ctx.PageData
   729  			ctx.Data["Context"] = &ctx
   730  
   731  			ctx.Req = WithContext(req, &ctx)
   732  			ctx.csrf = PrepareCSRFProtector(csrfOpts, &ctx)
   733  
   734  			// Get flash.
   735  			flashCookie := ctx.GetCookie("macaron_flash")
   736  			vals, _ := url.ParseQuery(flashCookie)
   737  			if len(vals) > 0 {
   738  				f := &middleware.Flash{
   739  					DataStore:  &ctx,
   740  					Values:     vals,
   741  					ErrorMsg:   vals.Get("error"),
   742  					SuccessMsg: vals.Get("success"),
   743  					InfoMsg:    vals.Get("info"),
   744  					WarningMsg: vals.Get("warning"),
   745  				}
   746  				ctx.Data["Flash"] = f
   747  			}
   748  
   749  			f := &middleware.Flash{
   750  				DataStore:  &ctx,
   751  				Values:     url.Values{},
   752  				ErrorMsg:   "",
   753  				WarningMsg: "",
   754  				InfoMsg:    "",
   755  				SuccessMsg: "",
   756  			}
   757  			ctx.Resp.Before(func(resp ResponseWriter) {
   758  				if flash := f.Encode(); len(flash) > 0 {
   759  					middleware.SetCookie(resp, "macaron_flash", flash, 0,
   760  						setting.SessionConfig.CookiePath,
   761  						middleware.Domain(setting.SessionConfig.Domain),
   762  						middleware.HTTPOnly(true),
   763  						middleware.Secure(setting.SessionConfig.Secure),
   764  						middleware.SameSite(setting.SessionConfig.SameSite),
   765  					)
   766  					return
   767  				}
   768  
   769  				middleware.SetCookie(ctx.Resp, "macaron_flash", "", -1,
   770  					setting.SessionConfig.CookiePath,
   771  					middleware.Domain(setting.SessionConfig.Domain),
   772  					middleware.HTTPOnly(true),
   773  					middleware.Secure(setting.SessionConfig.Secure),
   774  					middleware.SameSite(setting.SessionConfig.SameSite),
   775  				)
   776  			})
   777  
   778  			ctx.Flash = f
   779  
   780  			// If request sends files, parse them here otherwise the Query() can't be parsed and the CsrfToken will be invalid.
   781  			if ctx.Req.Method == "POST" && strings.Contains(ctx.Req.Header.Get("Content-Type"), "multipart/form-data") {
   782  				if err := ctx.Req.ParseMultipartForm(setting.Attachment.MaxSize << 20); err != nil && !strings.Contains(err.Error(), "EOF") { // 32MB max size
   783  					ctx.ServerError("ParseMultipartForm", err)
   784  					return
   785  				}
   786  			}
   787  
   788  			httpcache.SetCacheControlInHeader(ctx.Resp.Header(), 0, "no-transform")
   789  			ctx.Resp.Header().Set(`X-Frame-Options`, setting.CORSConfig.XFrameOptions)
   790  
   791  			ctx.Data["CsrfToken"] = ctx.csrf.GetToken()
   792  			ctx.Data["CsrfTokenHtml"] = template.HTML(`<input type="hidden" name="_csrf" value="` + ctx.Data["CsrfToken"].(string) + `">`)
   793  
   794  			// FIXME: do we really always need these setting? There should be someway to have to avoid having to always set these
   795  			ctx.Data["IsLandingPageHome"] = setting.LandingPageURL == setting.LandingPageHome
   796  			ctx.Data["IsLandingPageExplore"] = setting.LandingPageURL == setting.LandingPageExplore
   797  			ctx.Data["IsLandingPageOrganizations"] = setting.LandingPageURL == setting.LandingPageOrganizations
   798  
   799  			ctx.Data["ShowRegistrationButton"] = setting.Service.ShowRegistrationButton
   800  			ctx.Data["ShowMilestonesDashboardPage"] = setting.Service.ShowMilestonesDashboardPage
   801  			ctx.Data["ShowFooterBranding"] = setting.ShowFooterBranding
   802  			ctx.Data["ShowFooterVersion"] = setting.ShowFooterVersion
   803  
   804  			ctx.Data["EnableSwagger"] = setting.API.EnableSwagger
   805  			ctx.Data["EnableOpenIDSignIn"] = setting.Service.EnableOpenIDSignIn
   806  			ctx.Data["DisableMigrations"] = setting.Repository.DisableMigrations
   807  			ctx.Data["DisableStars"] = setting.Repository.DisableStars
   808  			ctx.Data["EnableActions"] = setting.Actions.Enabled
   809  
   810  			ctx.Data["ManifestData"] = setting.ManifestData
   811  
   812  			ctx.Data["UnitWikiGlobalDisabled"] = unit.TypeWiki.UnitGlobalDisabled()
   813  			ctx.Data["UnitIssuesGlobalDisabled"] = unit.TypeIssues.UnitGlobalDisabled()
   814  			ctx.Data["UnitPullsGlobalDisabled"] = unit.TypePullRequests.UnitGlobalDisabled()
   815  			ctx.Data["UnitProjectsGlobalDisabled"] = unit.TypeProjects.UnitGlobalDisabled()
   816  			ctx.Data["UnitActionsGlobalDisabled"] = unit.TypeActions.UnitGlobalDisabled()
   817  
   818  			ctx.Data["locale"] = locale
   819  			ctx.Data["AllLangs"] = translation.AllLangs()
   820  
   821  			next.ServeHTTP(ctx.Resp, ctx.Req)
   822  
   823  			// Handle adding signedUserName to the context for the AccessLogger
   824  			usernameInterface := ctx.Data["SignedUserName"]
   825  			identityPtrInterface := ctx.Req.Context().Value(signedUserNameStringPointerKey)
   826  			if usernameInterface != nil && identityPtrInterface != nil {
   827  				username := usernameInterface.(string)
   828  				identityPtr := identityPtrInterface.(*string)
   829  				if identityPtr != nil && username != "" {
   830  					*identityPtr = username
   831  				}
   832  			}
   833  		})
   834  	}
   835  }
   836  
   837  // SearchOrderByMap represents all possible search order
   838  var SearchOrderByMap = map[string]map[string]db.SearchOrderBy{
   839  	"asc": {
   840  		"alpha":   db.SearchOrderByAlphabetically,
   841  		"created": db.SearchOrderByOldest,
   842  		"updated": db.SearchOrderByLeastUpdated,
   843  		"size":    db.SearchOrderBySize,
   844  		"id":      db.SearchOrderByID,
   845  	},
   846  	"desc": {
   847  		"alpha":   db.SearchOrderByAlphabeticallyReverse,
   848  		"created": db.SearchOrderByNewest,
   849  		"updated": db.SearchOrderByRecentUpdated,
   850  		"size":    db.SearchOrderBySizeReverse,
   851  		"id":      db.SearchOrderByIDReverse,
   852  	},
   853  }