Fork of Pleroma with site-specific changes and feature branches https://git.pleroma.social/pleroma/pleroma
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.

301 lines
9.3KB

  1. # Pleroma: A lightweight social networking server
  2. # Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
  3. # SPDX-License-Identifier: AGPL-3.0-only
  4. defmodule Pleroma.ReverseProxyTest do
  5. use Pleroma.Web.ConnCase, async: true
  6. import ExUnit.CaptureLog
  7. import Mox
  8. alias Pleroma.ReverseProxy
  9. alias Pleroma.ReverseProxy.ClientMock
  10. setup_all do
  11. {:ok, _} = Registry.start_link(keys: :unique, name: Pleroma.ReverseProxy.ClientMock)
  12. :ok
  13. end
  14. setup :verify_on_exit!
  15. defp user_agent_mock(user_agent, invokes) do
  16. json = Jason.encode!(%{"user-agent": user_agent})
  17. ClientMock
  18. |> expect(:request, fn :get, url, _, _, _ ->
  19. Registry.register(Pleroma.ReverseProxy.ClientMock, url, 0)
  20. {:ok, 200,
  21. [
  22. {"content-type", "application/json"},
  23. {"content-length", byte_size(json) |> to_string()}
  24. ], %{url: url}}
  25. end)
  26. |> expect(:stream_body, invokes, fn %{url: url} ->
  27. case Registry.lookup(Pleroma.ReverseProxy.ClientMock, url) do
  28. [{_, 0}] ->
  29. Registry.update_value(Pleroma.ReverseProxy.ClientMock, url, &(&1 + 1))
  30. {:ok, json}
  31. [{_, 1}] ->
  32. Registry.unregister(Pleroma.ReverseProxy.ClientMock, url)
  33. :done
  34. end
  35. end)
  36. end
  37. describe "user-agent" do
  38. test "don't keep", %{conn: conn} do
  39. user_agent_mock("hackney/1.15.1", 2)
  40. conn = ReverseProxy.call(conn, "/user-agent")
  41. assert json_response(conn, 200) == %{"user-agent" => "hackney/1.15.1"}
  42. end
  43. test "keep", %{conn: conn} do
  44. user_agent_mock(Pleroma.Application.user_agent(), 2)
  45. conn = ReverseProxy.call(conn, "/user-agent-keep", keep_user_agent: true)
  46. assert json_response(conn, 200) == %{"user-agent" => Pleroma.Application.user_agent()}
  47. end
  48. end
  49. test "closed connection", %{conn: conn} do
  50. ClientMock
  51. |> expect(:request, fn :get, "/closed", _, _, _ -> {:ok, 200, [], %{}} end)
  52. |> expect(:stream_body, fn _ -> {:error, :closed} end)
  53. |> expect(:close, fn _ -> :ok end)
  54. conn = ReverseProxy.call(conn, "/closed")
  55. assert conn.halted
  56. end
  57. describe "max_body " do
  58. test "length returns error if content-length more than option", %{conn: conn} do
  59. user_agent_mock("hackney/1.15.1", 0)
  60. assert capture_log(fn ->
  61. ReverseProxy.call(conn, "/user-agent", max_body_length: 4)
  62. end) =~
  63. "[error] Elixir.Pleroma.ReverseProxy: request to \"/user-agent\" failed: :body_too_large"
  64. end
  65. defp stream_mock(invokes, with_close? \\ false) do
  66. ClientMock
  67. |> expect(:request, fn :get, "/stream-bytes/" <> length, _, _, _ ->
  68. Registry.register(Pleroma.ReverseProxy.ClientMock, "/stream-bytes/" <> length, 0)
  69. {:ok, 200, [{"content-type", "application/octet-stream"}],
  70. %{url: "/stream-bytes/" <> length}}
  71. end)
  72. |> expect(:stream_body, invokes, fn %{url: "/stream-bytes/" <> length} ->
  73. max = String.to_integer(length)
  74. case Registry.lookup(Pleroma.ReverseProxy.ClientMock, "/stream-bytes/" <> length) do
  75. [{_, current}] when current < max ->
  76. Registry.update_value(
  77. Pleroma.ReverseProxy.ClientMock,
  78. "/stream-bytes/" <> length,
  79. &(&1 + 10)
  80. )
  81. {:ok, "0123456789"}
  82. [{_, ^max}] ->
  83. Registry.unregister(Pleroma.ReverseProxy.ClientMock, "/stream-bytes/" <> length)
  84. :done
  85. end
  86. end)
  87. if with_close? do
  88. expect(ClientMock, :close, fn _ -> :ok end)
  89. end
  90. end
  91. test "max_body_size returns error if streaming body more than that option", %{conn: conn} do
  92. stream_mock(3, true)
  93. assert capture_log(fn ->
  94. ReverseProxy.call(conn, "/stream-bytes/50", max_body_size: 30)
  95. end) =~
  96. "[warn] Elixir.Pleroma.ReverseProxy request to /stream-bytes/50 failed while reading/chunking: :body_too_large"
  97. end
  98. end
  99. describe "HEAD requests" do
  100. test "common", %{conn: conn} do
  101. ClientMock
  102. |> expect(:request, fn :head, "/head", _, _, _ ->
  103. {:ok, 200, [{"content-type", "text/html; charset=utf-8"}]}
  104. end)
  105. conn = ReverseProxy.call(Map.put(conn, :method, "HEAD"), "/head")
  106. assert html_response(conn, 200) == ""
  107. end
  108. end
  109. defp error_mock(status) when is_integer(status) do
  110. ClientMock
  111. |> expect(:request, fn :get, "/status/" <> _, _, _, _ ->
  112. {:error, status}
  113. end)
  114. end
  115. describe "returns error on" do
  116. test "500", %{conn: conn} do
  117. error_mock(500)
  118. capture_log(fn -> ReverseProxy.call(conn, "/status/500") end) =~
  119. "[error] Elixir.Pleroma.ReverseProxy: request to /status/500 failed with HTTP status 500"
  120. end
  121. test "400", %{conn: conn} do
  122. error_mock(400)
  123. capture_log(fn -> ReverseProxy.call(conn, "/status/400") end) =~
  124. "[error] Elixir.Pleroma.ReverseProxy: request to /status/400 failed with HTTP status 400"
  125. end
  126. test "204", %{conn: conn} do
  127. ClientMock
  128. |> expect(:request, fn :get, "/status/204", _, _, _ -> {:ok, 204, [], %{}} end)
  129. capture_log(fn ->
  130. conn = ReverseProxy.call(conn, "/status/204")
  131. assert conn.resp_body == "Request failed: No Content"
  132. assert conn.halted
  133. end) =~
  134. "[error] Elixir.Pleroma.ReverseProxy: request to \"/status/204\" failed with HTTP status 204"
  135. end
  136. end
  137. test "streaming", %{conn: conn} do
  138. stream_mock(21)
  139. conn = ReverseProxy.call(conn, "/stream-bytes/200")
  140. assert conn.state == :chunked
  141. assert byte_size(conn.resp_body) == 200
  142. assert Plug.Conn.get_resp_header(conn, "content-type") == ["application/octet-stream"]
  143. end
  144. defp headers_mock(_) do
  145. ClientMock
  146. |> expect(:request, fn :get, "/headers", headers, _, _ ->
  147. Registry.register(Pleroma.ReverseProxy.ClientMock, "/headers", 0)
  148. {:ok, 200, [{"content-type", "application/json"}], %{url: "/headers", headers: headers}}
  149. end)
  150. |> expect(:stream_body, 2, fn %{url: url, headers: headers} ->
  151. case Registry.lookup(Pleroma.ReverseProxy.ClientMock, url) do
  152. [{_, 0}] ->
  153. Registry.update_value(Pleroma.ReverseProxy.ClientMock, url, &(&1 + 1))
  154. headers = for {k, v} <- headers, into: %{}, do: {String.capitalize(k), v}
  155. {:ok, Jason.encode!(%{headers: headers})}
  156. [{_, 1}] ->
  157. Registry.unregister(Pleroma.ReverseProxy.ClientMock, url)
  158. :done
  159. end
  160. end)
  161. :ok
  162. end
  163. describe "keep request headers" do
  164. setup [:headers_mock]
  165. test "header passes", %{conn: conn} do
  166. conn =
  167. Plug.Conn.put_req_header(
  168. conn,
  169. "accept",
  170. "text/html"
  171. )
  172. |> ReverseProxy.call("/headers")
  173. %{"headers" => headers} = json_response(conn, 200)
  174. assert headers["Accept"] == "text/html"
  175. end
  176. test "header is filtered", %{conn: conn} do
  177. conn =
  178. Plug.Conn.put_req_header(
  179. conn,
  180. "accept-language",
  181. "en-US"
  182. )
  183. |> ReverseProxy.call("/headers")
  184. %{"headers" => headers} = json_response(conn, 200)
  185. refute headers["Accept-Language"]
  186. end
  187. end
  188. test "returns 400 on non GET, HEAD requests", %{conn: conn} do
  189. conn = ReverseProxy.call(Map.put(conn, :method, "POST"), "/ip")
  190. assert conn.status == 400
  191. end
  192. describe "cache resp headers" do
  193. test "returns headers", %{conn: conn} do
  194. ClientMock
  195. |> expect(:request, fn :get, "/cache/" <> ttl, _, _, _ ->
  196. {:ok, 200, [{"cache-control", "public, max-age=" <> ttl}], %{}}
  197. end)
  198. |> expect(:stream_body, fn _ -> :done end)
  199. conn = ReverseProxy.call(conn, "/cache/10")
  200. assert {"cache-control", "public, max-age=10"} in conn.resp_headers
  201. end
  202. test "add cache-control", %{conn: conn} do
  203. ClientMock
  204. |> expect(:request, fn :get, "/cache", _, _, _ ->
  205. {:ok, 200, [{"ETag", "some ETag"}], %{}}
  206. end)
  207. |> expect(:stream_body, fn _ -> :done end)
  208. conn = ReverseProxy.call(conn, "/cache")
  209. assert {"cache-control", "public"} in conn.resp_headers
  210. end
  211. end
  212. defp disposition_headers_mock(headers) do
  213. ClientMock
  214. |> expect(:request, fn :get, "/disposition", _, _, _ ->
  215. Registry.register(Pleroma.ReverseProxy.ClientMock, "/disposition", 0)
  216. {:ok, 200, headers, %{url: "/disposition"}}
  217. end)
  218. |> expect(:stream_body, 2, fn %{url: "/disposition"} ->
  219. case Registry.lookup(Pleroma.ReverseProxy.ClientMock, "/disposition") do
  220. [{_, 0}] ->
  221. Registry.update_value(Pleroma.ReverseProxy.ClientMock, "/disposition", &(&1 + 1))
  222. {:ok, ""}
  223. [{_, 1}] ->
  224. Registry.unregister(Pleroma.ReverseProxy.ClientMock, "/disposition")
  225. :done
  226. end
  227. end)
  228. end
  229. describe "response content disposition header" do
  230. test "not atachment", %{conn: conn} do
  231. disposition_headers_mock([
  232. {"content-type", "image/gif"},
  233. {"content-length", 0}
  234. ])
  235. conn = ReverseProxy.call(conn, "/disposition")
  236. assert {"content-type", "image/gif"} in conn.resp_headers
  237. end
  238. test "with content-disposition header", %{conn: conn} do
  239. disposition_headers_mock([
  240. {"content-disposition", "attachment; filename=\"filename.jpg\""},
  241. {"content-length", 0}
  242. ])
  243. conn = ReverseProxy.call(conn, "/disposition")
  244. assert {"content-disposition", "attachment; filename=\"filename.jpg\""} in conn.resp_headers
  245. end
  246. end
  247. end