123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191 |
- // Copyright 2023 The Gitea Authors. All rights reserved.
- // SPDX-License-Identifier: MIT
-
- package context
-
- import (
- "errors"
- "fmt"
- "html/template"
- "net"
- "net/http"
- "net/url"
- "path"
- "strconv"
- "strings"
- "syscall"
- "time"
-
- user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/httplib"
- "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
- func RedirectToUser(ctx *Base, userName string, redirectUserID int64) {
- user, err := user_model.GetUserByID(ctx, redirectUserID)
- if err != nil {
- ctx.Error(http.StatusInternalServerError, "unable to get user")
- 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)
- }
-
- // RedirectToCurrentSite redirects to first not empty URL which belongs to current site
- func (ctx *Context) RedirectToCurrentSite(location ...string) {
- for _, loc := range location {
- if len(loc) == 0 {
- continue
- }
-
- if !httplib.IsCurrentGiteaSiteURL(loc) {
- continue
- }
-
- ctx.Redirect(loc)
- return
- }
-
- ctx.Redirect(setting.AppSubURL + "/")
- }
-
- 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"
- }
-
- err := ctx.Render.HTML(ctx.Resp, status, string(name), ctx.Data, ctx.TemplateContext)
- if err == nil || errors.Is(err, syscall.EPIPE) {
- 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
- }
- }
-
- // 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)
- }
- }
-
- // RenderToHTML renders the template content to a HTML string
- func (ctx *Context) RenderToHTML(name base.TplName, data map[string]any) (template.HTML, error) {
- var buf strings.Builder
- err := ctx.Render.HTML(&buf, 0, string(name), data, ctx.TemplateContext)
- return template.HTML(buf.String()), err
- }
-
- // RenderWithErr used for page has form validation but need to prompt error to users.
- func (ctx *Context) RenderWithErr(msg any, tpl base.TplName, form any) {
- if form != nil {
- middleware.AssignForm(form, ctx.Data)
- }
- ctx.Flash.Error(msg, true)
- 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.
- // TODO: remove the "errCheck" and use util.ErrNotFound to check
- func (ctx *Context) NotFoundOrServerError(logMsg string, errCheck func(error) bool, logErr error) {
- if errCheck(logErr) {
- ctx.notFoundInternal(logMsg, logErr)
- return
- }
- ctx.serverErrorInternal(logMsg, logErr)
- }
|