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