total rebase
[anni] / test / pleroma / web / media_proxy / media_proxy_controller_test.exs
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Web.MediaProxy.MediaProxyControllerTest do
6   use Pleroma.Web.ConnCase
7
8   import Mock
9   import Mox
10
11   alias Pleroma.ReverseProxy.ClientMock
12   alias Pleroma.UnstubbedConfigMock, as: ConfigMock
13   alias Pleroma.Web.MediaProxy
14   alias Plug.Conn
15
16   setup do
17     ConfigMock
18     |> stub_with(Pleroma.Test.StaticConfig)
19
20     :ok
21   end
22
23   describe "Media Proxy" do
24     setup do
25       clear_config([:media_proxy, :enabled], true)
26       clear_config([Pleroma.Web.Endpoint, :secret_key_base], "00000000000")
27
28       [url: MediaProxy.encode_url("https://google.fn/test.png")]
29     end
30
31     test "it returns 404 when disabled", %{conn: conn} do
32       clear_config([:media_proxy, :enabled], false)
33
34       assert %Conn{
35                status: 404,
36                resp_body: "Not Found"
37              } = get(conn, "/proxy/hhgfh/eeeee")
38
39       assert %Conn{
40                status: 404,
41                resp_body: "Not Found"
42              } = get(conn, "/proxy/hhgfh/eeee/fff")
43     end
44
45     test "it returns 403 for invalid signature", %{conn: conn, url: url} do
46       clear_config([Pleroma.Web.Endpoint, :secret_key_base], "000")
47       %{path: path} = URI.parse(url)
48
49       assert %Conn{
50                status: 403,
51                resp_body: "Forbidden"
52              } = get(conn, path)
53
54       assert %Conn{
55                status: 403,
56                resp_body: "Forbidden"
57              } = get(conn, "/proxy/hhgfh/eeee")
58
59       assert %Conn{
60                status: 403,
61                resp_body: "Forbidden"
62              } = get(conn, "/proxy/hhgfh/eeee/fff")
63     end
64
65     test "redirects to valid url when filename is invalidated", %{conn: conn, url: url} do
66       invalid_url = String.replace(url, "test.png", "test-file.png")
67       response = get(conn, invalid_url)
68       assert response.status == 302
69       assert redirected_to(response) == url
70     end
71
72     test "it performs ReverseProxy.call with valid signature", %{conn: conn, url: url} do
73       with_mock Pleroma.ReverseProxy,
74         call: fn _conn, _url, _opts -> %Conn{status: :success} end do
75         assert %Conn{status: :success} = get(conn, url)
76       end
77     end
78
79     test "it returns 404 when url is in banned_urls cache", %{conn: conn, url: url} do
80       MediaProxy.put_in_banned_urls("https://google.fn/test.png")
81
82       with_mock Pleroma.ReverseProxy,
83         call: fn _conn, _url, _opts -> %Conn{status: :success} end do
84         assert %Conn{status: 404, resp_body: "Not Found"} = get(conn, url)
85       end
86     end
87
88     test "it applies sandbox CSP to MediaProxy requests", %{conn: conn} do
89       media_url = "https://lain.com/image.png"
90       media_proxy_url = MediaProxy.encode_url(media_url)
91
92       ClientMock
93       |> expect(:request, fn :get, ^media_url, _, _, _ ->
94         {:ok, 200, [{"content-type", "image/png"}]}
95       end)
96
97       %Conn{resp_headers: headers} = get(conn, media_proxy_url)
98
99       assert {"content-security-policy", "sandbox;"} in headers
100     end
101   end
102
103   describe "Media Preview Proxy" do
104     def assert_dependencies_installed do
105       missing_dependencies = Pleroma.Helpers.MediaHelper.missing_dependencies()
106
107       assert missing_dependencies == [],
108              "Error: missing dependencies (please refer to `docs/installation`): #{inspect(missing_dependencies)}"
109     end
110
111     setup do
112       clear_config([:media_proxy, :enabled], true)
113       clear_config([:media_preview_proxy, :enabled], true)
114       clear_config([Pleroma.Web.Endpoint, :secret_key_base], "00000000000")
115
116       original_url = "https://google.fn/test.png"
117
118       [
119         url: MediaProxy.encode_preview_url(original_url),
120         media_proxy_url: MediaProxy.encode_url(original_url)
121       ]
122     end
123
124     test "returns 404 when media proxy is disabled", %{conn: conn} do
125       clear_config([:media_proxy, :enabled], false)
126
127       assert %Conn{
128                status: 404,
129                resp_body: "Not Found"
130              } = get(conn, "/proxy/preview/hhgfh/eeeee")
131
132       assert %Conn{
133                status: 404,
134                resp_body: "Not Found"
135              } = get(conn, "/proxy/preview/hhgfh/fff")
136     end
137
138     test "returns 404 when disabled", %{conn: conn} do
139       clear_config([:media_preview_proxy, :enabled], false)
140
141       assert %Conn{
142                status: 404,
143                resp_body: "Not Found"
144              } = get(conn, "/proxy/preview/hhgfh/eeeee")
145
146       assert %Conn{
147                status: 404,
148                resp_body: "Not Found"
149              } = get(conn, "/proxy/preview/hhgfh/fff")
150     end
151
152     test "it returns 403 for invalid signature", %{conn: conn, url: url} do
153       clear_config([Pleroma.Web.Endpoint, :secret_key_base], "000")
154       %{path: path} = URI.parse(url)
155
156       assert %Conn{
157                status: 403,
158                resp_body: "Forbidden"
159              } = get(conn, path)
160
161       assert %Conn{
162                status: 403,
163                resp_body: "Forbidden"
164              } = get(conn, "/proxy/preview/hhgfh/eeee")
165
166       assert %Conn{
167                status: 403,
168                resp_body: "Forbidden"
169              } = get(conn, "/proxy/preview/hhgfh/eeee/fff")
170     end
171
172     test "redirects to valid url when filename is invalidated", %{conn: conn, url: url} do
173       invalid_url = String.replace(url, "test.png", "test-file.png")
174       response = get(conn, invalid_url)
175       assert response.status == 302
176       assert redirected_to(response) == url
177     end
178
179     test "responds with 424 Failed Dependency if HEAD request to media proxy fails", %{
180       conn: conn,
181       url: url,
182       media_proxy_url: media_proxy_url
183     } do
184       Tesla.Mock.mock(fn
185         %{method: :head, url: ^media_proxy_url} ->
186           %Tesla.Env{status: 500, body: ""}
187       end)
188
189       response = get(conn, url)
190       assert response.status == 424
191       assert response.resp_body == "Can't fetch HTTP headers (HTTP 500)."
192     end
193
194     test "redirects to media proxy URI on unsupported content type", %{
195       conn: conn,
196       url: url,
197       media_proxy_url: media_proxy_url
198     } do
199       Tesla.Mock.mock(fn
200         %{method: :head, url: ^media_proxy_url} ->
201           %Tesla.Env{status: 200, body: "", headers: [{"content-type", "application/pdf"}]}
202       end)
203
204       response = get(conn, url)
205       assert response.status == 302
206       assert redirected_to(response) == media_proxy_url
207     end
208
209     test "with `static=true` and GIF image preview requested, responds with JPEG image", %{
210       conn: conn,
211       url: url,
212       media_proxy_url: media_proxy_url
213     } do
214       assert_dependencies_installed()
215
216       # Setting a high :min_content_length to ensure this scenario is not affected by its logic
217       clear_config([:media_preview_proxy, :min_content_length], 1_000_000_000)
218
219       Tesla.Mock.mock(fn
220         %{method: :head, url: ^media_proxy_url} ->
221           %Tesla.Env{
222             status: 200,
223             body: "",
224             headers: [{"content-type", "image/gif"}, {"content-length", "1001718"}]
225           }
226
227         %{method: :get, url: ^media_proxy_url} ->
228           %Tesla.Env{status: 200, body: File.read!("test/fixtures/image.gif")}
229       end)
230
231       response = get(conn, url <> "?static=true")
232
233       assert response.status == 200
234       assert Conn.get_resp_header(response, "content-type") == ["image/jpeg"]
235       assert response.resp_body != ""
236     end
237
238     test "with GIF image preview requested and no `static` param, redirects to media proxy URI",
239          %{
240            conn: conn,
241            url: url,
242            media_proxy_url: media_proxy_url
243          } do
244       Tesla.Mock.mock(fn
245         %{method: :head, url: ^media_proxy_url} ->
246           %Tesla.Env{status: 200, body: "", headers: [{"content-type", "image/gif"}]}
247       end)
248
249       response = get(conn, url)
250
251       assert response.status == 302
252       assert redirected_to(response) == media_proxy_url
253     end
254
255     test "with `static` param and non-GIF image preview requested, " <>
256            "redirects to media preview proxy URI without `static` param",
257          %{
258            conn: conn,
259            url: url,
260            media_proxy_url: media_proxy_url
261          } do
262       Tesla.Mock.mock(fn
263         %{method: :head, url: ^media_proxy_url} ->
264           %Tesla.Env{status: 200, body: "", headers: [{"content-type", "image/jpeg"}]}
265       end)
266
267       response = get(conn, url <> "?static=true")
268
269       assert response.status == 302
270       assert redirected_to(response) == url
271     end
272
273     test "with :min_content_length setting not matched by Content-Length header, " <>
274            "redirects to media proxy URI",
275          %{
276            conn: conn,
277            url: url,
278            media_proxy_url: media_proxy_url
279          } do
280       clear_config([:media_preview_proxy, :min_content_length], 100_000)
281
282       Tesla.Mock.mock(fn
283         %{method: :head, url: ^media_proxy_url} ->
284           %Tesla.Env{
285             status: 200,
286             body: "",
287             headers: [{"content-type", "image/gif"}, {"content-length", "5000"}]
288           }
289       end)
290
291       response = get(conn, url)
292
293       assert response.status == 302
294       assert redirected_to(response) == media_proxy_url
295     end
296
297     test "thumbnails PNG images into PNG", %{
298       conn: conn,
299       url: url,
300       media_proxy_url: media_proxy_url
301     } do
302       assert_dependencies_installed()
303
304       Tesla.Mock.mock(fn
305         %{method: :head, url: ^media_proxy_url} ->
306           %Tesla.Env{status: 200, body: "", headers: [{"content-type", "image/png"}]}
307
308         %{method: :get, url: ^media_proxy_url} ->
309           %Tesla.Env{status: 200, body: File.read!("test/fixtures/image.png")}
310       end)
311
312       response = get(conn, url)
313
314       assert response.status == 200
315       assert Conn.get_resp_header(response, "content-type") == ["image/png"]
316       assert response.resp_body != ""
317     end
318
319     test "thumbnails JPEG images into JPEG", %{
320       conn: conn,
321       url: url,
322       media_proxy_url: media_proxy_url
323     } do
324       assert_dependencies_installed()
325
326       Tesla.Mock.mock(fn
327         %{method: :head, url: ^media_proxy_url} ->
328           %Tesla.Env{status: 200, body: "", headers: [{"content-type", "image/jpeg"}]}
329
330         %{method: :get, url: ^media_proxy_url} ->
331           %Tesla.Env{status: 200, body: File.read!("test/fixtures/image.jpg")}
332       end)
333
334       response = get(conn, url)
335
336       assert response.status == 200
337       assert Conn.get_resp_header(response, "content-type") == ["image/jpeg"]
338       assert response.resp_body != ""
339     end
340
341     test "redirects to media proxy URI in case of thumbnailing error", %{
342       conn: conn,
343       url: url,
344       media_proxy_url: media_proxy_url
345     } do
346       Tesla.Mock.mock(fn
347         %{method: :head, url: ^media_proxy_url} ->
348           %Tesla.Env{status: 200, body: "", headers: [{"content-type", "image/jpeg"}]}
349
350         %{method: :get, url: ^media_proxy_url} ->
351           %Tesla.Env{status: 200, body: "<html><body>error</body></html>"}
352       end)
353
354       response = get(conn, url)
355
356       assert response.status == 302
357       assert redirected_to(response) == media_proxy_url
358     end
359   end
360 end