Plug
This commit is contained in:
parent
debcadf495
commit
91fdf2303b
133
lib/majic/plug.ex
Normal file
133
lib/majic/plug.ex
Normal file
|
@ -0,0 +1,133 @@
|
|||
if Code.ensure_loaded?(Plug) do
|
||||
defmodule Majic.PlugError do
|
||||
defexception [:message]
|
||||
end
|
||||
|
||||
defmodule Majic.Plug do
|
||||
@moduledoc """
|
||||
A `Plug` to automatically set the `content_type` of every `Plug.Upload`.
|
||||
|
||||
One of the required option of `pool`, `server` or `once` must be set.
|
||||
|
||||
Additional options:
|
||||
* `fix_extension`, default true: rewrite the user provided `filename` with a valid extension for the detected content type
|
||||
* `append_extension`, default false: append the valid extension to the previous filename, without removing the user provided extension
|
||||
|
||||
To use a gen_magic pool:
|
||||
|
||||
```
|
||||
plug Majic.Plug, pool: MyApp.MajicPool
|
||||
```
|
||||
|
||||
To use a single gen_magic server:
|
||||
|
||||
```
|
||||
plug Majic.Plug, server: MyApp.MajicServer
|
||||
```
|
||||
|
||||
To start a gen_magic process at each file (not recommended):
|
||||
|
||||
```
|
||||
plug Majic.Plug, once: true
|
||||
```
|
||||
"""
|
||||
@behaviour Plug
|
||||
|
||||
@impl Plug
|
||||
def init(opts) do
|
||||
mod =
|
||||
cond do
|
||||
Keyword.has_key?(opts, :pool) -> {Majic.Pool, Keyword.get(opts, :pool)}
|
||||
Keyword.has_key?(opts, :server) -> {Majic.Server, Keyword.get(opts, :server)}
|
||||
Keyword.has_key?(opts, :once) -> {Majic.Once, nil}
|
||||
true -> raise(Majic.PlugError, "No server/pool/once option defined")
|
||||
end
|
||||
|
||||
opts
|
||||
|> Keyword.put(:__module__, mod)
|
||||
|> Keyword.put_new(:fix_extension, true)
|
||||
|> Keyword.put_new(:append_extension, false)
|
||||
end
|
||||
|
||||
@impl Plug
|
||||
def call(%{params: params} = conn, opts) do
|
||||
%{conn | params: collect_uploads(params, opts)}
|
||||
end
|
||||
|
||||
defp collect_uploads(params, opts) do
|
||||
Enum.reduce(params, Map.new(), fn value, acc -> collect_upload(value, acc, opts) end)
|
||||
end
|
||||
|
||||
defp collect_upload({k, %{__struct__: Plug.Upload, path: path} = upload}, acc, opts) do
|
||||
case perform(Keyword.get(opts, :__module__), path) do
|
||||
{:ok, magic} ->
|
||||
Map.put(acc, k, fix_upload(upload, magic, opts))
|
||||
|
||||
{:error, error} ->
|
||||
raise(Majic.PlugError, "Failed to gen_magic: #{inspect(error)}")
|
||||
end
|
||||
end
|
||||
|
||||
defp collect_upload({k, v}, acc, opts) when is_map(v) do
|
||||
Map.put(acc, k, collect_uploads(v, opts))
|
||||
end
|
||||
|
||||
defp collect_upload({k, v}, acc, _opts) do
|
||||
Map.put(acc, k, v)
|
||||
end
|
||||
|
||||
defp perform({mod = Majic.Once, _}, path) do
|
||||
mod.perform(path)
|
||||
end
|
||||
|
||||
defp perform({mod, name}, path) do
|
||||
mod.perform(name, path)
|
||||
end
|
||||
|
||||
defp fix_upload(upload, magic, opts) do
|
||||
%{upload | content_type: magic.mime_type}
|
||||
|> fix_extension(Keyword.get(opts, :fix_extension), opts)
|
||||
end
|
||||
|
||||
defp fix_extension(upload, true, opts) do
|
||||
old_ext = String.downcase(Path.extname(upload.filename))
|
||||
extensions = MIME.extensions(upload.content_type)
|
||||
rewrite_extension(upload, old_ext, extensions, opts)
|
||||
end
|
||||
|
||||
defp fix_extension(upload, _, _) do
|
||||
upload
|
||||
end
|
||||
|
||||
defp rewrite_extension(upload, _old, [], _opts) do
|
||||
upload
|
||||
end
|
||||
|
||||
defp rewrite_extension(upload, old, [ext | _] = exts, opts) do
|
||||
if old in exts do
|
||||
upload
|
||||
else
|
||||
basename = Path.basename(upload.filename, old)
|
||||
|
||||
%{
|
||||
upload
|
||||
| filename:
|
||||
rewrite_or_append_extension(
|
||||
basename,
|
||||
old,
|
||||
ext,
|
||||
Keyword.get(opts, :append_extension)
|
||||
)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
defp rewrite_or_append_extension(basename, "." <> old, ext, true) do
|
||||
Enum.join([basename, old, ext], ".")
|
||||
end
|
||||
|
||||
defp rewrite_or_append_extension(basename, _, ext, _) do
|
||||
basename <> "." <> ext
|
||||
end
|
||||
end
|
||||
end
|
BIN
test/fixtures/cat.webp
vendored
Normal file
BIN
test/fixtures/cat.webp
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 220 KiB |
72
test/majic/plug_test.exs
Normal file
72
test/majic/plug_test.exs
Normal file
|
@ -0,0 +1,72 @@
|
|||
defmodule Majic.PlugTest do
|
||||
use ExUnit.Case, async: true
|
||||
use Plug.Test
|
||||
|
||||
defmodule TestRouter do
|
||||
use Plug.Router
|
||||
|
||||
plug :match
|
||||
plug :dispatch
|
||||
plug Plug.Parsers,
|
||||
parsers: [:urlencoded, :multipart],
|
||||
pass: ["*/*"]
|
||||
#plug Majic.Plug, once: true
|
||||
|
||||
post "/" do
|
||||
send_resp(conn, 200, "Ok")
|
||||
end
|
||||
end
|
||||
|
||||
setup_all do
|
||||
Application.ensure_all_started(:plug)
|
||||
:ok
|
||||
end
|
||||
|
||||
@router_opts TestRouter.init([])
|
||||
|
||||
test "convert uploads" do
|
||||
multipart = """
|
||||
------w58EW1cEpjzydSCq\r
|
||||
Content-Disposition: form-data; name=\"form[makefile]\"; filename*=\"utf-8''mymakefile.txt\"\r
|
||||
Content-Type: text/plain\r
|
||||
\r
|
||||
#{String.replace(File.read!("Makefile"), "\n", "\n")}\r
|
||||
------w58EW1cEpjzydSCq\r
|
||||
Content-Disposition: form-data; name=\"form[make][file]\"; filename*=\"utf-8''mymakefile.txt\"\r
|
||||
Content-Type: text/plain\r
|
||||
\r
|
||||
#{String.replace(File.read!("Makefile"), "\n", "\n")}\r
|
||||
------w58EW1cEpjzydSCq\r
|
||||
Content-Disposition: form-data; name=\"cat\"; filename*=\"utf-8''cute-cat.jpg\"\r
|
||||
Content-Type: image/jpg\r
|
||||
\r
|
||||
#{String.replace(File.read!("test/fixtures/cat.webp"), "\n", "\n")}\r
|
||||
------w58EW1cEpjzydSCq--\r
|
||||
"""
|
||||
|
||||
orig_conn = conn(:post, "/", multipart)
|
||||
|> put_req_header("content-type", "multipart/mixed; boundary=----w58EW1cEpjzydSCq")
|
||||
|> TestRouter.call(@router_opts)
|
||||
|
||||
plug = Majic.Plug.init([once: true])
|
||||
plug_no_ext = Majic.Plug.init([once: true, fix_extension: false])
|
||||
plug_append_ext = Majic.Plug.init([once: true, fix_extension: true, append_extension: true])
|
||||
|
||||
conn = Majic.Plug.call(orig_conn, plug)
|
||||
conn_no_ext = Majic.Plug.call(orig_conn, plug_no_ext)
|
||||
conn_append_ext = Majic.Plug.call(orig_conn, plug_append_ext)
|
||||
|
||||
assert conn.state == :sent
|
||||
assert conn.status == 200
|
||||
refute get_in(conn.body_params, ["form", "makefile"]).content_type == get_in(conn.params, ["form", "makefile"]).content_type
|
||||
assert get_in(conn.params, ["form", "makefile"]).content_type == "text/x-makefile"
|
||||
refute get_in(conn.body_params, ["form", "make", "file"]).content_type == get_in(conn.params, ["form", "make", "file"]).content_type
|
||||
assert get_in(conn.params, ["form", "make", "file"]).content_type == "text/x-makefile"
|
||||
refute get_in(conn.body_params, ["cat"]).content_type == get_in(conn.params, ["cat"]).content_type
|
||||
assert get_in(conn.params, ["cat"]).content_type == "image/webp"
|
||||
assert get_in(conn.params, ["cat"]).filename == "cute-cat.webp"
|
||||
assert get_in(conn_no_ext.params, ["cat"]).filename == "cute-cat.jpg"
|
||||
assert get_in(conn_append_ext.params, ["cat"]).filename == "cute-cat.jpg.webp"
|
||||
end
|
||||
|
||||
end
|
Loading…
Reference in a new issue