summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--config/config.exs13
-rw-r--r--lib/pleroma/helpers/mogrify_helper.ex25
-rw-r--r--lib/pleroma/reverse_proxy/reverse_proxy.ex4
-rw-r--r--lib/pleroma/web/mastodon_api/views/status_view.ex3
-rw-r--r--lib/pleroma/web/media_proxy/media_proxy.ex74
-rw-r--r--lib/pleroma/web/media_proxy/media_proxy_controller.ex136
-rw-r--r--lib/pleroma/web/router.ex2
-rw-r--r--mix.exs1
-rw-r--r--mix.lock2
-rw-r--r--test/web/media_proxy/media_proxy_test.exs187
10 files changed, 324 insertions, 123 deletions
diff --git a/config/config.exs b/config/config.exs
index e703c1632..e9403c7c8 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -381,6 +381,8 @@ config :pleroma, :media_proxy,
proxy_opts: [
redirect_on_failure: false,
max_body_length: 25 * 1_048_576,
+ # Note: max_read_duration defaults to Pleroma.ReverseProxy.max_read_duration_default/1
+ max_read_duration: 30_000,
http: [
follow_redirect: true,
pool: :media
@@ -388,6 +390,17 @@ config :pleroma, :media_proxy,
],
whitelist: []
+# Note: media preview proxy depends on media proxy to be enabled
+config :pleroma, :media_preview_proxy,
+ enabled: false,
+ enable_eimp: true,
+ thumbnail_max_width: 400,
+ thumbnail_max_height: 200,
+ proxy_opts: [
+ head_request_max_read_duration: 5_000,
+ max_read_duration: 10_000
+ ]
+
config :pleroma, :chat, enabled: true
config :phoenix, :format_encoders, json: Jason
diff --git a/lib/pleroma/helpers/mogrify_helper.ex b/lib/pleroma/helpers/mogrify_helper.ex
new file mode 100644
index 000000000..67edb35c3
--- /dev/null
+++ b/lib/pleroma/helpers/mogrify_helper.ex
@@ -0,0 +1,25 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Helpers.MogrifyHelper do
+ @moduledoc """
+ Handles common Mogrify operations.
+ """
+
+ @spec store_as_temporary_file(String.t(), binary()) :: {:ok, String.t()} | {:error, atom()}
+ @doc "Stores binary content fetched from specified URL as a temporary file."
+ def store_as_temporary_file(url, body) do
+ path = Mogrify.temporary_path_for(%{path: url})
+ with :ok <- File.write(path, body), do: {:ok, path}
+ end
+
+ @spec store_as_temporary_file(String.t(), String.t()) :: Mogrify.Image.t() | any()
+ @doc "Modifies file at specified path by resizing to specified limit dimensions."
+ def in_place_resize_to_limit(path, resize_dimensions) do
+ path
+ |> Mogrify.open()
+ |> Mogrify.resize_to_limit(resize_dimensions)
+ |> Mogrify.save(in_place: true)
+ end
+end
diff --git a/lib/pleroma/reverse_proxy/reverse_proxy.ex b/lib/pleroma/reverse_proxy/reverse_proxy.ex
index 4bbeb493c..aeaf9bd39 100644
--- a/lib/pleroma/reverse_proxy/reverse_proxy.ex
+++ b/lib/pleroma/reverse_proxy/reverse_proxy.ex
@@ -16,6 +16,8 @@ defmodule Pleroma.ReverseProxy do
@failed_request_ttl :timer.seconds(60)
@methods ~w(GET HEAD)
+ def max_read_duration_default, do: @max_read_duration
+
@moduledoc """
A reverse proxy.
@@ -370,6 +372,8 @@ defmodule Pleroma.ReverseProxy do
defp body_size_constraint(_, _), do: :ok
+ defp check_read_duration(nil = _duration, max), do: check_read_duration(@max_read_duration, max)
+
defp check_read_duration(duration, max)
when is_integer(duration) and is_integer(max) and max > 0 do
if duration > max do
diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex
index 24167f66f..2a206f743 100644
--- a/lib/pleroma/web/mastodon_api/views/status_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/status_view.ex
@@ -419,6 +419,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
[attachment_url | _] = attachment["url"]
media_type = attachment_url["mediaType"] || attachment_url["mimeType"] || "image"
href = attachment_url["href"] |> MediaProxy.url()
+ href_preview = attachment_url["href"] |> MediaProxy.preview_url()
type =
cond do
@@ -434,7 +435,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
id: to_string(attachment["id"] || hash_id),
url: href,
remote_url: href,
- preview_url: href,
+ preview_url: href_preview,
text_url: href,
type: type,
description: attachment["name"],
diff --git a/lib/pleroma/web/media_proxy/media_proxy.ex b/lib/pleroma/web/media_proxy/media_proxy.ex
index b2b524524..4e01c14e4 100644
--- a/lib/pleroma/web/media_proxy/media_proxy.ex
+++ b/lib/pleroma/web/media_proxy/media_proxy.ex
@@ -13,18 +13,32 @@ defmodule Pleroma.Web.MediaProxy do
def url("/" <> _ = url), do: url
def url(url) do
- if disabled?() or local?(url) or whitelisted?(url) do
+ if not enabled?() or local?(url) or whitelisted?(url) do
url
else
encode_url(url)
end
end
- defp disabled?, do: !Config.get([:media_proxy, :enabled], false)
+ # Note: routing all URLs to preview handler (even local and whitelisted).
+ # Preview handler will call url/1 on decoded URLs, and applicable ones will detour media proxy.
+ def preview_url(url) do
+ if preview_enabled?() do
+ encode_preview_url(url)
+ else
+ url
+ end
+ end
- defp local?(url), do: String.starts_with?(url, Pleroma.Web.base_url())
+ def enabled?, do: Config.get([:media_proxy, :enabled], false)
- defp whitelisted?(url) do
+ # Note: media proxy must be enabled for media preview proxy in order to load all
+ # non-local non-whitelisted URLs through it and be sure that body size constraint is preserved.
+ def preview_enabled?, do: enabled?() and Config.get([:media_preview_proxy, :enabled], false)
+
+ def local?(url), do: String.starts_with?(url, Pleroma.Web.base_url())
+
+ def whitelisted?(url) do
%{host: domain} = URI.parse(url)
mediaproxy_whitelist = Config.get([:media_proxy, :whitelist])
@@ -43,17 +57,29 @@ defmodule Pleroma.Web.MediaProxy do
end)
end
- def encode_url(url) do
+ defp base64_sig64(url) do
base64 = Base.url_encode64(url, @base64_opts)
sig64 =
base64
- |> signed_url
+ |> signed_url()
|> Base.url_encode64(@base64_opts)
+ {base64, sig64}
+ end
+
+ def encode_url(url) do
+ {base64, sig64} = base64_sig64(url)
+
build_url(sig64, base64, filename(url))
end
+ def encode_preview_url(url) do
+ {base64, sig64} = base64_sig64(url)
+
+ build_preview_url(sig64, base64, filename(url))
+ end
+
def decode_url(sig, url) do
with {:ok, sig} <- Base.url_decode64(sig, @base64_opts),
signature when signature == sig <- signed_url(url) do
@@ -71,10 +97,10 @@ defmodule Pleroma.Web.MediaProxy do
if path = URI.parse(url_or_path).path, do: Path.basename(path)
end
- def build_url(sig_base64, url_base64, filename \\ nil) do
+ defp proxy_url(path, sig_base64, url_base64, filename) do
[
Pleroma.Config.get([:media_proxy, :base_url], Web.base_url()),
- "proxy",
+ path,
sig_base64,
url_base64,
filename
@@ -82,4 +108,36 @@ defmodule Pleroma.Web.MediaProxy do
|> Enum.filter(& &1)
|> Path.join()
end
+
+ def build_url(sig_base64, url_base64, filename \\ nil) do
+ proxy_url("proxy", sig_base64, url_base64, filename)
+ end
+
+ def build_preview_url(sig_base64, url_base64, filename \\ nil) do
+ proxy_url("proxy/preview", sig_base64, url_base64, filename)
+ end
+
+ def verify_request_path_and_url(
+ %Plug.Conn{params: %{"filename" => _}, request_path: request_path},
+ url
+ ) do
+ verify_request_path_and_url(request_path, url)
+ end
+
+ def verify_request_path_and_url(request_path, url) when is_binary(request_path) do
+ filename = filename(url)
+
+ if filename && not basename_matches?(request_path, filename) do
+ {:wrong_filename, filename}
+ else
+ :ok
+ end
+ end
+
+ def verify_request_path_and_url(_, _), do: :ok
+
+ defp basename_matches?(path, filename) do
+ basename = Path.basename(path)
+ basename == filename or URI.decode(basename) == filename or URI.encode(basename) == filename
+ end
end
diff --git a/lib/pleroma/web/media_proxy/media_proxy_controller.ex b/lib/pleroma/web/media_proxy/media_proxy_controller.ex
index 4657a4383..8d8d073e9 100644
--- a/lib/pleroma/web/media_proxy/media_proxy_controller.ex
+++ b/lib/pleroma/web/media_proxy/media_proxy_controller.ex
@@ -5,19 +5,19 @@
defmodule Pleroma.Web.MediaProxy.MediaProxyController do
use Pleroma.Web, :controller
+ alias Pleroma.Config
+ alias Pleroma.Helpers.MogrifyHelper
alias Pleroma.ReverseProxy
alias Pleroma.Web.MediaProxy
- @default_proxy_opts [max_body_length: 25 * 1_048_576, http: [follow_redirect: true]]
-
- def remote(conn, %{"sig" => sig64, "url" => url64} = params) do
- with config <- Pleroma.Config.get([:media_proxy], []),
- true <- Keyword.get(config, :enabled, false),
+ def remote(conn, %{"sig" => sig64, "url" => url64}) do
+ with {_, true} <- {:enabled, MediaProxy.enabled?()},
{:ok, url} <- MediaProxy.decode_url(sig64, url64),
- :ok <- filename_matches(params, conn.request_path, url) do
- ReverseProxy.call(conn, url, Keyword.get(config, :proxy_opts, @default_proxy_opts))
+ :ok <- MediaProxy.verify_request_path_and_url(conn, url) do
+ proxy_opts = Config.get([:media_proxy, :proxy_opts], [])
+ ReverseProxy.call(conn, url, proxy_opts)
else
- false ->
+ {:enabled, false} ->
send_resp(conn, 404, Plug.Conn.Status.reason_phrase(404))
{:error, :invalid_signature} ->
@@ -28,20 +28,122 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do
end
end
- def filename_matches(%{"filename" => _} = _, path, url) do
- filename = MediaProxy.filename(url)
+ def preview(conn, %{"sig" => sig64, "url" => url64}) do
+ with {_, true} <- {:enabled, MediaProxy.preview_enabled?()},
+ {:ok, url} <- MediaProxy.decode_url(sig64, url64),
+ :ok <- MediaProxy.verify_request_path_and_url(conn, url) do
+ handle_preview(conn, url)
+ else
+ {:enabled, false} ->
+ send_resp(conn, 404, Plug.Conn.Status.reason_phrase(404))
- if filename && does_not_match(path, filename) do
- {:wrong_filename, filename}
+ {:error, :invalid_signature} ->
+ send_resp(conn, 403, Plug.Conn.Status.reason_phrase(403))
+
+ {:wrong_filename, filename} ->
+ redirect(conn, external: MediaProxy.build_preview_url(sig64, url64, filename))
+ end
+ end
+
+ defp handle_preview(conn, url) do
+ with {:ok, %{status: status} = head_response} when status in 200..299 <-
+ Tesla.head(url, opts: [adapter: [timeout: preview_head_request_timeout()]]) do
+ content_type = Tesla.get_header(head_response, "content-type")
+ handle_preview(content_type, conn, url)
else
- :ok
+ {_, %{status: status}} ->
+ send_resp(conn, :failed_dependency, "Can't fetch HTTP headers (HTTP #{status}).")
+
+ {:error, :recv_response_timeout} ->
+ send_resp(conn, :failed_dependency, "HEAD request timeout.")
+
+ _ ->
+ send_resp(conn, :failed_dependency, "Can't fetch HTTP headers.")
end
end
- def filename_matches(_, _, _), do: :ok
+ defp thumbnail_max_dimensions(params) do
+ config = Config.get([:media_preview_proxy], [])
+
+ thumbnail_max_width =
+ if w = params["thumbnail_max_width"] do
+ String.to_integer(w)
+ else
+ Keyword.fetch!(config, :thumbnail_max_width)
+ end
+
+ thumbnail_max_height =
+ if h = params["thumbnail_max_height"] do
+ String.to_integer(h)
+ else
+ Keyword.fetch!(config, :thumbnail_max_height)
+ end
+
+ {thumbnail_max_width, thumbnail_max_height}
+ end
+
+ defp thumbnail_binary(url, body, params) do
+ {thumbnail_max_width, thumbnail_max_height} = thumbnail_max_dimensions(params)
+
+ with true <- Config.get([:media_preview_proxy, :enable_eimp]),
+ {:ok, [type: image_type, width: source_width, height: source_height]} <-
+ :eimp.identify(body),
+ scale_factor <-
+ Enum.max([source_width / thumbnail_max_width, source_height / thumbnail_max_height]),
+ {:ok, thumbnail_binary} =
+ :eimp.convert(body, image_type, [
+ {:scale, {round(source_width / scale_factor), round(source_height / scale_factor)}}
+ ]) do
+ {:ok, thumbnail_binary}
+ else
+ _ ->
+ mogrify_dimensions = "#{thumbnail_max_width}x#{thumbnail_max_height}"
+
+ with {:ok, path} <- MogrifyHelper.store_as_temporary_file(url, body),
+ %Mogrify.Image{} <-
+ MogrifyHelper.in_place_resize_to_limit(path, mogrify_dimensions),
+ {:ok, thumbnail_binary} <- File.read(path),
+ _ <- File.rm(path) do
+ {:ok, thumbnail_binary}
+ else
+ _ -> :error
+ end
+ end
+ end
+
+ defp handle_preview("image/" <> _ = content_type, %{params: params} = conn, url) do
+ with {:ok, %{status: status, body: image_contents}} when status in 200..299 <-
+ url
+ |> MediaProxy.url()
+ |> Tesla.get(opts: [adapter: [timeout: preview_timeout()]]),
+ {:ok, thumbnail_binary} <- thumbnail_binary(url, image_contents, params) do
+ conn
+ |> put_resp_header("content-type", content_type)
+ |> send_resp(200, thumbnail_binary)
+ else
+ {_, %{status: _}} ->
+ send_resp(conn, :failed_dependency, "Can't fetch the image.")
+
+ {:error, :recv_response_timeout} ->
+ send_resp(conn, :failed_dependency, "Downstream timeout.")
+
+ _ ->
+ send_resp(conn, :failed_dependency, "Can't handle image preview.")
+ end
+ end
+
+ defp handle_preview(content_type, conn, _url) do
+ send_resp(conn, :unprocessable_entity, "Unsupported content type: #{content_type}.")
+ end
+
+ defp preview_head_request_timeout do
+ Config.get([:media_preview_proxy, :proxy_opts, :head_request_max_read_duration]) ||
+ preview_timeout()
+ end
- defp does_not_match(path, filename) do
- basename = Path.basename(path)
- basename != filename and URI.decode(basename) != filename and URI.encode(basename) != filename
+ defp preview_timeout do
+ Config.get([:media_preview_proxy, :proxy_opts, :max_read_duration]) ||
+ Config.get([:media_proxy, :proxy_opts, :max_read_duration]) ||
+ ReverseProxy.max_read_duration_default()
end
end
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index 7a171f9fb..6fb47029a 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -663,6 +663,8 @@ defmodule Pleroma.Web.Router do
end
scope "/proxy/", Pleroma.Web.MediaProxy do
+ get("/preview/:sig/:url", MediaProxyController, :preview)
+ get("/preview/:sig/:url/:filename", MediaProxyController, :preview)
get("/:sig/:url", MediaProxyController, :remote)
get("/:sig/:url/:filename", MediaProxyController, :remote)
end
diff --git a/mix.exs b/mix.exs
index b8e663a03..9ace55eff 100644
--- a/mix.exs
+++ b/mix.exs
@@ -146,6 +146,7 @@ defmodule Pleroma.Mixfile do
github: "ninenines/gun", ref: "e1a69b36b180a574c0ac314ced9613fdd52312cc", override: true},
{:jason, "~> 1.0"},
{:mogrify, "~> 0.6.1"},
+ {:eimp, "~> 1.0.14"},
{:ex_aws, "~> 2.1"},
{:ex_aws_s3, "~> 2.0"},
{:sweet_xml, "~> 0.6.6"},
diff --git a/mix.lock b/mix.lock
index 955b2bb37..ebd0cbdf5 100644
--- a/mix.lock
+++ b/mix.lock
@@ -30,6 +30,7 @@
"ecto": {:hex, :ecto, "3.4.0", "a7a83ab8359bf816ce729e5e65981ce25b9fc5adfc89c2ea3980f4fed0bfd7c1", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "5eed18252f5b5bbadec56a24112b531343507dbe046273133176b12190ce19cc"},
"ecto_enum": {:hex, :ecto_enum, "1.4.0", "d14b00e04b974afc69c251632d1e49594d899067ee2b376277efd8233027aec8", [:mix], [{:ecto, ">= 3.0.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "> 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:mariaex, ">= 0.0.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "8fb55c087181c2b15eee406519dc22578fa60dd82c088be376d0010172764ee4"},
"ecto_sql": {:hex, :ecto_sql, "3.3.4", "aa18af12eb875fbcda2f75e608b3bd534ebf020fc4f6448e4672fcdcbb081244", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.4 or ~> 3.3.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.3.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5eccbdbf92e3c6f213007a82d5dbba4cd9bb659d1a21331f89f408e4c0efd7a8"},
+ "eimp": {:hex, :eimp, "1.0.14", "fc297f0c7e2700457a95a60c7010a5f1dcb768a083b6d53f49cd94ab95a28f22", [:rebar3], [{:p1_utils, "1.0.18", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "501133f3112079b92d9e22da8b88bf4f0e13d4d67ae9c15c42c30bd25ceb83b6"},
"elixir_make": {:hex, :elixir_make, "0.6.0", "38349f3e29aff4864352084fc736fa7fa0f2995a819a737554f7ebd28b85aaab", [:mix], [], "hexpm", "d522695b93b7f0b4c0fcb2dfe73a6b905b1c301226a5a55cb42e5b14d509e050"},
"esshd": {:hex, :esshd, "0.1.1", "d4dd4c46698093a40a56afecce8a46e246eb35463c457c246dacba2e056f31b5", [:mix], [], "hexpm", "d73e341e3009d390aa36387dc8862860bf9f874c94d9fd92ade2926376f49981"},
"eternal": {:hex, :eternal, "1.2.1", "d5b6b2499ba876c57be2581b5b999ee9bdf861c647401066d3eeed111d096bc4", [:mix], [], "hexpm", "b14f1dc204321429479c569cfbe8fb287541184ed040956c8862cb7a677b8406"},
@@ -77,6 +78,7 @@
"nodex": {:git, "https://git.pleroma.social/pleroma/nodex", "cb6730f943cfc6aad674c92161be23a8411f15d1", [ref: "cb6730f943cfc6aad674c92161be23a8411f15d1"]},
"oban": {:hex, :oban, "1.2.0", "7cca94d341be43d220571e28f69131c4afc21095b25257397f50973d3fc59b07", [:mix], [{:ecto_sql, "~> 3.1", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.14", [hex: :postgrex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ba5f8b3f7d76967b3e23cf8014f6a13e4ccb33431e4808f036709a7f822362ee"},
"open_api_spex": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/open_api_spex.git", "f296ac0924ba3cf79c7a588c4c252889df4c2edd", [ref: "f296ac0924ba3cf79c7a588c4c252889df4c2edd"]},
+ "p1_utils": {:hex, :p1_utils, "1.0.18", "3fe224de5b2e190d730a3c5da9d6e8540c96484cf4b4692921d1e28f0c32b01c", [:rebar3], [], "hexpm", "1fc8773a71a15553b179c986b22fbeead19b28fe486c332d4929700ffeb71f88"},
"parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm", "17ef63abde837ad30680ea7f857dd9e7ced9476cdd7b0394432af4bfc241b960"},
"pbkdf2_elixir": {:hex, :pbkdf2_elixir, "1.2.1", "9cbe354b58121075bd20eb83076900a3832324b7dd171a6895fab57b6bb2752c", [:mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}], "hexpm", "d3b40a4a4630f0b442f19eca891fcfeeee4c40871936fed2f68e1c4faa30481f"},
"phoenix": {:hex, :phoenix, "1.4.13", "67271ad69b51f3719354604f4a3f968f83aa61c19199343656c9caee057ff3b8", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.8.1 or ~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ab765a0feddb81fc62e2116c827b5f068df85159c162bee760745276ad7ddc1b"},
diff --git a/test/web/media_proxy/media_proxy_test.exs b/test/web/media_proxy/media_proxy_test.exs
index 69c2d5dae..ac5d8fd32 100644
--- a/test/web/media_proxy/media_proxy_test.exs
+++ b/test/web/media_proxy/media_proxy_test.exs
@@ -5,42 +5,44 @@
defmodule Pleroma.Web.MediaProxyTest do
use ExUnit.Case
use Pleroma.Tests.Helpers
- import Pleroma.Web.MediaProxy
- alias Pleroma.Web.MediaProxy.MediaProxyController
- setup do: clear_config([:media_proxy, :enabled])
- setup do: clear_config(Pleroma.Upload)
+ alias Pleroma.Config
+ alias Pleroma.Web.Endpoint
+ alias Pleroma.Web.MediaProxy
+
+ defp decode_result(encoded) do
+ [_, "proxy", sig, base64 | _] = URI.parse(encoded).path |> String.split("/")
+ {:ok, decoded} = MediaProxy.decode_url(sig, base64)
+ decoded
+ end
describe "when enabled" do
- setup do
- Pleroma.Config.put([:media_proxy, :enabled], true)
- :ok
- end
+ setup do: clear_config([:media_proxy, :enabled], true)
test "ignores invalid url" do
- assert url(nil) == nil
- assert url("") == nil
+ assert MediaProxy.url(nil) == nil
+ assert MediaProxy.url("") == nil
end
test "ignores relative url" do
- assert url("/local") == "/local"
- assert url("/") == "/"
+ assert MediaProxy.url("/local") == "/local"
+ assert MediaProxy.url("/") == "/"
end
test "ignores local url" do
- local_url = Pleroma.Web.Endpoint.url() <> "/hello"
- local_root = Pleroma.Web.Endpoint.url()
- assert url(local_url) == local_url
- assert url(local_root) == local_root
+ local_url = Endpoint.url() <> "/hello"
+ local_root = Endpoint.url()
+ assert MediaProxy.url(local_url) == local_url
+ assert MediaProxy.url(local_root) == local_root
end
test "encodes and decodes URL" do
url = "https://pleroma.soykaf.com/static/logo.png"
- encoded = url(url)
+ encoded = MediaProxy.url(url)
assert String.starts_with?(
encoded,
- Pleroma.Config.get([:media_proxy, :base_url], Pleroma.Web.base_url())
+ Config.get([:media_proxy, :base_url], Pleroma.Web.base_url())
)
assert String.ends_with?(encoded, "/logo.png")
@@ -50,94 +52,107 @@ defmodule Pleroma.Web.MediaProxyTest do
test "encodes and decodes URL without a path" do
url = "https://pleroma.soykaf.com"
- encoded = url(url)
+ encoded = MediaProxy.url(url)
assert decode_result(encoded) == url
end
test "encodes and decodes URL without an extension" do
url = "https://pleroma.soykaf.com/path/"
- encoded = url(url)
+ encoded = MediaProxy.url(url)
assert String.ends_with?(encoded, "/path")
assert decode_result(encoded) == url
end
test "encodes and decodes URL and ignores query params for the path" do
url = "https://pleroma.soykaf.com/static/logo.png?93939393939&bunny=true"
- encoded = url(url)
+ encoded = MediaProxy.url(url)
assert String.ends_with?(encoded, "/logo.png")
assert decode_result(encoded) == url
end
test "validates signature" do
- secret_key_base = Pleroma.Config.get([Pleroma.Web.Endpoint, :secret_key_base])
+ secret_key_base = Config.get([Endpoint, :secret_key_base])
+ clear_config([Endpoint, :secret_key_base], secret_key_base)
- on_exit(fn ->
- Pleroma.Config.put([Pleroma.Web.Endpoint, :secret_key_base], secret_key_base)
- end)
+ encoded = MediaProxy.url("https://pleroma.social")
- encoded = url("https://pleroma.social")
-
- Pleroma.Config.put(
- [Pleroma.Web.Endpoint, :secret_key_base],
+ Config.put(
+ [Endpoint, :secret_key_base],
"00000000000000000000000000000000000000000000000"
)
[_, "proxy", sig, base64 | _] = URI.parse(encoded).path |> String.split("/")
- assert decode_url(sig, base64) == {:error, :invalid_signature}
+ assert MediaProxy.decode_url(sig, base64) == {:error, :invalid_signature}
end
- test "filename_matches preserves the encoded or decoded path" do
- assert MediaProxyController.filename_matches(
- %{"filename" => "/Hello world.jpg"},
- "/Hello world.jpg",
- "http://pleroma.social/Hello world.jpg"
- ) == :ok
+ def test_verify_request_path_and_url(request_path, url, expected_result) do
+ assert MediaProxy.verify_request_path_and_url(request_path, url) == expected_result
- assert MediaProxyController.filename_matches(
- %{"filename" => "/Hello%20world.jpg"},
- "/Hello%20world.jpg",
- "http://pleroma.social/Hello%20world.jpg"
+ assert MediaProxy.verify_request_path_and_url(
+ %Plug.Conn{
+ params: %{"filename" => Path.basename(request_path)},
+ request_path: request_path
+ },
+ url
+ ) == expected_result
+ end
+
+ test "if first arg of `verify_request_path_and_url/2` is a Plug.Conn without \"filename\" " <>
+ "parameter, `verify_request_path_and_url/2` returns :ok " do
+ assert MediaProxy.verify_request_path_and_url(
+ %Plug.Conn{params: %{}, request_path: "/some/path"},
+ "https://instance.com/file.jpg"
) == :ok
- assert MediaProxyController.filename_matches(
- %{"filename" => "/my%2Flong%2Furl%2F2019%2F07%2FS.jpg"},
- "/my%2Flong%2Furl%2F2019%2F07%2FS.jpg",
- "http://pleroma.social/my%2Flong%2Furl%2F2019%2F07%2FS.jpg"
+ assert MediaProxy.verify_request_path_and_url(
+ %Plug.Conn{params: %{}, request_path: "/path/to/file.jpg"},
+ "https://instance.com/file.jpg"
) == :ok
+ end
+
+ test "`verify_request_path_and_url/2` preserves the encoded or decoded path" do
+ test_verify_request_path_and_url(
+ "/Hello world.jpg",
+ "http://pleroma.social/Hello world.jpg",
+ :ok
+ )
+
+ test_verify_request_path_and_url(
+ "/Hello%20world.jpg",
+ "http://pleroma.social/Hello%20world.jpg",
+ :ok
+ )
- assert MediaProxyController.filename_matches(
- %{"filename" => "/my%2Flong%2Furl%2F2019%2F07%2FS.jp"},
- "/my%2Flong%2Furl%2F2019%2F07%2FS.jp",
- "http://pleroma.social/my%2Flong%2Furl%2F2019%2F07%2FS.jpg"
- ) == {:wrong_filename, "my%2Flong%2Furl%2F2019%2F07%2FS.jpg"}
+ test_verify_request_path_and_url(
+ "/my%2Flong%2Furl%2F2019%2F07%2FS.jpg",
+ "http://pleroma.social/my%2Flong%2Furl%2F2019%2F07%2FS.jpg",
+ :ok
+ )
+
+ test_verify_request_path_and_url(
+ "/my%2Flong%2Furl%2F2019%2F07%2FS",
+ "http://pleroma.social/my%2Flong%2Furl%2F2019%2F07%2FS.jpg",
+ {:wrong_filename, "my%2Flong%2Furl%2F2019%2F07%2FS.jpg"}
+ )
end
test "encoded url are tried to match for proxy as `conn.request_path` encodes the url" do
# conn.request_path will return encoded url
request_path = "/ANALYSE-DAI-_-LE-STABLECOIN-100-D%C3%89CENTRALIS%C3%89-BQ.jpg"
- assert MediaProxyController.filename_matches(
- true,
+ assert MediaProxy.verify_request_path_and_url(
request_path,
"https://mydomain.com/uploads/2019/07/ANALYSE-DAI-_-LE-STABLECOIN-100-DÉCENTRALISÉ-BQ.jpg"
) == :ok
end
test "uses the configured base_url" do
- base_url = Pleroma.Config.get([:media_proxy, :base_url])
-
- if base_url do
- on_exit(fn ->
- Pleroma.Config.put([:media_proxy, :base_url], base_url)
- end)
- end
-
- Pleroma.Config.put([:media_proxy, :base_url], "https://cache.pleroma.social")
+ clear_config([:media_proxy, :base_url], "https://cache.pleroma.social")
url = "https://pleroma.soykaf.com/static/logo.png"
- encoded = url(url)
+ encoded = MediaProxy.url(url)
- assert String.starts_with?(encoded, Pleroma.Config.get([:media_proxy, :base_url]))
+ assert String.starts_with?(encoded, Config.get([:media_proxy, :base_url]))
end
# Some sites expect ASCII encoded characters in the URL to be preserved even if
@@ -148,7 +163,7 @@ defmodule Pleroma.Web.MediaProxyTest do
url =
"https://pleroma.com/%20/%21/%22/%23/%24/%25/%26/%27/%28/%29/%2A/%2B/%2C/%2D/%2E/%2F/%30/%31/%32/%33/%34/%35/%36/%37/%38/%39/%3A/%3B/%3C/%3D/%3E/%3F/%40/%41/%42/%43/%44/%45/%46/%47/%48/%49/%4A/%4B/%4C/%4D/%4E/%4F/%50/%51/%52/%53/%54/%55/%56/%57/%58/%59/%5A/%5B/%5C/%5D/%5E/%5F/%60/%61/%62/%63/%64/%65/%66/%67/%68/%69/%6A/%6B/%6C/%6D/%6E/%6F/%70/%71/%72/%73/%74/%75/%76/%77/%78/%79/%7A/%7B/%7C/%7D/%7E/%7F/%80/%81/%82/%83/%84/%85/%86/%87/%88/%89/%8A/%8B/%8C/%8D/%8E/%8F/%90/%91/%92/%93/%94/%95/%96/%97/%98/%99/%9A/%9B/%9C/%9D/%9E/%9F/%C2%A0/%A1/%A2/%A3/%A4/%A5/%A6/%A7/%A8/%A9/%AA/%AB/%AC/%C2%AD/%AE/%AF/%B0/%B1/%B2/%B3/%B4/%B5/%B6/%B7/%B8/%B9/%BA/%BB/%BC/%BD/%BE/%BF/%C0/%C1/%C2/%C3/%C4/%C5/%C6/%C7/%C8/%C9/%CA/%CB/%CC/%CD/%CE/%CF/%D0/%D1/%D2/%D3/%D4/%D5/%D6/%D7/%D8/%D9/%DA/%DB/%DC/%DD/%DE/%DF/%E0/%E1/%E2/%E3/%E4/%E5/%E6/%E7/%E8/%E9/%EA/%EB/%EC/%ED/%EE/%EF/%F0/%F1/%F2/%F3/%F4/%F5/%F6/%F7/%F8/%F9/%FA/%FB/%FC/%FD/%FE/%FF"
- encoded = url(url)
+ encoded = MediaProxy.url(url)
assert decode_result(encoded) == url
end
@@ -159,77 +174,55 @@ defmodule Pleroma.Web.MediaProxyTest do
url =
"https://pleroma.com/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890-._~:/?#[]@!$&'()*+,;=|^`{}"
- encoded = url(url)
+ encoded = MediaProxy.url(url)
assert decode_result(encoded) == url
end
test "preserve unicode characters" do
url = "https://ko.wikipedia.org/wiki/위키백과:대문"
- encoded = url(url)
+ encoded = MediaProxy.url(url)
assert decode_result(encoded) == url
end
end
describe "when disabled" do
- setup do
- enabled = Pleroma.Config.get([:media_proxy, :enabled])
-
- if enabled do
- Pleroma.Config.put([:media_proxy, :enabled], false)
-
- on_exit(fn ->
- Pleroma.Config.put([:media_proxy, :enabled], enabled)
- :ok
- end)
- end
-
- :ok
- end
+ setup do: clear_config([:media_proxy, :enabled], false)
test "does not encode remote urls" do
- assert url("https://google.fr") == "https://google.fr"
+ assert MediaProxy.url("https://google.fr") == "https://google.fr"
end
end
- defp decode_result(encoded) do
- [_, "proxy", sig, base64 | _] = URI.parse(encoded).path |> String.split("/")
- {:ok, decoded} = decode_url(sig, base64)
- decoded
- end
-
describe "whitelist" do
- setup do
- Pleroma.Config.put([:media_proxy, :enabled], true)
- :ok
- end
+ setup do: clear_config([:media_proxy, :enabled], true)
test "mediaproxy whitelist" do
- Pleroma.Config.put([:media_proxy, :whitelist], ["google.com", "feld.me"])
+ clear_config([:media_proxy, :whitelist], ["google.com", "feld.me"])
url = "https://feld.me/foo.png"
- unencoded = url(url)
+ unencoded = MediaProxy.url(url)
assert unencoded == url
end
test "does not change whitelisted urls" do
- Pleroma.Config.put([:media_proxy, :whitelist], ["mycdn.akamai.com"])
- Pleroma.Config.put([:media_proxy, :base_url], "https://cache.pleroma.social")
+ clear_config([:media_proxy, :whitelist], ["mycdn.akamai.com"])
+ clear_config([:media_proxy, :base_url], "https://cache.pleroma.social")
media_url = "https://mycdn.akamai.com"
url = "#{media_url}/static/logo.png"
- encoded = url(url)
+ encoded = MediaProxy.url(url)
assert String.starts_with?(encoded, media_url)
end
test "ensure Pleroma.Upload base_url is always whitelisted" do
media_url = "https://media.pleroma.social"
- Pleroma.Config.put([Pleroma.Upload, :base_url], media_url)
+ clear_config([Pleroma.Upload, :base_url], media_url)
url = "#{media_url}/static/logo.png"
- encoded = url(url)
+ encoded = MediaProxy.url(url)
assert String.starts_with?(encoded, media_url)
end