X-Git-Url: http://git.squeep.com/?a=blobdiff_plain;f=lib%2Fpleroma%2Fweb%2Fmedia_proxy%2Fmedia_proxy_controller.ex;h=469fbae5962e07572d54e400e983c652f377a2c7;hb=c3b02341bf4ab610e9425d6811dca057e9f811a4;hp=4657a4383563802f19fef4a25730ac6e121ebf82;hpb=1898054da4bcccb25e0bcd9d6463af38ced43351;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 4657a4383..469fbae59 100644 --- a/lib/pleroma/web/media_proxy/media_proxy_controller.ex +++ b/lib/pleroma/web/media_proxy/media_proxy_controller.ex @@ -5,19 +5,22 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do use Pleroma.Web, :controller + alias Pleroma.Config + alias Pleroma.Helpers.MediaHelper 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)) + {_, false} <- {:in_banned_urls, MediaProxy.in_banned_urls(url)}, + :ok <- MediaProxy.verify_request_path_and_url(conn, url) do + ReverseProxy.call(conn, url, media_proxy_opts()) else - false -> + {:enabled, false} -> + send_resp(conn, 404, Plug.Conn.Status.reason_phrase(404)) + + {:in_banned_urls, true} -> send_resp(conn, 404, Plug.Conn.Status.reason_phrase(404)) {:error, :invalid_signature} -> @@ -28,20 +31,154 @@ 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) 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 + media_proxy_url = MediaProxy.url(url) - if filename && does_not_match(path, filename) do - {:wrong_filename, filename} + with {:ok, %{status: status} = head_response} when status in 200..299 <- + Pleroma.HTTP.request("head", media_proxy_url, [], [], adapter: [pool: :preview]) do + content_type = Tesla.get_header(head_response, "content-type") + handle_preview(content_type, conn, media_proxy_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 handle_preview( + "image/" <> _ = _content_type, + %{params: %{"output_format" => "jpeg"}} = conn, + media_proxy_url + ) do + handle_jpeg_preview(conn, media_proxy_url) + end + + defp handle_preview("image/gif" = _content_type, conn, media_proxy_url) do + redirect(conn, external: media_proxy_url) + end + + defp handle_preview("image/png" <> _ = _content_type, conn, media_proxy_url) do + handle_png_preview(conn, media_proxy_url) + end + + defp handle_preview("image/" <> _ = _content_type, conn, media_proxy_url) do + handle_jpeg_preview(conn, media_proxy_url) + end + + defp handle_preview("video/" <> _ = _content_type, conn, media_proxy_url) do + handle_video_preview(conn, media_proxy_url) + end + + defp handle_preview(content_type, conn, _media_proxy_url) do + send_resp(conn, :unprocessable_entity, "Unsupported content type: #{content_type}.") + end + + defp handle_png_preview(%{params: params} = conn, media_proxy_url) do + quality = Config.get!([:media_preview_proxy, :image_quality]) + + with {thumbnail_max_width, thumbnail_max_height} <- thumbnail_max_dimensions(params), + {:ok, thumbnail_binary} <- + MediaHelper.image_resize( + media_proxy_url, + %{ + max_width: thumbnail_max_width, + max_height: thumbnail_max_height, + quality: quality, + format: "png" + } + ) do + conn + |> put_preview_response_headers(["image/png", "preview.png"]) + |> send_resp(200, thumbnail_binary) + else + _ -> + send_resp(conn, :failed_dependency, "Can't handle preview.") + end + end + + defp handle_jpeg_preview(%{params: params} = conn, media_proxy_url) do + quality = Config.get!([:media_preview_proxy, :image_quality]) + + with {thumbnail_max_width, thumbnail_max_height} <- thumbnail_max_dimensions(params), + {:ok, thumbnail_binary} <- + MediaHelper.image_resize( + media_proxy_url, + %{max_width: thumbnail_max_width, max_height: thumbnail_max_height, quality: quality} + ) do + conn + |> put_preview_response_headers() + |> send_resp(200, thumbnail_binary) + else + _ -> + send_resp(conn, :failed_dependency, "Can't handle preview.") + end + end + + defp handle_video_preview(conn, media_proxy_url) do + with {:ok, thumbnail_binary} <- + MediaHelper.video_framegrab(media_proxy_url) do + conn + |> put_preview_response_headers() + |> send_resp(200, thumbnail_binary) + else + _ -> + send_resp(conn, :failed_dependency, "Can't handle preview.") + end + end + + defp put_preview_response_headers( + conn, + [content_type, filename] = _content_info \\ ["image/jpeg", "preview.jpg"] + ) do + conn + |> put_resp_header("content-type", content_type) + |> put_resp_header("content-disposition", "inline; filename=\"#{filename}\"") + # TODO: enable caching + |> put_resp_header("cache-control", "max-age=0, private, must-revalidate") + 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 + + 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 does_not_match(path, filename) do - basename = Path.basename(path) - basename != filename and URI.decode(basename) != filename and URI.encode(basename) != filename + defp media_proxy_opts do + Config.get([:media_proxy, :proxy_opts], []) end end