2023-05-08 11:36:54 +02:00
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package context
import (
"errors"
"fmt"
2024-03-02 16:05:07 +01:00
"html/template"
2023-05-08 11:36:54 +02:00
"net"
"net/http"
"net/url"
"path"
"strconv"
"strings"
"time"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
2023-06-16 08:32:43 +02:00
"code.gitea.io/gitea/modules/httplib"
2023-05-08 11:36:54 +02:00
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/web/middleware"
)
// RedirectToUser redirect to a differently-named user
2023-05-21 03:50:53 +02:00
func RedirectToUser ( ctx * Base , userName string , redirectUserID int64 ) {
2023-05-08 11:36:54 +02:00
user , err := user_model . GetUserByID ( ctx , redirectUserID )
if err != nil {
2023-05-21 03:50:53 +02:00
ctx . Error ( http . StatusInternalServerError , "unable to get user" )
2023-05-08 11:36:54 +02:00
return
}
redirectPath := strings . Replace (
ctx . Req . URL . EscapedPath ( ) ,
url . PathEscape ( userName ) ,
url . PathEscape ( user . Name ) ,
1 ,
)
if ctx . Req . URL . RawQuery != "" {
redirectPath += "?" + ctx . Req . URL . RawQuery
}
ctx . Redirect ( path . Join ( setting . AppSubURL , redirectPath ) , http . StatusTemporaryRedirect )
}
2024-04-15 15:03:08 +02:00
// RedirectToFirst redirects to first not empty URL which likely belongs to current site.
// If no suitable redirection is found, it redirects to the home.
// It returns the location it redirected to.
func ( ctx * Context ) RedirectToFirst ( location ... string ) string {
2023-05-08 11:36:54 +02:00
for _ , loc := range location {
if len ( loc ) == 0 {
continue
}
2023-06-16 08:32:43 +02:00
if httplib . IsRiskyRedirectURL ( loc ) {
2023-05-08 11:36:54 +02:00
continue
}
ctx . Redirect ( loc )
2024-04-15 15:03:08 +02:00
return loc
2023-05-08 11:36:54 +02:00
}
ctx . Redirect ( setting . AppSubURL + "/" )
2024-04-15 15:03:08 +02:00
return setting . AppSubURL + "/"
2023-05-08 11:36:54 +02:00
}
const tplStatus500 base . TplName = "status/500"
// HTML calls Context.HTML and renders the template to HTTP response
func ( ctx * Context ) HTML ( status int , name base . TplName ) {
log . Debug ( "Template: %s" , name )
tmplStartTime := time . Now ( )
if ! setting . IsProd {
ctx . Data [ "TemplateName" ] = name
}
ctx . Data [ "TemplateLoadTimes" ] = func ( ) string {
return strconv . FormatInt ( time . Since ( tmplStartTime ) . Nanoseconds ( ) / 1e6 , 10 ) + "ms"
}
2023-08-08 03:22:47 +02:00
err := ctx . Render . HTML ( ctx . Resp , status , string ( name ) , ctx . Data , ctx . TemplateContext )
2023-05-08 11:36:54 +02:00
if err == nil {
return
}
// if rendering fails, show error page
if name != tplStatus500 {
err = fmt . Errorf ( "failed to render template: %s, error: %s" , name , templates . HandleTemplateRenderingError ( err ) )
ctx . ServerError ( "Render failed" , err ) // show the 500 error page
} else {
ctx . PlainText ( http . StatusInternalServerError , "Unable to render status/500 page, the template system is broken, or Gitea can't find your template files." )
return
}
}
2024-02-18 10:52:02 +01:00
// JSONTemplate renders the template as JSON response
// keep in mind that the template is processed in HTML context, so JSON-things should be handled carefully, eg: by JSEscape
func ( ctx * Context ) JSONTemplate ( tmpl base . TplName ) {
t , err := ctx . Render . TemplateLookup ( string ( tmpl ) , nil )
if err != nil {
ctx . ServerError ( "unable to find template" , err )
return
}
ctx . Resp . Header ( ) . Set ( "Content-Type" , "application/json" )
if err = t . Execute ( ctx . Resp , ctx . Data ) ; err != nil {
ctx . ServerError ( "unable to execute template" , err )
}
}
2024-03-02 16:05:07 +01:00
// RenderToHTML renders the template content to a HTML string
func ( ctx * Context ) RenderToHTML ( name base . TplName , data map [ string ] any ) ( template . HTML , error ) {
2023-05-08 11:36:54 +02:00
var buf strings . Builder
2024-03-02 16:05:07 +01:00
err := ctx . Render . HTML ( & buf , 0 , string ( name ) , data , ctx . TemplateContext )
return template . HTML ( buf . String ( ) ) , err
2023-05-08 11:36:54 +02:00
}
// RenderWithErr used for page has form validation but need to prompt error to users.
2024-02-14 22:48:45 +01:00
func ( ctx * Context ) RenderWithErr ( msg any , tpl base . TplName , form any ) {
2023-05-08 11:36:54 +02:00
if form != nil {
middleware . AssignForm ( form , ctx . Data )
}
2024-02-14 22:48:45 +01:00
ctx . Flash . Error ( msg , true )
2023-05-08 11:36:54 +02:00
ctx . HTML ( http . StatusOK , tpl )
}
// NotFound displays a 404 (Not Found) page and prints the given error, if any.
func ( ctx * Context ) NotFound ( logMsg string , logErr error ) {
ctx . notFoundInternal ( logMsg , logErr )
}
func ( ctx * Context ) notFoundInternal ( logMsg string , logErr error ) {
if logErr != nil {
log . Log ( 2 , log . DEBUG , "%s: %v" , logMsg , logErr )
if ! setting . IsProd {
ctx . Data [ "ErrorMsg" ] = logErr
}
}
// response simple message if Accept isn't text/html
showHTML := false
for _ , part := range ctx . Req . Header [ "Accept" ] {
if strings . Contains ( part , "text/html" ) {
showHTML = true
break
}
}
if ! showHTML {
ctx . plainTextInternal ( 3 , http . StatusNotFound , [ ] byte ( "Not found.\n" ) )
return
}
ctx . Data [ "IsRepo" ] = ctx . Repo . Repository != nil
ctx . Data [ "Title" ] = "Page Not Found"
ctx . HTML ( http . StatusNotFound , base . TplName ( "status/404" ) )
}
// ServerError displays a 500 (Internal Server Error) page and prints the given error, if any.
func ( ctx * Context ) ServerError ( logMsg string , logErr error ) {
ctx . serverErrorInternal ( logMsg , logErr )
}
func ( ctx * Context ) serverErrorInternal ( logMsg string , logErr error ) {
if logErr != nil {
log . ErrorWithSkip ( 2 , "%s: %v" , logMsg , logErr )
if _ , ok := logErr . ( * net . OpError ) ; ok || errors . Is ( logErr , & net . OpError { } ) {
// This is an error within the underlying connection
// and further rendering will not work so just return
return
}
// it's safe to show internal error to admin users, and it helps
if ! setting . IsProd || ( ctx . Doer != nil && ctx . Doer . IsAdmin ) {
ctx . Data [ "ErrorMsg" ] = fmt . Sprintf ( "%s, %s" , logMsg , logErr )
}
}
ctx . Data [ "Title" ] = "Internal Server Error"
ctx . HTML ( http . StatusInternalServerError , tplStatus500 )
}
// NotFoundOrServerError use error check function to determine if the error
// is about not found. It responds with 404 status code for not found error,
// or error context description for logging purpose of 500 server error.
2023-07-27 12:47:41 +02:00
// TODO: remove the "errCheck" and use util.ErrNotFound to check
2023-05-08 11:36:54 +02:00
func ( ctx * Context ) NotFoundOrServerError ( logMsg string , errCheck func ( error ) bool , logErr error ) {
if errCheck ( logErr ) {
ctx . notFoundInternal ( logMsg , logErr )
return
}
ctx . serverErrorInternal ( logMsg , logErr )
}