1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
5 defmodule Pleroma.ReverseProxyTest do
6 use Pleroma.Web.ConnCase
7 import ExUnit.CaptureLog
9 alias Pleroma.ReverseProxy
12 describe "reverse proxy" do
13 test "do not track successful request", %{conn: conn} do
16 Tesla.Mock.mock(fn %{url: ^url} ->
23 conn = ReverseProxy.call(conn, url)
25 assert response(conn, 200)
26 assert Cachex.get(:failed_proxy_url_cache, url) == {:ok, nil}
29 test "use Pleroma's user agent in the request; don't pass the client's", %{conn: conn} do
30 clear_config([:http, :send_user_agent], true)
31 # Mock will fail if the client's user agent isn't filtered
32 wanted_headers = [{"user-agent", Pleroma.Application.user_agent()}]
34 Tesla.Mock.mock(fn %{url: "/user-agent", headers: ^wanted_headers} ->
43 |> Plug.Conn.put_req_header("user-agent", "fake/1.0")
44 |> ReverseProxy.call("/user-agent")
46 assert response(conn, 200)
50 describe "max_body" do
51 test "length returns error if content-length more than option", %{conn: conn} do
52 Tesla.Mock.mock(fn %{url: "/huge-file"} ->
55 headers: [{"content-length", "100"}],
56 body: "This body is too large."
60 assert capture_log(fn ->
61 ReverseProxy.call(conn, "/huge-file", max_body_length: 4)
63 "[error] Elixir.Pleroma.ReverseProxy: request to \"/huge-file\" failed: :body_too_large"
65 assert {:ok, true} == Cachex.get(:failed_proxy_url_cache, "/huge-file")
67 assert capture_log(fn ->
68 ReverseProxy.call(conn, "/huge-file", max_body_length: 4)
73 describe "HEAD requests" do
74 test "common", %{conn: conn} do
75 Tesla.Mock.mock(fn %{method: :head, url: "/head"} ->
78 headers: [{"content-type", "text/html; charset=utf-8"}],
83 conn = ReverseProxy.call(Map.put(conn, :method, "HEAD"), "/head")
84 assert html_response(conn, 200) == ""
88 describe "returns error on" do
89 test "500", %{conn: conn} do
92 Tesla.Mock.mock(fn %{url: ^url} ->
99 capture_log(fn -> ReverseProxy.call(conn, url) end) =~
100 "[error] Elixir.Pleroma.ReverseProxy: request to /status/500 failed with HTTP status 500"
102 assert Cachex.get(:failed_proxy_url_cache, url) == {:ok, true}
104 {:ok, ttl} = Cachex.ttl(:failed_proxy_url_cache, url)
108 test "400", %{conn: conn} do
111 Tesla.Mock.mock(fn %{url: ^url} ->
118 capture_log(fn -> ReverseProxy.call(conn, url) end) =~
119 "[error] Elixir.Pleroma.ReverseProxy: request to /status/400 failed with HTTP status 400"
121 assert Cachex.get(:failed_proxy_url_cache, url) == {:ok, true}
122 assert Cachex.ttl(:failed_proxy_url_cache, url) == {:ok, nil}
125 test "403", %{conn: conn} do
128 Tesla.Mock.mock(fn %{url: ^url} ->
136 ReverseProxy.call(conn, url, failed_request_ttl: :timer.seconds(120))
138 "[error] Elixir.Pleroma.ReverseProxy: request to /status/403 failed with HTTP status 403"
140 {:ok, ttl} = Cachex.ttl(:failed_proxy_url_cache, url)
145 describe "keep request headers" do
146 test "header passes", %{conn: conn} do
147 Tesla.Mock.mock(fn %{url: "/headers"} ->
160 |> ReverseProxy.call("/headers")
162 assert response(conn, 200)
163 assert {"accept", "text/html"} in conn.req_headers
166 test "header is filtered", %{conn: conn} do
167 # Mock will fail if the accept-language header isn't filtered
168 wanted_headers = [{"accept-encoding", "*"}]
170 Tesla.Mock.mock(fn %{url: "/headers", headers: ^wanted_headers} ->
179 |> Conn.put_req_header("accept-language", "en-US")
180 |> Conn.put_req_header("accept-encoding", "*")
181 |> ReverseProxy.call("/headers")
183 assert response(conn, 200)
187 test "returns 400 on non GET, HEAD requests", %{conn: conn} do
188 Tesla.Mock.mock(fn %{url: "/ip"} ->
195 conn = ReverseProxy.call(Map.put(conn, :method, "POST"), "/ip")
196 assert response(conn, 400)
199 describe "cache resp headers not filtered" do
200 test "add cache-control", %{conn: conn} do
201 Tesla.Mock.mock(fn %{url: "/cache"} ->
205 {"cache-control", "public, max-age=1209600"},
206 {"etag", "some ETag"},
207 {"expires", "Wed, 21 Oct 2015 07:28:00 GMT"}
213 conn = ReverseProxy.call(conn, "/cache")
214 assert {"cache-control", "public, max-age=1209600"} in conn.resp_headers
215 assert {"etag", "some ETag"} in conn.resp_headers
216 assert {"expires", "Wed, 21 Oct 2015 07:28:00 GMT"} in conn.resp_headers
220 describe "response content disposition header" do
221 test "not attachment", %{conn: conn} do
222 Tesla.Mock.mock(fn %{url: "/disposition"} ->
226 {"content-type", "image/gif"},
227 {"content-length", "0"}
233 conn = ReverseProxy.call(conn, "/disposition")
235 assert {"content-type", "image/gif"} in conn.resp_headers
238 test "with content-disposition header", %{conn: conn} do
239 Tesla.Mock.mock(fn %{url: "/disposition"} ->
243 {"content-disposition", "attachment; filename=\"filename.jpg\""},
244 {"content-length", "0"}
250 conn = ReverseProxy.call(conn, "/disposition")
252 assert {"content-disposition", "attachment; filename=\"filename.jpg\""} in conn.resp_headers