html.go 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
  2. // SPDX-License-Identifier: Apache-2.0
  3. package response // import "miniflux.app/v2/internal/http/response"
  4. import (
  5. "fmt"
  6. "html"
  7. "log/slog"
  8. "net/http"
  9. "miniflux.app/v2/internal/http/request"
  10. "miniflux.app/v2/internal/urllib"
  11. )
  12. // HTML creates a new HTML response with a 200 status code.
  13. func HTML[T []byte | string](w http.ResponseWriter, r *http.Request, body T) {
  14. builder := NewBuilder(w, r)
  15. builder.WithHeader("Content-Type", "text/html; charset=utf-8")
  16. builder.WithHeader("Cache-Control", "no-cache, max-age=0, must-revalidate, no-store")
  17. switch v := any(body).(type) {
  18. case []byte:
  19. builder.WithBodyAsBytes(v)
  20. case string:
  21. builder.WithBodyAsString(v)
  22. }
  23. builder.Write()
  24. }
  25. // HTMLServerError sends an internal error to the client.
  26. func HTMLServerError(w http.ResponseWriter, r *http.Request, err error) {
  27. slog.Error(http.StatusText(http.StatusInternalServerError),
  28. slog.Any("error", err),
  29. slog.String("client_ip", request.ClientIP(r)),
  30. slog.Group("request",
  31. slog.String("method", r.Method),
  32. slog.String("uri", r.RequestURI),
  33. slog.String("user_agent", r.UserAgent()),
  34. ),
  35. slog.Group("response",
  36. slog.Int("status_code", http.StatusInternalServerError),
  37. ),
  38. )
  39. builder := NewBuilder(w, r)
  40. builder.WithStatus(http.StatusInternalServerError)
  41. builder.WithHeader("Content-Security-Policy", ContentSecurityPolicyForUntrustedContent)
  42. builder.WithHeader("Content-Type", "text/plain; charset=utf-8")
  43. builder.WithHeader("Cache-Control", "no-cache, max-age=0, must-revalidate, no-store")
  44. builder.WithBodyAsString(html.EscapeString(err.Error()))
  45. builder.Write()
  46. }
  47. // HTMLBadRequest sends a bad request error to the client.
  48. func HTMLBadRequest(w http.ResponseWriter, r *http.Request, err error) {
  49. slog.Warn(http.StatusText(http.StatusBadRequest),
  50. slog.Any("error", err),
  51. slog.String("client_ip", request.ClientIP(r)),
  52. slog.Group("request",
  53. slog.String("method", r.Method),
  54. slog.String("uri", r.RequestURI),
  55. slog.String("user_agent", r.UserAgent()),
  56. ),
  57. slog.Group("response",
  58. slog.Int("status_code", http.StatusBadRequest),
  59. ),
  60. )
  61. builder := NewBuilder(w, r)
  62. builder.WithStatus(http.StatusBadRequest)
  63. builder.WithHeader("Content-Security-Policy", ContentSecurityPolicyForUntrustedContent)
  64. builder.WithHeader("Content-Type", "text/plain; charset=utf-8")
  65. builder.WithHeader("Cache-Control", "no-cache, max-age=0, must-revalidate, no-store")
  66. builder.WithBodyAsString(html.EscapeString(err.Error()))
  67. builder.Write()
  68. }
  69. // HTMLForbidden sends a forbidden error to the client.
  70. func HTMLForbidden(w http.ResponseWriter, r *http.Request) {
  71. slog.Warn(http.StatusText(http.StatusForbidden),
  72. slog.String("client_ip", request.ClientIP(r)),
  73. slog.Group("request",
  74. slog.String("method", r.Method),
  75. slog.String("uri", r.RequestURI),
  76. slog.String("user_agent", r.UserAgent()),
  77. ),
  78. slog.Group("response",
  79. slog.Int("status_code", http.StatusForbidden),
  80. ),
  81. )
  82. builder := NewBuilder(w, r)
  83. builder.WithStatus(http.StatusForbidden)
  84. builder.WithHeader("Content-Type", "text/html; charset=utf-8")
  85. builder.WithHeader("Cache-Control", "no-cache, max-age=0, must-revalidate, no-store")
  86. builder.WithBodyAsString("Access Forbidden")
  87. builder.Write()
  88. }
  89. // HTMLNotFound sends a page not found error to the client.
  90. func HTMLNotFound(w http.ResponseWriter, r *http.Request) {
  91. slog.Warn(http.StatusText(http.StatusNotFound),
  92. slog.String("client_ip", request.ClientIP(r)),
  93. slog.Group("request",
  94. slog.String("method", r.Method),
  95. slog.String("uri", r.RequestURI),
  96. slog.String("user_agent", r.UserAgent()),
  97. ),
  98. slog.Group("response",
  99. slog.Int("status_code", http.StatusNotFound),
  100. ),
  101. )
  102. builder := NewBuilder(w, r)
  103. builder.WithStatus(http.StatusNotFound)
  104. builder.WithHeader("Content-Type", "text/html; charset=utf-8")
  105. builder.WithHeader("Cache-Control", "no-cache, max-age=0, must-revalidate, no-store")
  106. builder.WithBodyAsString("Page Not Found")
  107. builder.Write()
  108. }
  109. // HTMLRedirect redirects the user to a relative path or an absolute http(s) URL.
  110. func HTMLRedirect(w http.ResponseWriter, r *http.Request, uri string) {
  111. if !urllib.IsRelativePath(uri) && !urllib.IsAbsoluteURL(uri) {
  112. HTMLBadRequest(w, r, fmt.Errorf("invalid redirect URL: %q", uri))
  113. return
  114. }
  115. http.Redirect(w, r, uri, http.StatusFound)
  116. }
  117. // HTMLRequestedRangeNotSatisfiable sends a range not satisfiable error to the client.
  118. func HTMLRequestedRangeNotSatisfiable(w http.ResponseWriter, r *http.Request, contentRange string) {
  119. slog.Warn(http.StatusText(http.StatusRequestedRangeNotSatisfiable),
  120. slog.String("client_ip", request.ClientIP(r)),
  121. slog.Group("request",
  122. slog.String("method", r.Method),
  123. slog.String("uri", r.RequestURI),
  124. slog.String("user_agent", r.UserAgent()),
  125. ),
  126. slog.Group("response",
  127. slog.Int("status_code", http.StatusRequestedRangeNotSatisfiable),
  128. ),
  129. )
  130. builder := NewBuilder(w, r)
  131. builder.WithStatus(http.StatusRequestedRangeNotSatisfiable)
  132. builder.WithHeader("Content-Type", "text/html; charset=utf-8")
  133. builder.WithHeader("Cache-Control", "no-cache, max-age=0, must-revalidate, no-store")
  134. builder.WithHeader("Content-Range", contentRange)
  135. builder.WithBodyAsString("Range Not Satisfiable")
  136. builder.Write()
  137. }