1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
5 defmodule Pleroma.Web.MediaProxy.MediaProxyControllerTest do
6 use Pleroma.Web.ConnCase
11 alias Pleroma.ReverseProxy.ClientMock
12 alias Pleroma.UnstubbedConfigMock, as: ConfigMock
13 alias Pleroma.Web.MediaProxy
18 |> stub_with(Pleroma.Test.StaticConfig)
23 describe "Media Proxy" do
25 clear_config([:media_proxy, :enabled], true)
26 clear_config([Pleroma.Web.Endpoint, :secret_key_base], "00000000000")
28 [url: MediaProxy.encode_url("https://google.fn/test.png")]
31 test "it returns 404 when disabled", %{conn: conn} do
32 clear_config([:media_proxy, :enabled], false)
36 resp_body: "Not Found"
37 } = get(conn, "/proxy/hhgfh/eeeee")
41 resp_body: "Not Found"
42 } = get(conn, "/proxy/hhgfh/eeee/fff")
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)
51 resp_body: "Forbidden"
56 resp_body: "Forbidden"
57 } = get(conn, "/proxy/hhgfh/eeee")
61 resp_body: "Forbidden"
62 } = get(conn, "/proxy/hhgfh/eeee/fff")
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
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)
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")
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)
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)
93 |> expect(:request, fn :get, ^media_url, _, _, _ ->
94 {:ok, 200, [{"content-type", "image/png"}]}
97 %Conn{resp_headers: headers} = get(conn, media_proxy_url)
99 assert {"content-security-policy", "sandbox;"} in headers
103 describe "Media Preview Proxy" do
104 def assert_dependencies_installed do
105 missing_dependencies = Pleroma.Helpers.MediaHelper.missing_dependencies()
107 assert missing_dependencies == [],
108 "Error: missing dependencies (please refer to `docs/installation`): #{inspect(missing_dependencies)}"
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")
116 original_url = "https://google.fn/test.png"
119 url: MediaProxy.encode_preview_url(original_url),
120 media_proxy_url: MediaProxy.encode_url(original_url)
124 test "returns 404 when media proxy is disabled", %{conn: conn} do
125 clear_config([:media_proxy, :enabled], false)
129 resp_body: "Not Found"
130 } = get(conn, "/proxy/preview/hhgfh/eeeee")
134 resp_body: "Not Found"
135 } = get(conn, "/proxy/preview/hhgfh/fff")
138 test "returns 404 when disabled", %{conn: conn} do
139 clear_config([:media_preview_proxy, :enabled], false)
143 resp_body: "Not Found"
144 } = get(conn, "/proxy/preview/hhgfh/eeeee")
148 resp_body: "Not Found"
149 } = get(conn, "/proxy/preview/hhgfh/fff")
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)
158 resp_body: "Forbidden"
163 resp_body: "Forbidden"
164 } = get(conn, "/proxy/preview/hhgfh/eeee")
168 resp_body: "Forbidden"
169 } = get(conn, "/proxy/preview/hhgfh/eeee/fff")
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
179 test "responds with 424 Failed Dependency if HEAD request to media proxy fails", %{
182 media_proxy_url: media_proxy_url
185 %{method: :head, url: ^media_proxy_url} ->
186 %Tesla.Env{status: 500, body: ""}
189 response = get(conn, url)
190 assert response.status == 424
191 assert response.resp_body == "Can't fetch HTTP headers (HTTP 500)."
194 test "redirects to media proxy URI on unsupported content type", %{
197 media_proxy_url: media_proxy_url
200 %{method: :head, url: ^media_proxy_url} ->
201 %Tesla.Env{status: 200, body: "", headers: [{"content-type", "application/pdf"}]}
204 response = get(conn, url)
205 assert response.status == 302
206 assert redirected_to(response) == media_proxy_url
209 test "with `static=true` and GIF image preview requested, responds with JPEG image", %{
212 media_proxy_url: media_proxy_url
214 assert_dependencies_installed()
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)
220 %{method: :head, url: ^media_proxy_url} ->
224 headers: [{"content-type", "image/gif"}, {"content-length", "1001718"}]
227 %{method: :get, url: ^media_proxy_url} ->
228 %Tesla.Env{status: 200, body: File.read!("test/fixtures/image.gif")}
231 response = get(conn, url <> "?static=true")
233 assert response.status == 200
234 assert Conn.get_resp_header(response, "content-type") == ["image/jpeg"]
235 assert response.resp_body != ""
238 test "with GIF image preview requested and no `static` param, redirects to media proxy URI",
242 media_proxy_url: media_proxy_url
245 %{method: :head, url: ^media_proxy_url} ->
246 %Tesla.Env{status: 200, body: "", headers: [{"content-type", "image/gif"}]}
249 response = get(conn, url)
251 assert response.status == 302
252 assert redirected_to(response) == media_proxy_url
255 test "with `static` param and non-GIF image preview requested, " <>
256 "redirects to media preview proxy URI without `static` param",
260 media_proxy_url: media_proxy_url
263 %{method: :head, url: ^media_proxy_url} ->
264 %Tesla.Env{status: 200, body: "", headers: [{"content-type", "image/jpeg"}]}
267 response = get(conn, url <> "?static=true")
269 assert response.status == 302
270 assert redirected_to(response) == url
273 test "with :min_content_length setting not matched by Content-Length header, " <>
274 "redirects to media proxy URI",
278 media_proxy_url: media_proxy_url
280 clear_config([:media_preview_proxy, :min_content_length], 100_000)
283 %{method: :head, url: ^media_proxy_url} ->
287 headers: [{"content-type", "image/gif"}, {"content-length", "5000"}]
291 response = get(conn, url)
293 assert response.status == 302
294 assert redirected_to(response) == media_proxy_url
297 test "thumbnails PNG images into PNG", %{
300 media_proxy_url: media_proxy_url
302 assert_dependencies_installed()
305 %{method: :head, url: ^media_proxy_url} ->
306 %Tesla.Env{status: 200, body: "", headers: [{"content-type", "image/png"}]}
308 %{method: :get, url: ^media_proxy_url} ->
309 %Tesla.Env{status: 200, body: File.read!("test/fixtures/image.png")}
312 response = get(conn, url)
314 assert response.status == 200
315 assert Conn.get_resp_header(response, "content-type") == ["image/png"]
316 assert response.resp_body != ""
319 test "thumbnails JPEG images into JPEG", %{
322 media_proxy_url: media_proxy_url
324 assert_dependencies_installed()
327 %{method: :head, url: ^media_proxy_url} ->
328 %Tesla.Env{status: 200, body: "", headers: [{"content-type", "image/jpeg"}]}
330 %{method: :get, url: ^media_proxy_url} ->
331 %Tesla.Env{status: 200, body: File.read!("test/fixtures/image.jpg")}
334 response = get(conn, url)
336 assert response.status == 200
337 assert Conn.get_resp_header(response, "content-type") == ["image/jpeg"]
338 assert response.resp_body != ""
341 test "redirects to media proxy URI in case of thumbnailing error", %{
344 media_proxy_url: media_proxy_url
347 %{method: :head, url: ^media_proxy_url} ->
348 %Tesla.Env{status: 200, body: "", headers: [{"content-type", "image/jpeg"}]}
350 %{method: :get, url: ^media_proxy_url} ->
351 %Tesla.Env{status: 200, body: "<html><body>error</body></html>"}
354 response = get(conn, url)
356 assert response.status == 302
357 assert redirected_to(response) == media_proxy_url