726 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			726 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			Go
		
	
	
	
| // Copyright 2014 The Gogs Authors. All rights reserved.
 | |
| // Copyright 2020 The Gitea Authors. All rights reserved.
 | |
| // Use of this source code is governed by a MIT-style
 | |
| // license that can be found in the LICENSE file.
 | |
| 
 | |
| package context
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"crypto/sha256"
 | |
| 	"encoding/hex"
 | |
| 	"fmt"
 | |
| 	"html"
 | |
| 	"html/template"
 | |
| 	"io"
 | |
| 	"net/http"
 | |
| 	"net/url"
 | |
| 	"path"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| 	"time"
 | |
| 
 | |
| 	"code.gitea.io/gitea/models"
 | |
| 	"code.gitea.io/gitea/modules/auth/sso"
 | |
| 	"code.gitea.io/gitea/modules/base"
 | |
| 	mc "code.gitea.io/gitea/modules/cache"
 | |
| 	"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/web/middleware"
 | |
| 
 | |
| 	"gitea.com/go-chi/cache"
 | |
| 	"gitea.com/go-chi/session"
 | |
| 	"github.com/go-chi/chi"
 | |
| 	jsoniter "github.com/json-iterator/go"
 | |
| 	"github.com/unknwon/com"
 | |
| 	"github.com/unknwon/i18n"
 | |
| 	"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{}
 | |
| 	Render Render
 | |
| 	translation.Locale
 | |
| 	Cache   cache.Cache
 | |
| 	csrf    CSRF
 | |
| 	Flash   *middleware.Flash
 | |
| 	Session session.Store
 | |
| 
 | |
| 	Link        string // current request URL
 | |
| 	EscapedLink string
 | |
| 	User        *models.User
 | |
| 	IsSigned    bool
 | |
| 	IsBasicAuth bool
 | |
| 
 | |
| 	Repo *Repository
 | |
| 	Org  *Organization
 | |
| }
 | |
| 
 | |
| // 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.User.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 []models.UnitType) 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 models.UnitType) 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 := models.GetUserByID(redirectUserID)
 | |
| 	if err != nil {
 | |
| 		ctx.ServerError("GetUserByID", err)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	redirectPath := strings.Replace(
 | |
| 		ctx.Req.URL.Path,
 | |
| 		userName,
 | |
| 		user.Name,
 | |
| 		1,
 | |
| 	)
 | |
| 	if ctx.Req.URL.RawQuery != "" {
 | |
| 		redirectPath += "?" + ctx.Req.URL.RawQuery
 | |
| 	}
 | |
| 	ctx.Redirect(path.Join(setting.AppSubURL, redirectPath))
 | |
| }
 | |
| 
 | |
| // 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.
 | |
| 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
 | |
| 		}
 | |
| 
 | |
| 		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 converts template name to string.
 | |
| func (ctx *Context) HTML(status int, name base.TplName) {
 | |
| 	log.Debug("Template: %s", name)
 | |
| 	var startTime = time.Now()
 | |
| 	ctx.Data["TmplLoadTimes"] = func() string {
 | |
| 		return fmt.Sprint(time.Since(startTime).Nanoseconds()/1e6) + "ms"
 | |
| 	}
 | |
| 	if err := ctx.Render.HTML(ctx.Resp, status, string(name), ctx.Data); err != nil {
 | |
| 		if status == http.StatusInternalServerError && name == base.TplName("status/500") {
 | |
| 			ctx.PlainText(http.StatusInternalServerError, []byte("Unable to find status/500 template"))
 | |
| 			return
 | |
| 		}
 | |
| 		ctx.ServerError("Render failed", err)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // HTMLString render content to a string but not http.ResponseWriter
 | |
| func (ctx *Context) HTMLString(name string, data interface{}) (string, error) {
 | |
| 	var buf strings.Builder
 | |
| 	var startTime = time.Now()
 | |
| 	ctx.Data["TmplLoadTimes"] = func() string {
 | |
| 		return fmt.Sprint(time.Since(startTime).Nanoseconds()/1e6) + "ms"
 | |
| 	}
 | |
| 	err := ctx.Render.HTML(&buf, 200, 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(title string, err error) {
 | |
| 	ctx.notFoundInternal(title, err)
 | |
| }
 | |
| 
 | |
| func (ctx *Context) notFoundInternal(title string, err error) {
 | |
| 	if err != nil {
 | |
| 		log.ErrorWithSkip(2, "%s: %v", title, err)
 | |
| 		if !setting.IsProd() {
 | |
| 			ctx.Data["ErrorMsg"] = err
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// response simple meesage if Accept isn't text/html
 | |
| 	reqTypes, has := ctx.Req.Header["Accept"]
 | |
| 	if has && len(reqTypes) > 0 {
 | |
| 		notHTML := true
 | |
| 		for _, part := range reqTypes {
 | |
| 			if strings.Contains(part, "text/html") {
 | |
| 				notHTML = false
 | |
| 				break
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		if notHTML {
 | |
| 			ctx.PlainText(404, []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(title string, err error) {
 | |
| 	ctx.serverErrorInternal(title, err)
 | |
| }
 | |
| 
 | |
| func (ctx *Context) serverErrorInternal(title string, err error) {
 | |
| 	if err != nil {
 | |
| 		log.ErrorWithSkip(2, "%s: %v", title, err)
 | |
| 		if !setting.IsProd() {
 | |
| 			ctx.Data["ErrorMsg"] = err
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	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 responses with 404 status code for not found error,
 | |
| // or error context description for logging purpose of 500 server error.
 | |
| func (ctx *Context) NotFoundOrServerError(title string, errck func(error) bool, err error) {
 | |
| 	if errck(err) {
 | |
| 		ctx.notFoundInternal(title, err)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	ctx.serverErrorInternal(title, err)
 | |
| }
 | |
| 
 | |
| // Header returns a header
 | |
| func (ctx *Context) Header() http.Header {
 | |
| 	return ctx.Resp.Header()
 | |
| }
 | |
| 
 | |
| // FIXME: We should differ Query and Form, currently we just use form as query
 | |
| // Currently to be compatible with macaron, we keep it.
 | |
| 
 | |
| // Query returns request form as string with default
 | |
| func (ctx *Context) Query(key string, defaults ...string) string {
 | |
| 	return (*Forms)(ctx.Req).MustString(key, defaults...)
 | |
| }
 | |
| 
 | |
| // QueryTrim returns request form as string with default and trimmed spaces
 | |
| func (ctx *Context) QueryTrim(key string, defaults ...string) string {
 | |
| 	return (*Forms)(ctx.Req).MustTrimmed(key, defaults...)
 | |
| }
 | |
| 
 | |
| // QueryStrings returns request form as strings with default
 | |
| func (ctx *Context) QueryStrings(key string, defaults ...[]string) []string {
 | |
| 	return (*Forms)(ctx.Req).MustStrings(key, defaults...)
 | |
| }
 | |
| 
 | |
| // QueryInt returns request form as int with default
 | |
| func (ctx *Context) QueryInt(key string, defaults ...int) int {
 | |
| 	return (*Forms)(ctx.Req).MustInt(key, defaults...)
 | |
| }
 | |
| 
 | |
| // QueryInt64 returns request form as int64 with default
 | |
| func (ctx *Context) QueryInt64(key string, defaults ...int64) int64 {
 | |
| 	return (*Forms)(ctx.Req).MustInt64(key, defaults...)
 | |
| }
 | |
| 
 | |
| // QueryBool returns request form as bool with default
 | |
| func (ctx *Context) QueryBool(key string, defaults ...bool) bool {
 | |
| 	return (*Forms)(ctx.Req).MustBool(key, defaults...)
 | |
| }
 | |
| 
 | |
| // HandleText handles HTTP status code
 | |
| func (ctx *Context) HandleText(status int, title string) {
 | |
| 	if (status/100 == 4) || (status/100 == 5) {
 | |
| 		log.Error("%s", title)
 | |
| 	}
 | |
| 	ctx.PlainText(status, []byte(title))
 | |
| }
 | |
| 
 | |
| // ServeContent serves content to http request
 | |
| func (ctx *Context) ServeContent(name string, r io.ReadSeeker, params ...interface{}) {
 | |
| 	modtime := time.Now()
 | |
| 	for _, p := range params {
 | |
| 		switch v := p.(type) {
 | |
| 		case time.Time:
 | |
| 			modtime = v
 | |
| 		}
 | |
| 	}
 | |
| 	ctx.Resp.Header().Set("Content-Description", "File Transfer")
 | |
| 	ctx.Resp.Header().Set("Content-Type", "application/octet-stream")
 | |
| 	ctx.Resp.Header().Set("Content-Disposition", "attachment; filename="+name)
 | |
| 	ctx.Resp.Header().Set("Content-Transfer-Encoding", "binary")
 | |
| 	ctx.Resp.Header().Set("Expires", "0")
 | |
| 	ctx.Resp.Header().Set("Cache-Control", "must-revalidate")
 | |
| 	ctx.Resp.Header().Set("Pragma", "public")
 | |
| 	ctx.Resp.Header().Set("Access-Control-Expose-Headers", "Content-Disposition")
 | |
| 	http.ServeContent(ctx.Resp, ctx.Req, name, modtime, r)
 | |
| }
 | |
| 
 | |
| // PlainText render content as plain text
 | |
| func (ctx *Context) PlainText(status int, bs []byte) {
 | |
| 	ctx.Resp.WriteHeader(status)
 | |
| 	ctx.Resp.Header().Set("Content-Type", "text/plain;charset=utf-8")
 | |
| 	if _, err := ctx.Resp.Write(bs); err != nil {
 | |
| 		ctx.ServerError("Render JSON failed", err)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // ServeFile serves given file to response.
 | |
| func (ctx *Context) ServeFile(file string, names ...string) {
 | |
| 	var name string
 | |
| 	if len(names) > 0 {
 | |
| 		name = names[0]
 | |
| 	} else {
 | |
| 		name = path.Base(file)
 | |
| 	}
 | |
| 	ctx.Resp.Header().Set("Content-Description", "File Transfer")
 | |
| 	ctx.Resp.Header().Set("Content-Type", "application/octet-stream")
 | |
| 	ctx.Resp.Header().Set("Content-Disposition", "attachment; filename="+name)
 | |
| 	ctx.Resp.Header().Set("Content-Transfer-Encoding", "binary")
 | |
| 	ctx.Resp.Header().Set("Expires", "0")
 | |
| 	ctx.Resp.Header().Set("Cache-Control", "must-revalidate")
 | |
| 	ctx.Resp.Header().Set("Pragma", "public")
 | |
| 	http.ServeFile(ctx.Resp, ctx.Req, file)
 | |
| }
 | |
| 
 | |
| // Error returned an error to web browser
 | |
| func (ctx *Context) Error(status int, contents ...string) {
 | |
| 	var 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)
 | |
| 	json := jsoniter.ConfigCompatibleWithStandardLibrary
 | |
| 	if err := json.NewEncoder(ctx.Resp).Encode(content); err != nil {
 | |
| 		ctx.ServerError("Render JSON failed", err)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // Redirect redirect the request
 | |
| func (ctx *Context) Redirect(location string, status ...int) {
 | |
| 	code := http.StatusFound
 | |
| 	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 = com.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 := com.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.Req.Context())
 | |
| 	chiCtx.URLParams.Add(strings.TrimPrefix(k, ":"), url.PathEscape(v))
 | |
| }
 | |
| 
 | |
| // Write writes data to webbrowser
 | |
| 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)
 | |
| }
 | |
| 
 | |
| // Handler represents a custom handler
 | |
| type Handler func(*Context)
 | |
| 
 | |
| // enumerate all content
 | |
| var (
 | |
| 	contextKey interface{} = "default_context"
 | |
| )
 | |
| 
 | |
| // 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)
 | |
| }
 | |
| 
 | |
| // SignedUserName returns signed user's name via context
 | |
| func SignedUserName(req *http.Request) string {
 | |
| 	if middleware.IsInternalPath(req) {
 | |
| 		return ""
 | |
| 	}
 | |
| 	if middleware.IsAPIPath(req) {
 | |
| 		ctx, ok := req.Context().Value(apiContextKey).(*APIContext)
 | |
| 		if ok {
 | |
| 			v := ctx.Data["SignedUserName"]
 | |
| 			if res, ok := v.(string); ok {
 | |
| 				return res
 | |
| 			}
 | |
| 		}
 | |
| 	} else {
 | |
| 		ctx, ok := req.Context().Value(contextKey).(*Context)
 | |
| 		if ok {
 | |
| 			v := ctx.Data["SignedUserName"]
 | |
| 			if res, ok := v.(string); ok {
 | |
| 				return res
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	return ""
 | |
| }
 | |
| 
 | |
| 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() func(next http.Handler) http.Handler {
 | |
| 	var rnd = templates.HTMLRenderer()
 | |
| 	var csrfOpts = getCsrfOpts()
 | |
| 
 | |
| 	return func(next http.Handler) http.Handler {
 | |
| 		return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
 | |
| 			var locale = middleware.Locale(resp, req)
 | |
| 			var startTime = time.Now()
 | |
| 			var link = setting.AppSubURL + strings.TrimSuffix(req.URL.EscapedPath(), "/")
 | |
| 			var 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,
 | |
| 				},
 | |
| 			}
 | |
| 
 | |
| 			ctx.Req = WithContext(req, &ctx)
 | |
| 			ctx.csrf = Csrfer(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
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			// Get user from session if logged in.
 | |
| 			ctx.User, ctx.IsBasicAuth = sso.SignedInUser(ctx.Req, ctx.Resp, &ctx, ctx.Session)
 | |
| 
 | |
| 			if ctx.User != nil {
 | |
| 				ctx.IsSigned = true
 | |
| 				ctx.Data["IsSigned"] = ctx.IsSigned
 | |
| 				ctx.Data["SignedUser"] = ctx.User
 | |
| 				ctx.Data["SignedUserID"] = ctx.User.ID
 | |
| 				ctx.Data["SignedUserName"] = ctx.User.Name
 | |
| 				ctx.Data["IsAdmin"] = ctx.User.IsAdmin
 | |
| 			} else {
 | |
| 				ctx.Data["SignedUserID"] = int64(0)
 | |
| 				ctx.Data["SignedUserName"] = ""
 | |
| 			}
 | |
| 
 | |
| 			ctx.Resp.Header().Set(`X-Frame-Options`, `SAMEORIGIN`)
 | |
| 
 | |
| 			ctx.Data["CsrfToken"] = html.EscapeString(ctx.csrf.GetToken())
 | |
| 			ctx.Data["CsrfTokenHtml"] = template.HTML(`<input type="hidden" name="_csrf" value="` + ctx.Data["CsrfToken"].(string) + `">`)
 | |
| 			log.Debug("Session ID: %s", ctx.Session.ID())
 | |
| 			log.Debug("CSRF Token: %v", ctx.Data["CsrfToken"])
 | |
| 
 | |
| 			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["ManifestData"] = setting.ManifestData
 | |
| 
 | |
| 			ctx.Data["i18n"] = locale
 | |
| 			ctx.Data["Tr"] = i18n.Tr
 | |
| 			ctx.Data["Lang"] = locale.Language()
 | |
| 			ctx.Data["AllLangs"] = translation.AllLangs()
 | |
| 			for _, lang := range translation.AllLangs() {
 | |
| 				if lang.Lang == locale.Language() {
 | |
| 					ctx.Data["LangName"] = lang.Name
 | |
| 					break
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			next.ServeHTTP(ctx.Resp, ctx.Req)
 | |
| 		})
 | |
| 	}
 | |
| }
 |