From ae1148df4147cafd3be0bc7b36524fe22afe8439 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Pr=C3=A9vost?= Date: Fri, 2 Feb 2024 10:32:44 -0500 Subject: [PATCH] Fix svg+xml content type header --- lib/plug_image_processing/sources/url.ex | 84 ++++++++++++++++-------- lib/plug_image_processing/web.ex | 1 + test/plug_image_processing/web_test.exs | 11 +++- 3 files changed, 66 insertions(+), 30 deletions(-) diff --git a/lib/plug_image_processing/sources/url.ex b/lib/plug_image_processing/sources/url.ex index 794cd12..22b2492 100644 --- a/lib/plug_image_processing/sources/url.ex +++ b/lib/plug_image_processing/sources/url.ex @@ -5,7 +5,19 @@ defmodule PlugImageProcessing.Sources.URL do alias PlugImageProcessing.Options - @valid_types ~w(jpg jpeg png webp gif svg svg+xml) + @types_extensions_mapping %{ + "jpg" => ".jpg", + "jpeg" => ".jpg", + "png" => ".png", + "webp" => ".webp", + "gif" => ".gif", + "svg" => ".svg", + "svg+xml" => ".svg" + } + + @valid_types Map.keys(@types_extensions_mapping) + @valid_extensions Map.values(@types_extensions_mapping) + @extensions_types_mapping Map.new(@types_extensions_mapping, fn {k, v} -> {v, k} end) def fetch_body(source, http_client_timeout, http_client_max_length, http_client, http_client_cache) do metadata = %{uri: source.uri} @@ -58,27 +70,35 @@ defmodule PlugImageProcessing.Sources.URL do end end - defp get_file_suffix(source, content_type) do - # Find the type in the source response content-type header - type = String.trim_leading(content_type, "image/") + defp get_file_suffix_from_http_header(content_type) do + content_type = String.trim_leading(content_type, "image/") - type = - if type in @valid_types do - "." <> type - else - nil - end + if content_type in @valid_types do + content_type + end + end - # Find the type in the client provided "type" query param - type = - if source.params["type"] in @valid_types do - "." <> source.params["type"] - else - type - end + defp get_file_suffix_from_query_params(params) do + if params["type"] in @valid_types do + params["type"] + end + end - # Fallback to the extension name of the "url" query param - type = type || (source.uri.path && Path.extname(source.uri.path)) + defp get_file_suffix_from_uri(uri) do + case uri.path && Path.extname(uri.path) do + "." <> content_type -> content_type + _ -> nil + end + end + + defp get_file_suffix(source, content_type) do + image_type = get_file_suffix_from_query_params(source.params) + # If "type" query param is not found or is invalid, fallback to HTTP header + image_type = image_type || get_file_suffix_from_http_header(content_type) + # If HTTP header "Content-Type" is not found or is invalid, fallback to source uri + image_type = image_type || get_file_suffix_from_uri(source.uri) + + type = Map.get(@types_extensions_mapping, image_type) case type do ".gif" -> @@ -98,16 +118,22 @@ defmodule PlugImageProcessing.Sources.URL do {"image/png", ".png" <> options} - "." <> type_name -> - options = - [ - {"Q", Options.cast_integer(source.params["quality"])}, - {"strip", Options.cast_boolean(source.params["stripmeta"])} - ] - |> Options.build() - |> Options.encode_suffix() - - {"image/#{type_name}", type <> options} + extension_name when extension_name in @valid_extensions -> + content_type = Map.get(@extensions_types_mapping, extension_name) + + if content_type do + options = + [ + {"Q", Options.cast_integer(source.params["quality"])}, + {"strip", Options.cast_boolean(source.params["stripmeta"])} + ] + |> Options.build() + |> Options.encode_suffix() + + {"image/#{content_type}", type <> options} + else + :invalid_file_type + end _ -> :invalid_file_type diff --git a/lib/plug_image_processing/web.ex b/lib/plug_image_processing/web.ex index c936b3d..d1f98c6 100644 --- a/lib/plug_image_processing/web.ex +++ b/lib/plug_image_processing/web.ex @@ -6,6 +6,7 @@ defmodule PlugImageProcessing.Web do plug(:cast_config) plug(:assign_operation_name) plug(:run_middlewares) + plug(:fetch_query_params) plug(:action) def call(conn, opts) do diff --git a/test/plug_image_processing/web_test.exs b/test/plug_image_processing/web_test.exs index e9494c5..bb39a45 100644 --- a/test/plug_image_processing/web_test.exs +++ b/test/plug_image_processing/web_test.exs @@ -13,7 +13,8 @@ defmodule PlugImageProcessing.WebTest do @behaviour PlugImageProcessing.Sources.HTTPClient def get("http://example.org/valid.jpg", _), do: {:ok, @image, [{"Content-type", "image/jpg"}]} def get("http://example.org/valid.gif", _), do: {:ok, @gif_image, [{"Content-type", "image/gif"}]} - def get("http://example.org/valid.svg", _), do: {:ok, @svg_image, [{"Content-type", "image/svg_xml"}]} + def get("http://example.org/valid.svg", _), do: {:ok, @svg_image, [{"Content-type", "image/svg"}]} + def get("http://example.org/valid-xml.svg", _), do: {:ok, @svg_image, [{"Content-type", "image/svg+xml"}]} def get("http://example.org/retry.jpg", _), do: {:ok, @image, [{"Content-type", "image/jpg"}]} def get("http://example.org/404.jpg", _), do: {:error, "404 Not found"} @@ -144,6 +145,14 @@ defmodule PlugImageProcessing.WebTest do assert conn.status === 200 end + test "echo svg+xml", %{config: config} do + plug_opts = Web.init(config) + conn = conn(:get, "/imageproxy", %{url: "http://example.org/valid-xml.svg"}) + conn = Web.call(conn, plug_opts) + + assert conn.status === 200 + end + test "echo redirect gif", %{config: config} do config = Keyword.merge(config, source_url_redirect_operations: [""]) plug_opts = Web.init(config)