Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F113108
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Size
10 KB
Referenced Files
None
Subscribers
None
View Options
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
Details
Attached
Mime Type
text/x-diff
Expires
Sun, Nov 24, 9:26 AM (23 h, 21 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
39400
Default Alt Text
(10 KB)
Attached To
Mode
R20 majic
Attached
Detach File
Event Timeline
Log In to Comment