You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

context_response.go 5.0KB

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