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

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