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.Web.MediaProxy
15 describe "Media Proxy" do
17 clear_config([:media_proxy, :enabled], true)
18 clear_config([Pleroma.Web.Endpoint, :secret_key_base], "00000000000")
20 [url: MediaProxy.encode_url("https://google.fn/test.png")]
23 test "it returns 404 when disabled", %{conn: conn} do
24 clear_config([:media_proxy, :enabled], false)
28 resp_body: "Not Found"
29 } = get(conn, "/proxy/hhgfh/eeeee")
33 resp_body: "Not Found"
34 } = get(conn, "/proxy/hhgfh/eeee/fff")
37 test "it returns 403 for invalid signature", %{conn: conn, url: url} do
38 clear_config([Pleroma.Web.Endpoint, :secret_key_base], "000")
39 %{path: path} = URI.parse(url)
43 resp_body: "Forbidden"
48 resp_body: "Forbidden"
49 } = get(conn, "/proxy/hhgfh/eeee")
53 resp_body: "Forbidden"
54 } = get(conn, "/proxy/hhgfh/eeee/fff")
57 test "redirects to valid url when filename is invalidated", %{conn: conn, url: url} do
58 invalid_url = String.replace(url, "test.png", "test-file.png")
59 response = get(conn, invalid_url)
60 assert response.status == 302
61 assert redirected_to(response) == url
64 test "it performs ReverseProxy.call with valid signature", %{conn: conn, url: url} do
65 with_mock Pleroma.ReverseProxy,
66 call: fn _conn, _url, _opts -> %Conn{status: :success} end do
67 assert %Conn{status: :success} = get(conn, url)
71 test "it returns 404 when url is in banned_urls cache", %{conn: conn, url: url} do
72 MediaProxy.put_in_banned_urls("https://google.fn/test.png")
74 with_mock Pleroma.ReverseProxy,
75 call: fn _conn, _url, _opts -> %Conn{status: :success} end do
76 assert %Conn{status: 404, resp_body: "Not Found"} = get(conn, url)
80 test "it applies sandbox CSP to MediaProxy requests", %{conn: conn} do
81 media_url = "https://lain.com/image.png"
82 media_proxy_url = MediaProxy.encode_url(media_url)
85 |> expect(:request, fn :get, ^media_url, _, _, _ ->
86 {:ok, 200, [{"content-type", "image/png"}]}
89 %Conn{resp_headers: headers} = get(conn, media_proxy_url)
91 assert {"content-security-policy", "sandbox;"} in headers
95 describe "Media Preview Proxy" do
96 def assert_dependencies_installed do
97 missing_dependencies = Pleroma.Helpers.MediaHelper.missing_dependencies()
99 assert missing_dependencies == [],
100 "Error: missing dependencies (please refer to `docs/installation`): #{inspect(missing_dependencies)}"
104 clear_config([:media_proxy, :enabled], true)
105 clear_config([:media_preview_proxy, :enabled], true)
106 clear_config([Pleroma.Web.Endpoint, :secret_key_base], "00000000000")
108 original_url = "https://google.fn/test.png"
111 url: MediaProxy.encode_preview_url(original_url),
112 media_proxy_url: MediaProxy.encode_url(original_url)
116 test "returns 404 when media proxy is disabled", %{conn: conn} do
117 clear_config([:media_proxy, :enabled], false)
121 resp_body: "Not Found"
122 } = get(conn, "/proxy/preview/hhgfh/eeeee")
126 resp_body: "Not Found"
127 } = get(conn, "/proxy/preview/hhgfh/fff")
130 test "returns 404 when disabled", %{conn: conn} do
131 clear_config([:media_preview_proxy, :enabled], false)
135 resp_body: "Not Found"
136 } = get(conn, "/proxy/preview/hhgfh/eeeee")
140 resp_body: "Not Found"
141 } = get(conn, "/proxy/preview/hhgfh/fff")
144 test "it returns 403 for invalid signature", %{conn: conn, url: url} do
145 clear_config([Pleroma.Web.Endpoint, :secret_key_base], "000")
146 %{path: path} = URI.parse(url)
150 resp_body: "Forbidden"
155 resp_body: "Forbidden"
156 } = get(conn, "/proxy/preview/hhgfh/eeee")
160 resp_body: "Forbidden"
161 } = get(conn, "/proxy/preview/hhgfh/eeee/fff")
164 test "redirects to valid url when filename is invalidated", %{conn: conn, url: url} do
165 invalid_url = String.replace(url, "test.png", "test-file.png")
166 response = get(conn, invalid_url)
167 assert response.status == 302
168 assert redirected_to(response) == url
171 test "responds with 424 Failed Dependency if HEAD request to media proxy fails", %{
174 media_proxy_url: media_proxy_url
177 %{method: "HEAD", url: ^media_proxy_url} ->
178 %Tesla.Env{status: 500, body: ""}
181 response = get(conn, url)
182 assert response.status == 424
183 assert response.resp_body == "Can't fetch HTTP headers (HTTP 500)."
186 test "redirects to media proxy URI on unsupported content type", %{
189 media_proxy_url: media_proxy_url
192 %{method: "HEAD", url: ^media_proxy_url} ->
193 %Tesla.Env{status: 200, body: "", headers: [{"content-type", "application/pdf"}]}
196 response = get(conn, url)
197 assert response.status == 302
198 assert redirected_to(response) == media_proxy_url
201 test "with `static=true` and GIF image preview requested, responds with JPEG image", %{
204 media_proxy_url: media_proxy_url
206 assert_dependencies_installed()
208 # Setting a high :min_content_length to ensure this scenario is not affected by its logic
209 clear_config([:media_preview_proxy, :min_content_length], 1_000_000_000)
212 %{method: "HEAD", url: ^media_proxy_url} ->
216 headers: [{"content-type", "image/gif"}, {"content-length", "1001718"}]
219 %{method: :get, url: ^media_proxy_url} ->
220 %Tesla.Env{status: 200, body: File.read!("test/fixtures/image.gif")}
223 response = get(conn, url <> "?static=true")
225 assert response.status == 200
226 assert Conn.get_resp_header(response, "content-type") == ["image/jpeg"]
227 assert response.resp_body != ""
230 test "with GIF image preview requested and no `static` param, redirects to media proxy URI",
234 media_proxy_url: media_proxy_url
237 %{method: "HEAD", url: ^media_proxy_url} ->
238 %Tesla.Env{status: 200, body: "", headers: [{"content-type", "image/gif"}]}
241 response = get(conn, url)
243 assert response.status == 302
244 assert redirected_to(response) == media_proxy_url
247 test "with `static` param and non-GIF image preview requested, " <>
248 "redirects to media preview proxy URI without `static` param",
252 media_proxy_url: media_proxy_url
255 %{method: "HEAD", url: ^media_proxy_url} ->
256 %Tesla.Env{status: 200, body: "", headers: [{"content-type", "image/jpeg"}]}
259 response = get(conn, url <> "?static=true")
261 assert response.status == 302
262 assert redirected_to(response) == url
265 test "with :min_content_length setting not matched by Content-Length header, " <>
266 "redirects to media proxy URI",
270 media_proxy_url: media_proxy_url
272 clear_config([:media_preview_proxy, :min_content_length], 100_000)
275 %{method: "HEAD", url: ^media_proxy_url} ->
279 headers: [{"content-type", "image/gif"}, {"content-length", "5000"}]
283 response = get(conn, url)
285 assert response.status == 302
286 assert redirected_to(response) == media_proxy_url
289 test "thumbnails PNG images into PNG", %{
292 media_proxy_url: media_proxy_url
294 assert_dependencies_installed()
297 %{method: "HEAD", url: ^media_proxy_url} ->
298 %Tesla.Env{status: 200, body: "", headers: [{"content-type", "image/png"}]}
300 %{method: :get, url: ^media_proxy_url} ->
301 %Tesla.Env{status: 200, body: File.read!("test/fixtures/image.png")}
304 response = get(conn, url)
306 assert response.status == 200
307 assert Conn.get_resp_header(response, "content-type") == ["image/png"]
308 assert response.resp_body != ""
311 test "thumbnails JPEG images into JPEG", %{
314 media_proxy_url: media_proxy_url
316 assert_dependencies_installed()
319 %{method: "HEAD", url: ^media_proxy_url} ->
320 %Tesla.Env{status: 200, body: "", headers: [{"content-type", "image/jpeg"}]}
322 %{method: :get, url: ^media_proxy_url} ->
323 %Tesla.Env{status: 200, body: File.read!("test/fixtures/image.jpg")}
326 response = get(conn, url)
328 assert response.status == 200
329 assert Conn.get_resp_header(response, "content-type") == ["image/jpeg"]
330 assert response.resp_body != ""
333 test "redirects to media proxy URI in case of thumbnailing error", %{
336 media_proxy_url: media_proxy_url
339 %{method: "HEAD", url: ^media_proxy_url} ->
340 %Tesla.Env{status: 200, body: "", headers: [{"content-type", "image/jpeg"}]}
342 %{method: :get, url: ^media_proxy_url} ->
343 %Tesla.Env{status: 200, body: "<html><body>error</body></html>"}
346 response = get(conn, url)
348 assert response.status == 302
349 assert redirected_to(response) == media_proxy_url