X-Git-Url: http://git.squeep.com/?a=blobdiff_plain;f=lib%2Fpleroma%2Fweb%2Fmedia_proxy%2Fmedia_proxy_controller.ex;h=8d8d073e9ba8814bef89aac76187ad2c1511e76d;hb=e4b12494d77e7a6920a408d62d5b8fe0f886a14d;hp=1a09ac62a0245d74b80c2a0d65405355618a5ae5;hpb=7605fe5065b62dcb8aaf7db07e5d168138b80d3e;p=akkoma diff --git a/lib/pleroma/web/media_proxy/media_proxy_controller.ex b/lib/pleroma/web/media_proxy/media_proxy_controller.ex index 1a09ac62a..8d8d073e9 100644 --- a/lib/pleroma/web/media_proxy/media_proxy_controller.ex +++ b/lib/pleroma/web/media_proxy/media_proxy_controller.ex @@ -4,19 +4,20 @@ 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} -> @@ -27,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)) + + {: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 + {_, %{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 + + 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 - if filename && does_not_match(path, filename) do - {:wrong_filename, filename} + 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 - :ok + _ -> + 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 - def filename_matches(_, _, _), do: :ok + 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