4
0

html_test.go 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  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. "errors"
  6. "net/http"
  7. "net/http/httptest"
  8. "testing"
  9. )
  10. func TestHTMLResponse(t *testing.T) {
  11. r, err := http.NewRequest("GET", "/", nil)
  12. if err != nil {
  13. t.Fatal(err)
  14. }
  15. w := httptest.NewRecorder()
  16. handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  17. HTML(w, r, "Some HTML")
  18. })
  19. handler.ServeHTTP(w, r)
  20. resp := w.Result()
  21. if resp.StatusCode != http.StatusOK {
  22. t.Fatalf(`Unexpected status code, got %d instead of %d`, resp.StatusCode, http.StatusOK)
  23. }
  24. if actualBody := w.Body.String(); actualBody != `Some HTML` {
  25. t.Fatalf(`Unexpected body, got %s instead of %s`, actualBody, `Some HTML`)
  26. }
  27. headers := map[string]string{
  28. "Content-Type": "text/html; charset=utf-8",
  29. "Cache-Control": "no-cache, max-age=0, must-revalidate, no-store",
  30. }
  31. for header, expected := range headers {
  32. if actual := resp.Header.Get(header); actual != expected {
  33. t.Fatalf(`Unexpected header value, got %q instead of %q`, actual, expected)
  34. }
  35. }
  36. }
  37. func TestHTMLServerErrorResponse(t *testing.T) {
  38. r, err := http.NewRequest("GET", "/", nil)
  39. if err != nil {
  40. t.Fatal(err)
  41. }
  42. w := httptest.NewRecorder()
  43. handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  44. HTMLServerError(w, r, errors.New("Some error with injected HTML <script>alert('XSS')</script>"))
  45. })
  46. handler.ServeHTTP(w, r)
  47. resp := w.Result()
  48. if resp.StatusCode != http.StatusInternalServerError {
  49. t.Fatalf(`Unexpected status code, got %d instead of %d`, resp.StatusCode, http.StatusInternalServerError)
  50. }
  51. if actualBody := w.Body.String(); actualBody != `Some error with injected HTML &lt;script&gt;alert(&#39;XSS&#39;)&lt;/script&gt;` {
  52. t.Fatalf(`Unexpected body, got %s instead of %s`, actualBody, `Some error with injected HTML &lt;script&gt;alert(&#39;XSS&#39;)&lt;/script&gt;`)
  53. }
  54. if actualContentType := resp.Header.Get("Content-Type"); actualContentType != "text/plain; charset=utf-8" {
  55. t.Fatalf(`Unexpected content type, got %q instead of %q`, actualContentType, "text/plain; charset=utf-8")
  56. }
  57. }
  58. func TestHTMLBadRequestResponse(t *testing.T) {
  59. r, err := http.NewRequest("GET", "/", nil)
  60. if err != nil {
  61. t.Fatal(err)
  62. }
  63. w := httptest.NewRecorder()
  64. handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  65. HTMLBadRequest(w, r, errors.New("Some error with injected HTML <script>alert('XSS')</script>"))
  66. })
  67. handler.ServeHTTP(w, r)
  68. resp := w.Result()
  69. if resp.StatusCode != http.StatusBadRequest {
  70. t.Fatalf(`Unexpected status code, got %d instead of %d`, resp.StatusCode, http.StatusBadRequest)
  71. }
  72. if actualBody := w.Body.String(); actualBody != `Some error with injected HTML &lt;script&gt;alert(&#39;XSS&#39;)&lt;/script&gt;` {
  73. t.Fatalf(`Unexpected body, got %s instead of %s`, actualBody, `Some error with injected HTML &lt;script&gt;alert(&#39;XSS&#39;)&lt;/script&gt;`)
  74. }
  75. if actualContentType := resp.Header.Get("Content-Type"); actualContentType != "text/plain; charset=utf-8" {
  76. t.Fatalf(`Unexpected content type, got %q instead of %q`, actualContentType, "text/plain; charset=utf-8")
  77. }
  78. }
  79. func TestHTMLForbiddenResponse(t *testing.T) {
  80. r, err := http.NewRequest("GET", "/", nil)
  81. if err != nil {
  82. t.Fatal(err)
  83. }
  84. w := httptest.NewRecorder()
  85. handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  86. HTMLForbidden(w, r)
  87. })
  88. handler.ServeHTTP(w, r)
  89. resp := w.Result()
  90. if resp.StatusCode != http.StatusForbidden {
  91. t.Fatalf(`Unexpected status code, got %d instead of %d`, resp.StatusCode, http.StatusForbidden)
  92. }
  93. if actualBody := w.Body.String(); actualBody != `Access Forbidden` {
  94. t.Fatalf(`Unexpected body, got %s instead of %s`, actualBody, `Access Forbidden`)
  95. }
  96. if actualContentType := resp.Header.Get("Content-Type"); actualContentType != "text/html; charset=utf-8" {
  97. t.Fatalf(`Unexpected content type, got %q instead of %q`, actualContentType, "text/html; charset=utf-8")
  98. }
  99. }
  100. func TestHTMLNotFoundResponse(t *testing.T) {
  101. r, err := http.NewRequest("GET", "/", nil)
  102. if err != nil {
  103. t.Fatal(err)
  104. }
  105. w := httptest.NewRecorder()
  106. handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  107. HTMLNotFound(w, r)
  108. })
  109. handler.ServeHTTP(w, r)
  110. resp := w.Result()
  111. if resp.StatusCode != http.StatusNotFound {
  112. t.Fatalf(`Unexpected status code, got %d instead of %d`, resp.StatusCode, http.StatusNotFound)
  113. }
  114. if actualBody := w.Body.String(); actualBody != `Page Not Found` {
  115. t.Fatalf(`Unexpected body, got %s instead of %s`, actualBody, `Page Not Found`)
  116. }
  117. if actualContentType := resp.Header.Get("Content-Type"); actualContentType != "text/html; charset=utf-8" {
  118. t.Fatalf(`Unexpected content type, got %q instead of %q`, actualContentType, "text/html; charset=utf-8")
  119. }
  120. }
  121. func TestHTMLRedirectResponse(t *testing.T) {
  122. r, err := http.NewRequest("GET", "/", nil)
  123. if err != nil {
  124. t.Fatal(err)
  125. }
  126. w := httptest.NewRecorder()
  127. handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  128. HTMLRedirect(w, r, "/path")
  129. })
  130. handler.ServeHTTP(w, r)
  131. resp := w.Result()
  132. defer resp.Body.Close()
  133. if resp.StatusCode != http.StatusFound {
  134. t.Fatalf(`Unexpected status code, got %d instead of %d`, resp.StatusCode, http.StatusFound)
  135. }
  136. if actualResult := resp.Header.Get("Location"); actualResult != "/path" {
  137. t.Fatalf(`Unexpected redirect location, got %q instead of %q`, actualResult, "/path")
  138. }
  139. }
  140. func TestHTMLRedirectAcceptedTargets(t *testing.T) {
  141. scenarios := []string{
  142. "/feeds",
  143. "/category/1/entries",
  144. "https://example.org/article",
  145. "http://example.org/article",
  146. }
  147. for _, target := range scenarios {
  148. t.Run(target, func(t *testing.T) {
  149. r, err := http.NewRequest("GET", "/", nil)
  150. if err != nil {
  151. t.Fatal(err)
  152. }
  153. w := httptest.NewRecorder()
  154. HTMLRedirect(w, r, target)
  155. resp := w.Result()
  156. defer resp.Body.Close()
  157. if resp.StatusCode != http.StatusFound {
  158. t.Fatalf(`Unexpected status code for %q, got %d instead of %d`, target, resp.StatusCode, http.StatusFound)
  159. }
  160. if actualResult := resp.Header.Get("Location"); actualResult != target {
  161. t.Fatalf(`Unexpected redirect location, got %q instead of %q`, actualResult, target)
  162. }
  163. })
  164. }
  165. }
  166. func TestHTMLRedirectRejectsUnsafeTargets(t *testing.T) {
  167. scenarios := []string{
  168. "javascript:alert(1)",
  169. "JAVASCRIPT:alert(1)",
  170. "data:text/html,<script>alert(1)</script>",
  171. "vbscript:msgbox(1)",
  172. "file:///etc/passwd",
  173. "mailto:victim@example.org",
  174. "//evil.example.org/path",
  175. "ftp://example.org/file",
  176. "",
  177. }
  178. for _, target := range scenarios {
  179. t.Run(target, func(t *testing.T) {
  180. r, err := http.NewRequest("GET", "/", nil)
  181. if err != nil {
  182. t.Fatal(err)
  183. }
  184. w := httptest.NewRecorder()
  185. HTMLRedirect(w, r, target)
  186. resp := w.Result()
  187. defer resp.Body.Close()
  188. if resp.StatusCode != http.StatusBadRequest {
  189. t.Fatalf(`Expected 400 for %q, got %d`, target, resp.StatusCode)
  190. }
  191. if location := resp.Header.Get("Location"); location != "" {
  192. t.Fatalf(`Expected no Location header for %q, got %q`, target, location)
  193. }
  194. })
  195. }
  196. }
  197. func TestHTMLRequestedRangeNotSatisfiable(t *testing.T) {
  198. r, err := http.NewRequest("GET", "/", nil)
  199. if err != nil {
  200. t.Fatal(err)
  201. }
  202. w := httptest.NewRecorder()
  203. handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  204. HTMLRequestedRangeNotSatisfiable(w, r, "bytes */12777")
  205. })
  206. handler.ServeHTTP(w, r)
  207. resp := w.Result()
  208. defer resp.Body.Close()
  209. if resp.StatusCode != http.StatusRequestedRangeNotSatisfiable {
  210. t.Fatalf(`Unexpected status code, got %d instead of %d`, resp.StatusCode, http.StatusRequestedRangeNotSatisfiable)
  211. }
  212. if actualContentRangeHeader := resp.Header.Get("Content-Range"); actualContentRangeHeader != "bytes */12777" {
  213. t.Fatalf(`Unexpected content range header, got %q instead of %q`, actualContentRangeHeader, "bytes */12777")
  214. }
  215. }