Page MenuHomePhorge

No OneTemporary

Size
10 KB
Referenced Files
None
Subscribers
None
diff --git a/lib/majic/plug.ex b/lib/majic/plug.ex
index 64667e3..5d647c9 100644
--- a/lib/majic/plug.ex
+++ b/lib/majic/plug.ex
@@ -1,140 +1,168 @@
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(%{params: params} = conn, opts) do
- %{conn | params: collect_uploads(params, opts)}
- end
+ def call(conn, opts) do
+ collected = collect_uploads([], conn.body_params, [])
+
+ Enum.reduce(collected, conn, fn {param_path, upload}, conn ->
+ 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
- def call(conn, _) do
- conn
+ 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
+ end)
end
- defp collect_uploads(params, opts) do
- Enum.reduce(params, Map.new(), fn value, acc -> collect_upload(value, acc, opts) end)
+ defp collect_uploads(path, params, acc) do
+ Enum.reduce(params, acc, fn value, acc -> collect_upload(path, value, acc) end)
end
- defp collect_upload({k, %{__struct__: Plug.Upload, path: path} = upload}, acc, opts) do
- case Majic.perform(path, opts) do
- {:ok, magic} ->
- Map.put(acc, k, fix_upload(upload, magic, opts))
+ # An upload!
+ defp collect_upload(path, {k, %{__struct__: Plug.Upload} = upload}, acc) do
+ [{[k | path], upload} | acc]
+ end
- {:error, error} ->
- raise(Majic.PlugError, "Failed to gen_magic: #{inspect(error)}")
- end
+ # Ignore structs.
+ defp collect_upload(_path, {_, %{__struct__: _}}, acc) do
+ acc
end
- defp collect_upload({k, v}, acc, opts) when is_map(v) do
- Map.put(acc, k, collect_uploads(v, opts))
+ # Nested map.
+ defp collect_upload(path, {k, v}, acc) when is_map(v) do
+ collect_uploads([k | path], v, acc)
end
- defp collect_upload({k, v}, acc, _opts) do
- Map.put(acc, k, v)
+ 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))}
+ %{
+ 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
end
end
diff --git a/test/builds-sr-ht.exs b/test/builds-sr-ht.exs
new file mode 100644
index 0000000..7f7c7f8
--- /dev/null
+++ b/test/builds-sr-ht.exs
@@ -0,0 +1,52 @@
+name =
+ case System.cmd("git", ~w(describe --all --long --dirty --broken --always)) do
+ {name, 0} -> String.trim(name)
+ _ -> "cannot-git-describe"
+ end
+
+repo = System.get_env("TEST_REPO") || "https://git.sr.ht/~href/gen_magic"
+
+IO.puts("Using repository: #{repo}")
+
+token = System.get_env("SR_HT_TOKEN")
+
+unless token do
+ IO.puts("""
+ sr.ht token not defined (SR_HT_TOKEN)\n\n
+ Get one at https://meta.sr.ht/oauth/personal-token\n
+ Define one by setting the SR_HT_TOKEN environment variable
+ """)
+else
+ Application.ensure_all_started(:ssl)
+ Application.ensure_all_started(:inets)
+
+ File.ls!(".builds")
+ |> Enum.filter(fn file -> Path.extname(file) == ".yaml" end)
+ |> Enum.each(fn file ->
+ file = Path.join(".builds", file)
+ build = Path.basename(file, ".yaml")
+
+ build =
+ %{
+ "manifest" => File.read!(file),
+ "note" => "gen_magic/#{name} #{build}",
+ "tags" => ["gen_magic"]
+ }
+ |> Jason.encode!()
+
+ case :httpc.request(
+ :post,
+ {'https://builds.sr.ht/api/jobs', [{'authorization', 'token ' ++ to_charlist(token)}],
+ 'application/json', build},
+ [],
+ []
+ ) do
+ {:ok, {{_http_v, 200, 'OK'}, _headers, body}} ->
+ resp = Jason.decode!(body)
+ IO.puts("#{resp["status"]} job #{resp["note"]}, id: #{resp["id"]}")
+
+ error ->
+ IO.puts("Failed to enqueue job #{inspect(error)}")
+ end
+ end)
+end
diff --git a/test/majic/plug_test.exs b/test/majic/plug_test.exs
index 060408d..2e9638b 100644
--- a/test/majic/plug_test.exs
+++ b/test/majic/plug_test.exs
@@ -1,87 +1,85 @@
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.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"
- refute get_in(conn.body_params, ["form", "make", "file"]).content_type ==
- get_in(conn.params, ["form", "make", "file"]).content_type
+ 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"
- refute get_in(conn.body_params, ["cat"]).content_type ==
- get_in(conn.params, ["cat"]).content_type
-
+ 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"
end
end

File Metadata

Mime Type
text/x-diff
Expires
Mon, Nov 25, 1:55 PM (1 d, 14 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
39400
Default Alt Text
(10 KB)

Event Timeline