2014-03-15 15:01:50 +04:00
// Copyright 2014 The Gogs Authors. All rights reserved.
2020-01-10 00:34:25 +03:00
// Copyright 2020 The Gitea Authors. All rights reserved.
2014-03-15 15:01:50 +04:00
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
2016-03-11 19:56:52 +03:00
package context
2014-03-15 15:01:50 +04:00
import (
2021-01-26 18:36:53 +03:00
"context"
"crypto/sha256"
"encoding/hex"
2022-02-06 22:28:25 +03:00
"errors"
2022-04-07 21:59:56 +03:00
"fmt"
2016-11-30 00:49:06 +03:00
"html"
2014-03-22 21:44:02 +04:00
"html/template"
2014-04-15 20:27:29 +04:00
"io"
2022-02-06 22:28:25 +03:00
"net"
2014-03-15 15:01:50 +04:00
"net/http"
2018-03-16 00:13:34 +03:00
"net/url"
2017-06-26 04:06:40 +03:00
"path"
2021-01-26 18:36:53 +03:00
"strconv"
2014-03-23 00:40:09 +04:00
"strings"
2014-03-19 17:57:55 +04:00
"time"
2014-03-15 15:01:50 +04:00
2022-04-07 21:59:56 +03:00
"code.gitea.io/gitea/models/db"
2021-11-09 22:57:58 +03:00
"code.gitea.io/gitea/models/unit"
2021-11-24 12:49:20 +03:00
user_model "code.gitea.io/gitea/models/user"
2016-11-10 19:24:48 +03:00
"code.gitea.io/gitea/modules/base"
2021-01-27 17:56:54 +03:00
mc "code.gitea.io/gitea/modules/cache"
2022-01-20 02:26:57 +03:00
"code.gitea.io/gitea/modules/git"
2021-07-24 19:03:58 +03:00
"code.gitea.io/gitea/modules/json"
2016-11-10 19:24:48 +03:00
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
2021-01-26 18:36:53 +03:00
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/translation"
2022-04-01 11:47:50 +03:00
"code.gitea.io/gitea/modules/util"
2021-01-30 11:55:53 +03:00
"code.gitea.io/gitea/modules/web/middleware"
2021-06-09 20:53:16 +03:00
"code.gitea.io/gitea/services/auth"
2019-08-23 19:40:30 +03:00
2021-01-26 18:36:53 +03:00
"gitea.com/go-chi/cache"
"gitea.com/go-chi/session"
2021-10-14 05:50:23 +03:00
chi "github.com/go-chi/chi/v5"
2021-01-26 18:36:53 +03:00
"github.com/unrolled/render"
"golang.org/x/crypto/pbkdf2"
2014-03-15 15:01:50 +04:00
)
2021-01-26 18:36:53 +03:00
// 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
}
2014-03-15 17:17:16 +04:00
// Context represents context of a request.
2014-03-15 15:01:50 +04:00
type Context struct {
2021-10-12 21:11:35 +03:00
Resp ResponseWriter
Req * http . Request
Data map [ string ] interface { } // data used by MVC templates
2021-10-15 05:35:26 +03:00
PageData map [ string ] interface { } // data used by JavaScript modules in one page, it's `window.config.pageData`
2021-10-12 21:11:35 +03:00
Render Render
2021-01-26 18:36:53 +03:00
translation . Locale
2014-08-01 01:25:34 +04:00
Cache cache . Cache
2022-04-08 08:21:05 +03:00
csrf CSRFProtector
2021-01-30 11:55:53 +03:00
Flash * middleware . Flash
2014-07-26 08:24:27 +04:00
Session session . Store
2017-06-26 04:06:40 +03:00
Link string // current request URL
2017-11-28 12:43:51 +03:00
EscapedLink string
2022-03-22 10:03:22 +03:00
Doer * user_model . User
2014-11-18 19:07:16 +03:00
IsSigned bool
IsBasicAuth bool
2014-03-15 20:03:23 +04:00
2022-03-26 12:04:22 +03:00
ContextUser * user_model . User
Repo * Repository
Org * Organization
2022-03-30 11:42:47 +03:00
Package * Package
2014-03-15 15:01:50 +04:00
}
2022-05-05 17:13:23 +03:00
// 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
}
2021-11-16 21:18:25 +03:00
// 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 ... )
}
2021-01-05 16:05:40 +03:00
// GetData returns the data
func ( ctx * Context ) GetData ( ) map [ string ] interface { } {
return ctx . Data
}
2019-04-08 01:49:34 +03:00
// IsUserSiteAdmin returns true if current user is a site admin
func ( ctx * Context ) IsUserSiteAdmin ( ) bool {
2022-03-22 10:03:22 +03:00
return ctx . IsSigned && ctx . Doer . IsAdmin
2019-04-08 01:49:34 +03:00
}
// 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
2021-11-09 22:57:58 +03:00
func ( ctx * Context ) IsUserRepoWriter ( unitTypes [ ] unit . Type ) bool {
2019-04-08 01:49:34 +03:00
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
2021-11-09 22:57:58 +03:00
func ( ctx * Context ) IsUserRepoReaderSpecific ( unitType unit . Type ) bool {
2019-04-08 01:49:34 +03:00
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 ( )
}
2021-01-24 18:23:05 +03:00
// RedirectToUser redirect to a differently-named user
func RedirectToUser ( ctx * Context , userName string , redirectUserID int64 ) {
2021-11-24 12:49:20 +03:00
user , err := user_model . GetUserByID ( redirectUserID )
2021-01-24 18:23:05 +03:00
if err != nil {
ctx . ServerError ( "GetUserByID" , err )
return
}
redirectPath := strings . Replace (
2021-11-16 21:18:25 +03:00
ctx . Req . URL . EscapedPath ( ) ,
url . PathEscape ( userName ) ,
url . PathEscape ( user . Name ) ,
2021-01-24 18:23:05 +03:00
1 ,
)
if ctx . Req . URL . RawQuery != "" {
redirectPath += "?" + ctx . Req . URL . RawQuery
}
2022-03-23 07:54:07 +03:00
ctx . Redirect ( path . Join ( setting . AppSubURL , redirectPath ) , http . StatusTemporaryRedirect )
2021-01-24 18:23:05 +03:00
}
2016-11-25 09:51:01 +03:00
// HasAPIError returns true if error occurs in form validation.
func ( ctx * Context ) HasAPIError ( ) bool {
2014-05-05 21:08:01 +04:00
hasErr , ok := ctx . Data [ "HasError" ]
if ! ok {
return false
}
return hasErr . ( bool )
}
2016-11-25 09:51:01 +03:00
// GetErrMsg returns error message
2014-05-05 21:08:01 +04:00
func ( ctx * Context ) GetErrMsg ( ) string {
return ctx . Data [ "ErrorMsg" ] . ( string )
}
2014-03-15 18:52:14 +04:00
// HasError returns true if error occurs in form validation.
2021-12-15 09:59:57 +03:00
// Attention: this function changes ctx.Data and ctx.Flash
2014-03-15 18:52:14 +04:00
func ( ctx * Context ) HasError ( ) bool {
hasErr , ok := ctx . Data [ "HasError" ]
if ! ok {
return false
}
2014-04-14 02:12:07 +04:00
ctx . Flash . ErrorMsg = ctx . Data [ "ErrorMsg" ] . ( string )
ctx . Data [ "Flash" ] = ctx . Flash
2014-03-15 18:52:14 +04:00
return hasErr . ( bool )
}
2015-07-08 14:47:56 +03:00
// HasValue returns true if value of given name exists.
func ( ctx * Context ) HasValue ( name string ) bool {
_ , ok := ctx . Data [ name ]
return ok
}
2018-03-16 00:13:34 +03:00
// RedirectToFirst redirects to first not empty URL
func ( ctx * Context ) RedirectToFirst ( location ... string ) {
for _ , loc := range location {
if len ( loc ) == 0 {
continue
}
2022-03-23 19:12:36 +03:00
// 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
}
2018-03-16 00:13:34 +03:00
u , err := url . Parse ( loc )
2020-01-10 00:34:25 +03:00
if err != nil || ( ( u . Scheme != "" || u . Host != "" ) && ! strings . HasPrefix ( strings . ToLower ( loc ) , strings . ToLower ( setting . AppURL ) ) ) {
2018-03-16 00:13:34 +03:00
continue
}
ctx . Redirect ( loc )
return
}
ctx . Redirect ( setting . AppSubURL + "/" )
}
2021-12-15 09:59:57 +03:00
// HTML calls Context.HTML and renders the template to HTTP response
2014-07-26 08:24:27 +04:00
func ( ctx * Context ) HTML ( status int , name base . TplName ) {
2015-12-20 09:06:54 +03:00
log . Debug ( "Template: %s" , name )
2021-12-15 09:59:57 +03:00
tmplStartTime := time . Now ( )
2022-02-11 20:08:22 +03:00
if ! setting . IsProd {
ctx . Data [ "TemplateName" ] = name
}
ctx . Data [ "TemplateLoadTimes" ] = func ( ) string {
2021-12-15 09:59:57 +03:00
return strconv . FormatInt ( time . Since ( tmplStartTime ) . Nanoseconds ( ) / 1e6 , 10 ) + "ms"
2021-01-29 07:33:47 +03:00
}
2021-01-26 18:36:53 +03:00
if err := ctx . Render . HTML ( ctx . Resp , status , string ( name ) , ctx . Data ) ; err != nil {
2021-02-20 07:26:57 +03:00
if status == http . StatusInternalServerError && name == base . TplName ( "status/500" ) {
2021-12-15 09:59:57 +03:00
ctx . PlainText ( http . StatusInternalServerError , "Unable to find status/500 template" )
2021-02-20 07:26:57 +03:00
return
}
2021-01-26 18:36:53 +03:00
ctx . ServerError ( "Render failed" , err )
}
}
2021-12-15 09:59:57 +03:00
// RenderToString renders the template content to a string
func ( ctx * Context ) RenderToString ( name base . TplName , data map [ string ] interface { } ) ( string , error ) {
2021-01-26 18:36:53 +03:00
var buf strings . Builder
2022-03-23 07:54:07 +03:00
err := ctx . Render . HTML ( & buf , http . StatusOK , string ( name ) , data )
2021-01-26 18:36:53 +03:00
return buf . String ( ) , err
2014-03-20 15:50:26 +04:00
}
2014-03-15 18:52:14 +04:00
// RenderWithErr used for page has form validation but need to prompt error to users.
2014-07-26 08:24:27 +04:00
func ( ctx * Context ) RenderWithErr ( msg string , tpl base . TplName , form interface { } ) {
2014-04-03 23:50:55 +04:00
if form != nil {
2021-01-30 11:55:53 +03:00
middleware . AssignForm ( form , ctx . Data )
2014-04-03 23:50:55 +04:00
}
2014-04-11 00:36:50 +04:00
ctx . Flash . ErrorMsg = msg
ctx . Data [ "Flash" ] = ctx . Flash
2021-04-05 18:30:52 +03:00
ctx . HTML ( http . StatusOK , tpl )
2014-03-15 18:52:14 +04:00
}
2018-01-11 00:34:17 +03:00
// NotFound displays a 404 (Not Found) page and prints the given error, if any.
2021-12-15 09:59:57 +03:00
func ( ctx * Context ) NotFound ( logMsg string , logErr error ) {
ctx . notFoundInternal ( logMsg , logErr )
2019-04-09 21:10:42 +03:00
}
2021-12-15 09:59:57 +03:00
func ( ctx * Context ) notFoundInternal ( logMsg string , logErr error ) {
if logErr != nil {
2022-01-29 23:52:37 +03:00
log . Log ( 2 , log . DEBUG , "%s: %v" , logMsg , logErr )
2021-10-20 17:37:19 +03:00
if ! setting . IsProd {
2021-12-15 09:59:57 +03:00
ctx . Data [ "ErrorMsg" ] = logErr
2014-05-02 02:53:41 +04:00
}
2014-03-19 12:48:45 +04:00
}
2021-12-15 09:59:57 +03:00
// 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
2021-04-01 18:11:42 +03:00
}
2021-12-15 09:59:57 +03:00
}
2021-04-01 18:11:42 +03:00
2021-12-15 09:59:57 +03:00
if ! showHTML {
2022-01-29 23:52:37 +03:00
ctx . plainTextInternal ( 3 , http . StatusNotFound , [ ] byte ( "Not found.\n" ) )
2021-12-15 09:59:57 +03:00
return
2021-04-01 18:11:42 +03:00
}
2019-02-20 02:09:47 +03:00
ctx . Data [ "IsRepo" ] = ctx . Repo . Repository != nil
2018-01-11 00:34:17 +03:00
ctx . Data [ "Title" ] = "Page Not Found"
ctx . HTML ( http . StatusNotFound , base . TplName ( "status/404" ) )
}
2021-12-15 09:59:57 +03:00
// 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 )
2019-04-09 21:10:42 +03:00
}
2021-12-15 09:59:57 +03:00
func ( ctx * Context ) serverErrorInternal ( logMsg string , logErr error ) {
if logErr != nil {
log . ErrorWithSkip ( 2 , "%s: %v" , logMsg , logErr )
2022-03-10 23:23:15 +03:00
if _ , ok := logErr . ( * net . OpError ) ; ok || errors . Is ( logErr , & net . OpError { } ) {
2022-02-06 22:28:25 +03:00
// This is an error within the underlying connection
// and further rendering will not work so just return
return
}
2021-10-20 17:37:19 +03:00
if ! setting . IsProd {
2021-12-15 09:59:57 +03:00
ctx . Data [ "ErrorMsg" ] = logErr
2018-01-11 00:34:17 +03:00
}
2014-05-02 02:53:41 +04:00
}
2018-01-11 00:34:17 +03:00
ctx . Data [ "Title" ] = "Internal Server Error"
2019-01-31 01:00:00 +03:00
ctx . HTML ( http . StatusInternalServerError , base . TplName ( "status/500" ) )
2014-03-15 15:01:50 +04:00
}
2016-08-30 12:08:38 +03:00
// NotFoundOrServerError use error check function to determine if the error
2021-12-15 09:59:57 +03:00
// is about not found. It responds with 404 status code for not found error,
2016-08-30 12:08:38 +03:00
// or error context description for logging purpose of 500 server error.
2021-12-15 09:59:57 +03:00
func ( ctx * Context ) NotFoundOrServerError ( logMsg string , errCheck func ( error ) bool , err error ) {
if errCheck ( err ) {
ctx . notFoundInternal ( logMsg , err )
2016-07-25 21:48:17 +03:00
return
}
2021-12-15 09:59:57 +03:00
ctx . serverErrorInternal ( logMsg , err )
}
2016-07-25 21:48:17 +03:00
2021-12-15 09:59:57 +03:00
// PlainTextBytes renders bytes as plain text
2022-01-29 23:52:37 +03:00
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 ) )
2021-12-15 09:59:57 +03:00
}
ctx . Resp . WriteHeader ( status )
ctx . Resp . Header ( ) . Set ( "Content-Type" , "text/plain;charset=utf-8" )
2022-01-22 21:32:35 +03:00
ctx . Resp . Header ( ) . Set ( "X-Content-Type-Options" , "nosniff" )
2021-12-15 09:59:57 +03:00
if _ , err := ctx . Resp . Write ( bs ) ; err != nil {
2022-01-29 23:52:37 +03:00
log . ErrorWithSkip ( skip , "plainTextInternal (status=%d): write bytes failed: %v" , status , err )
2021-12-15 09:59:57 +03:00
}
2016-07-25 21:48:17 +03:00
}
2022-01-29 23:52:37 +03:00
// PlainTextBytes renders bytes as plain text
func ( ctx * Context ) PlainTextBytes ( status int , bs [ ] byte ) {
ctx . plainTextInternal ( 2 , status , bs )
}
2021-12-15 09:59:57 +03:00
// PlainText renders content as plain text
func ( ctx * Context ) PlainText ( status int , text string ) {
2022-01-29 23:52:37 +03:00
ctx . plainTextInternal ( 2 , status , [ ] byte ( text ) )
2021-01-26 18:36:53 +03:00
}
2021-12-15 09:59:57 +03:00
// RespHeader returns the response header
func ( ctx * Context ) RespHeader ( ) http . Header {
return ctx . Resp . Header ( )
2015-03-28 17:30:05 +03:00
}
2022-03-30 11:42:47 +03:00
// SetServeHeaders sets necessary content serve headers
func ( ctx * Context ) SetServeHeaders ( filename string ) {
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=" + filename )
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" )
}
2016-11-25 09:51:01 +03:00
// ServeContent serves content to http request
2014-04-15 20:27:29 +04:00
func ( ctx * Context ) ServeContent ( name string , r io . ReadSeeker , params ... interface { } ) {
2021-12-15 09:59:57 +03:00
modTime := time . Now ( )
2014-04-15 20:27:29 +04:00
for _ , p := range params {
switch v := p . ( type ) {
case time . Time :
2021-12-15 09:59:57 +03:00
modTime = v
2014-04-15 20:27:29 +04:00
}
}
2022-03-30 11:42:47 +03:00
ctx . SetServeHeaders ( name )
2021-12-15 09:59:57 +03:00
http . ServeContent ( ctx . Resp , ctx . Req , name , modTime , r )
2021-01-26 18:36:53 +03:00
}
// 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 )
}
2022-03-30 11:42:47 +03:00
ctx . SetServeHeaders ( name )
2021-01-26 18:36:53 +03:00
http . ServeFile ( ctx . Resp , ctx . Req , file )
}
2021-06-24 00:12:38 +03:00
// ServeStream serves file via io stream
func ( ctx * Context ) ServeStream ( rd io . Reader , name string ) {
2022-03-30 11:42:47 +03:00
ctx . SetServeHeaders ( name )
2021-06-24 00:12:38 +03:00
_ , err := io . Copy ( ctx . Resp , rd )
if err != nil {
ctx . ServerError ( "Download file failed" , err )
}
}
2022-03-30 11:42:47 +03:00
// 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
}
2021-01-26 18:36:53 +03:00
// Error returned an error to web browser
func ( ctx * Context ) Error ( status int , contents ... string ) {
2022-01-20 20:46:10 +03:00
v := http . StatusText ( status )
2021-01-26 18:36:53 +03:00
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 { } ) {
2021-01-29 16:42:47 +03:00
ctx . Resp . Header ( ) . Set ( "Content-Type" , "application/json;charset=utf-8" )
2021-01-28 00:46:22 +03:00
ctx . Resp . WriteHeader ( status )
2021-01-26 18:36:53 +03:00
if err := json . NewEncoder ( ctx . Resp ) . Encode ( content ) ; err != nil {
ctx . ServerError ( "Render JSON failed" , err )
}
}
2021-12-15 09:59:57 +03:00
// Redirect redirects the request
2021-01-26 18:36:53 +03:00
func ( ctx * Context ) Redirect ( location string , status ... int ) {
2022-03-23 07:54:07 +03:00
code := http . StatusSeeOther
2021-01-26 18:36:53 +03:00
if len ( status ) == 1 {
code = status [ 0 ]
}
http . Redirect ( ctx . Resp , ctx . Req , location , code )
}
2021-03-07 11:12:43 +03:00
// 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 ) )
2021-01-26 18:36:53 +03:00
}
// GetCookie returns given cookie value from request header.
func ( ctx * Context ) GetCookie ( name string ) string {
2021-01-30 11:55:53 +03:00
return middleware . GetCookie ( ctx . Req , name )
2021-01-26 18:36:53 +03:00
}
// GetSuperSecureCookie returns given cookie value from request header with secret string.
func ( ctx * Context ) GetSuperSecureCookie ( secret , name string ) ( string , bool ) {
val := ctx . GetCookie ( name )
2021-03-07 11:12:43 +03:00
return ctx . CookieDecrypt ( secret , val )
}
// CookieDecrypt returns given value from with secret string.
func ( ctx * Context ) CookieDecrypt ( secret , val string ) ( string , bool ) {
2021-01-26 18:36:53 +03:00
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 )
2022-04-01 11:47:50 +03:00
text , err = util . AESGCMDecrypt ( key , text )
2021-01-26 18:36:53 +03:00
return string ( text ) , err == nil
}
// SetSuperSecureCookie sets given cookie value to response header with secret string.
2021-03-07 11:12:43 +03:00
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 {
2021-01-26 18:36:53 +03:00
key := pbkdf2 . Key ( [ ] byte ( secret ) , [ ] byte ( secret ) , 1000 , 16 , sha256 . New )
2022-04-01 11:47:50 +03:00
text , err := util . AESGCMEncrypt ( key , [ ] byte ( value ) )
2021-01-26 18:36:53 +03:00
if err != nil {
panic ( "error encrypting cookie: " + err . Error ( ) )
}
2021-03-07 11:12:43 +03:00
return hex . EncodeToString ( text )
2021-01-26 18:36:53 +03:00
}
// 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 ) {
2021-05-31 09:18:11 +03:00
chiCtx := chi . RouteContext ( ctx )
2021-01-26 18:36:53 +03:00
chiCtx . URLParams . Add ( strings . TrimPrefix ( k , ":" ) , url . PathEscape ( v ) )
}
2021-12-15 09:59:57 +03:00
// Write writes data to web browser
2021-01-26 18:36:53 +03:00
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 )
}
2021-05-31 09:18:11 +03:00
// 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 { } {
2022-01-20 02:26:57 +03:00
if key == git . RepositoryContextKey && ctx . Repo != nil {
return ctx . Repo . GitRepo
}
2021-05-31 09:18:11 +03:00
return ctx . Req . Context ( ) . Value ( key )
}
2022-04-07 21:59:56 +03:00
// 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 , ", " ) )
}
}
2021-01-26 18:36:53 +03:00
// Handler represents a custom handler
type Handler func ( * Context )
2021-12-15 09:59:57 +03:00
type contextKeyType struct { }
var contextKey interface { } = contextKeyType { }
2021-01-26 18:36:53 +03:00
// 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 )
}
2021-09-28 16:13:04 +03:00
// GetContextUser returns context user
2021-11-24 12:49:20 +03:00
func GetContextUser ( req * http . Request ) * user_model . User {
2021-09-28 16:13:04 +03:00
if apiContext , ok := req . Context ( ) . Value ( apiContextKey ) . ( * APIContext ) ; ok {
2022-03-22 10:03:22 +03:00
return apiContext . Doer
2021-09-28 16:13:04 +03:00
}
if ctx , ok := req . Context ( ) . Value ( contextKey ) . ( * Context ) ; ok {
2022-03-22 10:03:22 +03:00
return ctx . Doer
2021-09-28 16:13:04 +03:00
}
return nil
}
2021-01-26 18:36:53 +03:00
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 ,
2021-03-07 11:12:43 +03:00
SameSite : setting . SessionConfig . SameSite ,
2021-01-26 18:36:53 +03:00
}
2014-04-10 22:37:43 +04:00
}
2021-06-09 20:53:16 +03:00
// Auth converts auth.Auth as a middleware
2021-07-24 13:16:34 +03:00
func Auth ( authMethod auth . Method ) func ( * Context ) {
2021-06-09 20:53:16 +03:00
return func ( ctx * Context ) {
2022-03-22 10:03:22 +03:00
ctx . Doer = authMethod . Verify ( ctx . Req , ctx . Resp , ctx , ctx . Session )
if ctx . Doer != nil {
if ctx . Locale . Language ( ) != ctx . Doer . Language {
2021-11-21 00:32:37 +03:00
ctx . Locale = middleware . Locale ( ctx . Resp , ctx . Req )
}
2021-11-20 18:33:18 +03:00
ctx . IsBasicAuth = ctx . Data [ "AuthedMethod" ] . ( string ) == auth . BasicMethodName
2021-06-09 20:53:16 +03:00
ctx . IsSigned = true
ctx . Data [ "IsSigned" ] = ctx . IsSigned
2022-03-22 10:03:22 +03:00
ctx . Data [ "SignedUser" ] = ctx . Doer
ctx . Data [ "SignedUserID" ] = ctx . Doer . ID
ctx . Data [ "SignedUserName" ] = ctx . Doer . Name
ctx . Data [ "IsAdmin" ] = ctx . Doer . IsAdmin
2021-06-09 20:53:16 +03:00
} else {
ctx . Data [ "SignedUserID" ] = int64 ( 0 )
ctx . Data [ "SignedUserName" ] = ""
// ensure the session uid is deleted
_ = ctx . Session . Delete ( "uid" )
}
}
}
2014-07-26 08:24:27 +04:00
// Contexter initializes a classic context for a request.
2021-01-26 18:36:53 +03:00
func Contexter ( ) func ( next http . Handler ) http . Handler {
2022-01-20 20:46:10 +03:00
rnd := templates . HTMLRenderer ( )
csrfOpts := getCsrfOpts ( )
2022-04-08 08:21:05 +03:00
if ! setting . IsProd {
CsrfTokenRegenerationInterval = 5 * time . Second // in dev, re-generate the tokens more aggressively for debug purpose
}
2021-01-26 18:36:53 +03:00
return func ( next http . Handler ) http . Handler {
return http . HandlerFunc ( func ( resp http . ResponseWriter , req * http . Request ) {
2022-01-20 20:46:10 +03:00
locale := middleware . Locale ( resp , req )
startTime := time . Now ( )
link := setting . AppSubURL + strings . TrimSuffix ( req . URL . EscapedPath ( ) , "/" )
2021-12-16 20:40:18 +03:00
2022-01-20 20:46:10 +03:00
ctx := Context {
2021-01-26 18:36:53 +03:00
Resp : NewResponse ( resp ) ,
2021-01-27 17:56:54 +03:00
Cache : mc . GetCache ( ) ,
2021-01-26 18:36:53 +03:00
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 ,
2021-01-29 07:33:47 +03:00
"Link" : link ,
2021-10-21 10:37:43 +03:00
"RunModeIsProd" : setting . IsProd ,
2021-01-26 18:36:53 +03:00
} ,
}
2022-05-05 17:13:23 +03:00
defer ctx . Close ( )
2021-10-15 05:35:26 +03:00
// PageData is passed by reference, and it will be rendered to `window.config.pageData` in `head.tmpl` for JavaScript modules
2021-10-12 21:11:35 +03:00
ctx . PageData = map [ string ] interface { } { }
ctx . Data [ "PageData" ] = ctx . PageData
2022-01-20 02:26:57 +03:00
ctx . Data [ "Context" ] = & ctx
2021-01-26 18:36:53 +03:00
ctx . Req = WithContext ( req , & ctx )
2022-04-08 08:21:05 +03:00
ctx . csrf = PrepareCSRFProtector ( csrfOpts , & ctx )
2021-01-26 18:36:53 +03:00
// Get flash.
flashCookie := ctx . GetCookie ( "macaron_flash" )
vals , _ := url . ParseQuery ( flashCookie )
if len ( vals ) > 0 {
2021-01-30 11:55:53 +03:00
f := & middleware . Flash {
2021-01-26 18:36:53 +03:00
DataStore : & ctx ,
Values : vals ,
ErrorMsg : vals . Get ( "error" ) ,
SuccessMsg : vals . Get ( "success" ) ,
InfoMsg : vals . Get ( "info" ) ,
WarningMsg : vals . Get ( "warning" ) ,
}
ctx . Data [ "Flash" ] = f
}
2021-01-30 11:55:53 +03:00
f := & middleware . Flash {
2021-01-26 18:36:53 +03:00
DataStore : & ctx ,
Values : url . Values { } ,
ErrorMsg : "" ,
WarningMsg : "" ,
InfoMsg : "" ,
SuccessMsg : "" ,
}
ctx . Resp . Before ( func ( resp ResponseWriter ) {
if flash := f . Encode ( ) ; len ( flash ) > 0 {
2021-01-30 11:55:53 +03:00
middleware . SetCookie ( resp , "macaron_flash" , flash , 0 ,
2021-01-27 17:56:54 +03:00
setting . SessionConfig . CookiePath ,
2021-01-30 11:55:53 +03:00
middleware . Domain ( setting . SessionConfig . Domain ) ,
middleware . HTTPOnly ( true ) ,
middleware . Secure ( setting . SessionConfig . Secure ) ,
2021-03-07 11:12:43 +03:00
middleware . SameSite ( setting . SessionConfig . SameSite ) ,
2021-01-27 17:56:54 +03:00
)
return
2021-01-26 18:36:53 +03:00
}
2021-03-07 11:12:43 +03:00
middleware . SetCookie ( ctx . Resp , "macaron_flash" , "" , - 1 ,
2021-01-26 18:36:53 +03:00
setting . SessionConfig . CookiePath ,
2021-01-30 11:55:53 +03:00
middleware . Domain ( setting . SessionConfig . Domain ) ,
middleware . HTTPOnly ( true ) ,
middleware . Secure ( setting . SessionConfig . Secure ) ,
2021-03-07 11:12:43 +03:00
middleware . SameSite ( setting . SessionConfig . SameSite ) ,
2021-01-26 18:36:53 +03:00
)
} )
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
}
}
2018-08-27 05:23:27 +03:00
2021-08-06 23:47:10 +03:00
ctx . Resp . Header ( ) . Set ( ` X-Frame-Options ` , setting . CORSConfig . XFrameOptions )
2021-01-26 18:36:53 +03:00
2022-04-08 08:21:05 +03:00
ctx . Data [ "CsrfToken" ] = ctx . csrf . GetToken ( )
2021-01-26 18:36:53 +03:00
ctx . Data [ "CsrfTokenHtml" ] = template . HTML ( ` <input type="hidden" name="_csrf" value=" ` + ctx . Data [ "CsrfToken" ] . ( string ) + ` "> ` )
2021-05-05 00:48:31 +03:00
// FIXME: do we really always need these setting? There should be someway to have to avoid having to always set these
2021-01-26 18:36:53 +03:00
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
2021-04-15 19:53:57 +03:00
ctx . Data [ "DisableStars" ] = setting . Repository . DisableStars
2021-01-26 18:36:53 +03:00
ctx . Data [ "ManifestData" ] = setting . ManifestData
2021-11-09 22:57:58 +03:00
ctx . Data [ "UnitWikiGlobalDisabled" ] = unit . TypeWiki . UnitGlobalDisabled ( )
ctx . Data [ "UnitIssuesGlobalDisabled" ] = unit . TypeIssues . UnitGlobalDisabled ( )
ctx . Data [ "UnitPullsGlobalDisabled" ] = unit . TypePullRequests . UnitGlobalDisabled ( )
ctx . Data [ "UnitProjectsGlobalDisabled" ] = unit . TypeProjects . UnitGlobalDisabled ( )
2021-05-05 00:48:31 +03:00
2022-06-27 23:58:46 +03:00
ctx . Data [ "locale" ] = locale
2021-01-26 18:36:53 +03:00
ctx . Data [ "AllLangs" ] = translation . AllLangs ( )
2020-12-22 14:13:50 +03:00
2021-01-26 18:36:53 +03:00
next . ServeHTTP ( ctx . Resp , ctx . Req )
2021-08-04 20:26:30 +03:00
// 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
}
}
2021-01-26 18:36:53 +03:00
} )
2014-03-15 15:01:50 +04:00
}
}
2022-04-07 21:59:56 +03:00
// 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 ,
} ,
}