Page MenuHomePhorge

No OneTemporary

Size
9 KB
Referenced Files
None
Subscribers
None
diff --git a/lib/majic/plug.ex b/lib/majic/plug.ex
index 5d647c9..311d20b 100644
--- a/lib/majic/plug.ex
+++ b/lib/majic/plug.ex
@@ -1,168 +1,194 @@
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
cond do
Keyword.has_key?(opts, :pool) -> true
Keyword.has_key?(opts, :server) -> true
Keyword.has_key?(opts, :once) -> true
true -> raise(Majic.PlugError, "No server/pool/once option defined")
end
- opts =
- opts
- |> Keyword.put_new(:fix_extension, true)
- |> Keyword.put_new(:append_extension, false)
-
opts
+ |> Keyword.put_new(:fix_extension, true)
+ |> Keyword.put_new(:append_extension, false)
end
@impl Plug
def call(conn, opts) do
collected = collect_uploads([], conn.body_params, [])
Enum.reduce(collected, conn, fn {param_path, upload}, conn ->
+ {array_index, param_path} =
+ case param_path do
+ [index, :array | path] ->
+ {index, path}
+
+ path ->
+ {nil, path}
+ end
+
param_path = Enum.reverse(param_path)
upload =
case Majic.perform(upload.path, opts) do
{:ok, magic} -> fix_upload(upload, magic, opts)
{:error, error} -> raise(Majic.PlugError, "Failed to majic: #{inspect(error)}")
end
- conn =
- if get_in(conn.params, param_path) do
- %{conn | params: put_in(conn.params, param_path, upload)}
- end
-
- if get_in(conn.body_params, param_path) do
- %{conn | body_params: put_in(conn.body_params, param_path, upload)}
- end
+ conn
+ |> put_in_if_exists(:params, param_path, upload, array_index)
+ |> put_in_if_exists(:body_params, param_path, upload, array_index)
end)
end
defp collect_uploads(path, params, acc) do
Enum.reduce(params, acc, fn value, acc -> collect_upload(path, value, acc) end)
end
# An upload!
defp collect_upload(path, {k, %{__struct__: Plug.Upload} = upload}, acc) do
[{[k | path], upload} | acc]
end
# Ignore structs.
defp collect_upload(_path, {_, %{__struct__: _}}, acc) do
acc
end
# Nested map.
defp collect_upload(path, {k, v}, acc) when is_map(v) do
collect_uploads([k | path], v, acc)
end
+ defp collect_upload(path, {k, v}, acc) when is_list(v) do
+ Enum.reduce(Enum.with_index(v), acc, fn {item, index}, acc ->
+ collect_upload([:array, k | path], {index, item}, acc)
+ end)
+ end
+
defp collect_upload(_path, _, acc) do
acc
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, [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
# No extension for type.
defp rewrite_extension(upload, old, [], opts) do
%{
upload
| filename:
rewrite_or_append_extension(
Path.basename(upload.filename, old),
old,
nil,
Keyword.get(opts, :append_extension)
)
}
end
# Append, no extension for type: keep old extension
defp rewrite_or_append_extension(basename, "." <> old, nil, true) do
basename <> "." <> old
end
# No extension for type: only keep basename
defp rewrite_or_append_extension(basename, _, nil, _) do
basename
end
# Append
defp rewrite_or_append_extension(basename, "." <> old, ext, true) do
Enum.join([basename, old, ext], ".")
end
# Rewrite
defp rewrite_or_append_extension(basename, _, ext, _) do
basename <> "." <> ext
end
+
+ # put value at path in conn.
+ defp put_in_if_exists(conn, key, path, value, nil) do
+ if get_in(Map.get(conn, key), path) do
+ Map.put(conn, key, put_in(Map.get(conn, key), path, value))
+ else
+ conn
+ end
+ end
+
+ # change value at index in list at path in conn.
+ defp put_in_if_exists(conn, key, path, value, index) do
+ if array = get_in(Map.get(conn, key), path) do
+ array = List.replace_at(array, index, value)
+ Map.put(conn, key, put_in(Map.get(conn, key), path, array))
+ else
+ conn
+ end
+ end
end
end
diff --git a/test/majic/plug_test.exs b/test/majic/plug_test.exs
index 2e9638b..c0187cc 100644
--- a/test/majic/plug_test.exs
+++ b/test/majic/plug_test.exs
@@ -1,85 +1,97 @@
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
+ Content-Disposition: form-data; name=\"cats[]\"; filename*=\"utf-8''first-cute-cat.jpg\"\r
+ Content-Type: image/jpg\r
+ \r
+ #{String.replace(File.read!("test/fixtures/cat.webp"), "\n", "\n")}\r
+ ------w58EW1cEpjzydSCq\r
+ Content-Disposition: form-data; name=\"cats[]\"; filename*=\"utf-8''second-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
assert get_in(conn.body_params, ["form", "makefile"]) ==
get_in(conn.params, ["form", "makefile"])
assert get_in(conn.params, ["form", "makefile"]).content_type == "text/x-makefile"
assert get_in(conn.params, ["form", "makefile"]).filename == "mymakefile"
assert get_in(conn_no_ext.params, ["form", "makefile"]).filename == "mymakefile.txt"
assert get_in(conn_append_ext.params, ["form", "makefile"]).filename == "mymakefile.txt"
assert get_in(conn.body_params, ["form", "make", "file"]) ==
get_in(conn.params, ["form", "make", "file"])
assert get_in(conn.params, ["form", "make", "file"]).content_type == "text/x-makefile"
assert get_in(conn.body_params, ["cat"]) == get_in(conn.params, ["cat"])
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"
+
+ assert Enum.all?(conn.params["cats"], fn upload -> upload.content_type == "image/webp" end)
end
end

File Metadata

Mime Type
text/x-diff
Expires
Mon, Nov 25, 9:38 AM (1 d, 13 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
39759
Default Alt Text
(9 KB)

Event Timeline