From 6b8ffe36a0761492450b214ab11a617cb4fd2b8c Mon Sep 17 00:00:00 2001 From: Oneric Date: Tue, 2 Apr 2024 18:38:44 +0200 Subject: [PATCH 1/2] Add support for preset MIME types It is not always possible to express custom types with a static filename -> type mapping. Currently Plug.Static will (if the file exists) always overwrite the header and halt the connection making it impossible to preset or later overwrite it based on custom logic. Thus introduce an opt-out mode to preserve whatever `Content-Type` was set or not set before. --- lib/plug/static.ex | 18 ++++++++++++++---- test/plug/static_test.exs | 22 ++++++++++++++++++++++ 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/lib/plug/static.ex b/lib/plug/static.ex index fd9804ba..346da454 100644 --- a/lib/plug/static.ex +++ b/lib/plug/static.ex @@ -100,9 +100,13 @@ defmodule Plug.Static do an enum of key-value pairs or a `{module, function, args}` to return an enum. The `conn` will be passed to the function, as well as the `args`. - * `:content_types` - custom MIME type mapping. As a map with filename as key - and content type as value. For example: + * `:content_types` - controls custom MIME type mapping. + Can be either a map with filename as key and content type as value to override + the default type for matching filenames. For example: `content_types: %{"apple-app-site-association" => "application/json"}`. + Or the value `false` to opt out of setting the header at all. The latter + can be used to set the header based on custom logic before calling this plug. + Defaults to an empty map `%{}`. ## Examples @@ -218,6 +222,13 @@ defmodule Plug.Static do h in full or (prefix != [] and match?({0, _}, :binary.match(h, prefix))) end + defp maybe_put_content_type(conn, false, _), do: conn + + defp maybe_put_content_type(conn, types, filename) do + content_type = Map.get(types, filename) || MIME.from_path(filename) + put_resp_header(conn, "content-type", content_type) + end + defp serve_static({content_encoding, file_info, path}, conn, segments, range, options) do %{ qs_cache: qs_cache, @@ -230,10 +241,9 @@ defmodule Plug.Static do case put_cache_header(conn, qs_cache, et_cache, et_generation, file_info, path) do {:stale, conn} -> filename = List.last(segments) - content_type = Map.get(types, filename) || MIME.from_path(filename) conn - |> put_resp_header("content-type", content_type) + |> maybe_put_content_type(types, filename) |> put_resp_header("accept-ranges", "bytes") |> maybe_add_encoding(content_encoding) |> merge_headers(headers) diff --git a/test/plug/static_test.exs b/test/plug/static_test.exs index 2a3ed8cb..ce89a145 100644 --- a/test/plug/static_test.exs +++ b/test/plug/static_test.exs @@ -35,6 +35,28 @@ defmodule Plug.StaticTest do assert get_resp_header(conn, "content-type") == ["application/vnd.manifest+json"] end + test "overwrites Content-Type by default" do + conn = + conn(:get, "/public/fixtures/static.txt") + |> put_resp_header("content-type", "application/octet-stream") + |> call() + + assert conn.status == 200 + assert conn.resp_body == "HELLO" + assert get_resp_header(conn, "content-type") == ["text/plain"] + end + + test "preserves original Content-Type if requested" do + conn = + conn(:get, "/public/fixtures/static.txt") + |> put_resp_header("content-type", "application/octet-stream") + |> call(content_types: false) + + assert conn.status == 200 + assert conn.resp_body == "HELLO" + assert get_resp_header(conn, "content-type") == ["application/octet-stream"] + end + test "serves the file with a urlencoded filename" do conn = call(conn(:get, "/public/fixtures/static%20with%20spaces.txt")) assert conn.status == 200 From daa69b08349453e1434125466c837c8853f57ff6 Mon Sep 17 00:00:00 2001 From: Andrea Leopardi Date: Wed, 3 Apr 2024 10:01:41 +0200 Subject: [PATCH 2/2] Apply suggestions from code review --- lib/plug/static.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/plug/static.ex b/lib/plug/static.ex index 346da454..7fc2d495 100644 --- a/lib/plug/static.ex +++ b/lib/plug/static.ex @@ -101,10 +101,10 @@ defmodule Plug.Static do `conn` will be passed to the function, as well as the `args`. * `:content_types` - controls custom MIME type mapping. - Can be either a map with filename as key and content type as value to override + It can be a map with filename as key and content type as value to override the default type for matching filenames. For example: `content_types: %{"apple-app-site-association" => "application/json"}`. - Or the value `false` to opt out of setting the header at all. The latter + Alternatively, it can be the value `false` to opt out of setting the header at all. The latter can be used to set the header based on custom logic before calling this plug. Defaults to an empty map `%{}`.