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.6KB

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