code.gitea.io/gitea@v1.22.3/services/context/context_response.go (about)

     1  // Copyright 2023 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package context
     5  
     6  import (
     7  	"errors"
     8  	"fmt"
     9  	"html/template"
    10  	"net"
    11  	"net/http"
    12  	"net/url"
    13  	"path"
    14  	"strconv"
    15  	"strings"
    16  	"syscall"
    17  	"time"
    18  
    19  	user_model "code.gitea.io/gitea/models/user"
    20  	"code.gitea.io/gitea/modules/base"
    21  	"code.gitea.io/gitea/modules/httplib"
    22  	"code.gitea.io/gitea/modules/log"
    23  	"code.gitea.io/gitea/modules/setting"
    24  	"code.gitea.io/gitea/modules/templates"
    25  	"code.gitea.io/gitea/modules/web/middleware"
    26  )
    27  
    28  // RedirectToUser redirect to a differently-named user
    29  func RedirectToUser(ctx *Base, userName string, redirectUserID int64) {
    30  	user, err := user_model.GetUserByID(ctx, redirectUserID)
    31  	if err != nil {
    32  		ctx.Error(http.StatusInternalServerError, "unable to get user")
    33  		return
    34  	}
    35  
    36  	redirectPath := strings.Replace(
    37  		ctx.Req.URL.EscapedPath(),
    38  		url.PathEscape(userName),
    39  		url.PathEscape(user.Name),
    40  		1,
    41  	)
    42  	if ctx.Req.URL.RawQuery != "" {
    43  		redirectPath += "?" + ctx.Req.URL.RawQuery
    44  	}
    45  	ctx.Redirect(path.Join(setting.AppSubURL, redirectPath), http.StatusTemporaryRedirect)
    46  }
    47  
    48  // RedirectToCurrentSite redirects to first not empty URL which belongs to current site
    49  func (ctx *Context) RedirectToCurrentSite(location ...string) {
    50  	for _, loc := range location {
    51  		if len(loc) == 0 {
    52  			continue
    53  		}
    54  
    55  		if !httplib.IsCurrentGiteaSiteURL(ctx, loc) {
    56  			continue
    57  		}
    58  
    59  		ctx.Redirect(loc)
    60  		return
    61  	}
    62  
    63  	ctx.Redirect(setting.AppSubURL + "/")
    64  }
    65  
    66  const tplStatus500 base.TplName = "status/500"
    67  
    68  // HTML calls Context.HTML and renders the template to HTTP response
    69  func (ctx *Context) HTML(status int, name base.TplName) {
    70  	log.Debug("Template: %s", name)
    71  
    72  	tmplStartTime := time.Now()
    73  	if !setting.IsProd {
    74  		ctx.Data["TemplateName"] = name
    75  	}
    76  	ctx.Data["TemplateLoadTimes"] = func() string {
    77  		return strconv.FormatInt(time.Since(tmplStartTime).Nanoseconds()/1e6, 10) + "ms"
    78  	}
    79  
    80  	err := ctx.Render.HTML(ctx.Resp, status, string(name), ctx.Data, ctx.TemplateContext)
    81  	if err == nil || errors.Is(err, syscall.EPIPE) {
    82  		return
    83  	}
    84  
    85  	// if rendering fails, show error page
    86  	if name != tplStatus500 {
    87  		err = fmt.Errorf("failed to render template: %s, error: %s", name, templates.HandleTemplateRenderingError(err))
    88  		ctx.ServerError("Render failed", err) // show the 500 error page
    89  	} else {
    90  		ctx.PlainText(http.StatusInternalServerError, "Unable to render status/500 page, the template system is broken, or Gitea can't find your template files.")
    91  		return
    92  	}
    93  }
    94  
    95  // JSONTemplate renders the template as JSON response
    96  // keep in mind that the template is processed in HTML context, so JSON-things should be handled carefully, eg: by JSEscape
    97  func (ctx *Context) JSONTemplate(tmpl base.TplName) {
    98  	t, err := ctx.Render.TemplateLookup(string(tmpl), nil)
    99  	if err != nil {
   100  		ctx.ServerError("unable to find template", err)
   101  		return
   102  	}
   103  	ctx.Resp.Header().Set("Content-Type", "application/json")
   104  	if err = t.Execute(ctx.Resp, ctx.Data); err != nil {
   105  		ctx.ServerError("unable to execute template", err)
   106  	}
   107  }
   108  
   109  // RenderToHTML renders the template content to a HTML string
   110  func (ctx *Context) RenderToHTML(name base.TplName, data map[string]any) (template.HTML, error) {
   111  	var buf strings.Builder
   112  	err := ctx.Render.HTML(&buf, 0, string(name), data, ctx.TemplateContext)
   113  	return template.HTML(buf.String()), err
   114  }
   115  
   116  // RenderWithErr used for page has form validation but need to prompt error to users.
   117  func (ctx *Context) RenderWithErr(msg any, tpl base.TplName, form any) {
   118  	if form != nil {
   119  		middleware.AssignForm(form, ctx.Data)
   120  	}
   121  	ctx.Flash.Error(msg, true)
   122  	ctx.HTML(http.StatusOK, tpl)
   123  }
   124  
   125  // NotFound displays a 404 (Not Found) page and prints the given error, if any.
   126  func (ctx *Context) NotFound(logMsg string, logErr error) {
   127  	ctx.notFoundInternal(logMsg, logErr)
   128  }
   129  
   130  func (ctx *Context) notFoundInternal(logMsg string, logErr error) {
   131  	if logErr != nil {
   132  		log.Log(2, log.DEBUG, "%s: %v", logMsg, logErr)
   133  		if !setting.IsProd {
   134  			ctx.Data["ErrorMsg"] = logErr
   135  		}
   136  	}
   137  
   138  	// response simple message if Accept isn't text/html
   139  	showHTML := false
   140  	for _, part := range ctx.Req.Header["Accept"] {
   141  		if strings.Contains(part, "text/html") {
   142  			showHTML = true
   143  			break
   144  		}
   145  	}
   146  
   147  	if !showHTML {
   148  		ctx.plainTextInternal(3, http.StatusNotFound, []byte("Not found.\n"))
   149  		return
   150  	}
   151  
   152  	ctx.Data["IsRepo"] = ctx.Repo.Repository != nil
   153  	ctx.Data["Title"] = "Page Not Found"
   154  	ctx.HTML(http.StatusNotFound, base.TplName("status/404"))
   155  }
   156  
   157  // ServerError displays a 500 (Internal Server Error) page and prints the given error, if any.
   158  func (ctx *Context) ServerError(logMsg string, logErr error) {
   159  	ctx.serverErrorInternal(logMsg, logErr)
   160  }
   161  
   162  func (ctx *Context) serverErrorInternal(logMsg string, logErr error) {
   163  	if logErr != nil {
   164  		log.ErrorWithSkip(2, "%s: %v", logMsg, logErr)
   165  		if _, ok := logErr.(*net.OpError); ok || errors.Is(logErr, &net.OpError{}) {
   166  			// This is an error within the underlying connection
   167  			// and further rendering will not work so just return
   168  			return
   169  		}
   170  
   171  		// it's safe to show internal error to admin users, and it helps
   172  		if !setting.IsProd || (ctx.Doer != nil && ctx.Doer.IsAdmin) {
   173  			ctx.Data["ErrorMsg"] = fmt.Sprintf("%s, %s", logMsg, logErr)
   174  		}
   175  	}
   176  
   177  	ctx.Data["Title"] = "Internal Server Error"
   178  	ctx.HTML(http.StatusInternalServerError, tplStatus500)
   179  }
   180  
   181  // NotFoundOrServerError use error check function to determine if the error
   182  // is about not found. It responds with 404 status code for not found error,
   183  // or error context description for logging purpose of 500 server error.
   184  // TODO: remove the "errCheck" and use util.ErrNotFound to check
   185  func (ctx *Context) NotFoundOrServerError(logMsg string, errCheck func(error) bool, logErr error) {
   186  	if errCheck(logErr) {
   187  		ctx.notFoundInternal(logMsg, logErr)
   188  		return
   189  	}
   190  	ctx.serverErrorInternal(logMsg, logErr)
   191  }