Page MenuHomePhorge

No OneTemporary

Size
36 KB
Referenced Files
None
Subscribers
None
diff --git a/.dialyzer_ignore.exs b/.dialyzer_ignore.exs
new file mode 100644
index 0000000..e69de29
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 7f87bf7..c5e1346 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,33 +1,33 @@
-image: elixir:1.10-alpine
+image: elixir:1.17-alpine
variables:
MIX_ENV: test
GIT_SUBMODULE_STRATEGY: recursive
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- deps
- _build
stages:
- test
- publish
before_script:
- apk add build-base cmake libmagic file-dev
- mix local.hex --force
- mix local.rebar --force
- mix deps.get --only test
- mix compile --force
lint:
stage: test
script:
- mix format --check-formatted
unit-testing:
stage: test
coverage: '/(\d+\.\d+\%) \| Total/'
script:
- mix test --trace --preload-modules --cover
diff --git a/.tool-versions b/.tool-versions
deleted file mode 100644
index db012a6..0000000
--- a/.tool-versions
+++ /dev/null
@@ -1,2 +0,0 @@
-erlang 24.0.2
-elixir 1.12.3-otp-24
diff --git a/CHANGELOG.md b/CHANGELOG.md
index bebb05d..5563aa8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,62 +1,70 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog][1], and this project adheres to [Semantic Versioning][2].
[1]: https://keepachangelog.com/en/1.0.0/
[2]: https://semver.org/spec/v2.0.0.html
-## majic [Unreleased]
+## majic [1.1.0]
+
+- Updated dependencies
+- `-Werror` removed from `Makefile`
+- `Majic.Extension.fix/3` will append an extension when requested if none exist
+
+## majic [1.0.0]
## Added
- Forked gen_magic.
-- Pool: `Majic.Pool`
-- Plug: `Majic.Plug`
-- Unified API: `Majic.perform/1,2,3`
+- Pool: `Majic.Pool`, using [nimble_pool](https://hex.pm/packages/nimble_pool).
+- Plug: `Majic.Plug`.
+- Unified API: `Majic.perform/1,2,3`.
+- Builds an up-to-date and patched magic database.
+- `Majic.compile/2`
+- `Majic.Server.reload/2,3`
+- `Majic.Server.recycle/2,3`
## Changed
-- C port now using erl_interface
+- Improved C port, now using erl_interface
- Builds on Musl
- Better error and timeout handling
-- `Majic.Server.reload/2,3`
-- `Majic.Server.recycle/2,3`
- Bytes support: `Majic.Server.perform(ref, {:bytes, <<>>})`
- Renamed `priv/apprentice` to `priv/libmagic_port` to be more obvious in `ps`
- Renamed `Majic.Helpers.perform_once` to `Majic.Once.perform`
## gen_majic [1.0]
### Added
- Added support for process recycling (evadne).
- Added documentation (evadne).
### Changed
- Replaced GenServer with `:gen_statem` (evadne).
- Changed API; added support for customisation.
- Refined tests and other aspects of the library (evadne).
## [0.20.83]
### Added
- Soak testing script (devstopfix)
### Changed
- Replaced Erlexec usage with Port (devstopfix)
## 0.0.1
### Added
- Initial Elixir wrapper with Erlexec (evadne)
- Intiial C program (evadne)
[unreleased]: https://github.com/evadne/gen_magic/compare/develop
[0.20.83]: https://github.com/devstopfix/gen_magic/commit/7e27fd094cb462d26ba54fde0205a5be313d12da
diff --git a/Makefile b/Makefile
index deacaca..edfdf18 100644
--- a/Makefile
+++ b/Makefile
@@ -1,22 +1,25 @@
-CFLAGS += -std=c99 -g -Wall -Werror
+CFLAGS += -std=c99 -g -Wall
CPPFLAGS += -I$(ERL_EI_INCLUDE_DIR) -I/usr/local/include
LDFLAGS += -L$(ERL_EI_LIBDIR) -L/usr/local/lib
-LDLIBS = -lpthread -lei -lm -lmagic
+LDLIBS = -lpthread
PRIV = priv/
RM = rm -Rf
ifeq ($(EI_INCOMPLETE),YES)
LDLIBS += -lerl_interface
CFLAGS += -DEI_INCOMPLETE
endif
+LDLIBS += -lei -lm -lmagic
+
+
all: priv/libmagic_port
priv/libmagic_port: src/libmagic_port.c
mkdir -p priv
$(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) $< $(LDLIBS) -o $@
clean:
$(RM) $(PRIV)
.PHONY: clean
diff --git a/lib/majic/extension.ex b/lib/majic/extension.ex
index 4a503fc..3bbdeba 100644
--- a/lib/majic/extension.ex
+++ b/lib/majic/extension.ex
@@ -1,94 +1,112 @@
defmodule Majic.Extension do
@moduledoc """
Helper module to fix extensions. Uses [MIME](https://hexdocs.pm/mime/MIME.html).
"""
@typedoc """
If an extension is defined for a given MIME type, append it to the previous extension.
If no extension could be found for the MIME type, and `subtype_as_extension: false`, the returned filename will have no extension.
"""
@type option_append :: {:append, false | true}
@typedoc "If no extension is defined for a given MIME type, use the subtype as its extension."
@type option_subtype_as_extension :: {:subtype_as_extension, false | true}
@spec fix(Path.t(), Majic.Result.t() | String.t(), [
option_append() | option_subtype_as_extension()
]) :: Path.t()
@doc """
Fix `name`'s extension according to `result_or_mime_type`.
```elixir
iex(1)> {:ok, result} = Majic.perform("cat.jpeg", once: true)
{:ok, %Majic.Result{mime_type: "image/webp", ...}}
iex(1)> Majic.Extension.fix("cat.jpeg", result)
"cat.webp"
```
The `append: true` option will append the correct extension to the user-provided one, if there's an extension for the
type:
```
iex(1)> Majic.Extension.fix("cat.jpeg", result, append: true)
"cat.jpeg.webp"
iex(2)> Majic.Extension.fix("Makefile.txt", "text/x-makefile", append: true)
"Makefile"
```
The `subtype_as_extension: true` option will use the subtype part of the MIME type as an extension for the ones that
don't have any:
```elixir
iex(1)> Majic.Extension.fix("Makefile.txt", "text/x-makefile", subtype_as_extension: true)
"Makefile.x-makefile"
iex(1)> Majic.Extension.fix("Makefile.txt", "text/x-makefile", subtype_as_extension: true, append: true)
"Makefile.txt.x-makefile"
```
"""
def fix(name, result_or_mime_type, options \\ [])
def fix(name, %Majic.Result{mime_type: mime_type}, options) do
do_fix(name, mime_type, options)
end
def fix(name, mime_type, options) do
do_fix(name, mime_type, options)
end
defp do_fix(name, mime_type, options) do
append? = Keyword.get(options, :append, false)
subtype? = Keyword.get(options, :subtype_as_extension, false)
- exts = MIME.extensions(mime_type) ++ subtype_extension(subtype?, mime_type)
+ ext_candidates = MIME.extensions(mime_type)
old_ext = String.downcase(Path.extname(name))
+ old_ext_bare = String.trim_leading(old_ext, ".")
+ basename = Path.basename(name, old_ext)
- unless old_ext == "" do
- basename = Path.basename(name, old_ext)
- "." <> old = old_ext
-
- if old in exts do
- Enum.join([basename, ".", old])
- else
- ext = List.first(exts)
-
- ext_list =
- cond do
- ext && append? -> [old, ext]
- !ext -> []
- ext -> [ext]
- end
-
- Enum.join([basename] ++ ext_list, ".")
- end
- else
- name
+ cond do
+ # extension already in candidate list, so no-op
+ old_ext_bare in ext_candidates ->
+ name
+
+ # has extension, append the subtype
+ not match?("", old_ext) && append? && subtype? ->
+ Enum.join([name, subtype_extension(subtype?, mime_type)], ".")
+
+ # has extension, change to subtype
+ not match?("", old_ext) && subtype? ->
+ Enum.join([basename, subtype_extension(subtype?, mime_type)], ".")
+
+ # no extension, append
+ match?("", old_ext) && append? ->
+ Enum.join([basename, List.first(ext_candidates)], ".")
+
+ # no candidates, so strip extension
+ match?([], ext_candidates) ->
+ basename
+
+ # no extension but no appending, so no-op
+ match?("", old_ext) ->
+ name
+
+ # append first candidate
+ not Enum.empty?(ext_candidates) && append? ->
+ Enum.join([name, List.first(ext_candidates)], ".")
+
+ # change extension to first candidate
+ not Enum.empty?(ext_candidates) ->
+ Enum.join([basename, List.first(ext_candidates)], ".")
+
+ # do nothing
+ true ->
+ name
end
end
defp subtype_extension(true, type) do
[_type, sub] = String.split(type, "/", parts: 2)
[sub]
end
defp subtype_extension(_, _), do: []
end
diff --git a/mix.exs b/mix.exs
index b360433..e1e1349 100644
--- a/mix.exs
+++ b/mix.exs
@@ -1,83 +1,83 @@
defmodule Majic.MixProject do
use Mix.Project
- if :erlang.system_info(:otp_release) < '21' do
+ if :erlang.system_info(:otp_release) < ~c"21" do
raise "Majic requires Erlang/OTP 21 or newer"
end
def project do
[
app: :majic,
- version: "1.0.0",
+ version: "1.1.0",
elixir: "~> 1.7",
elixirc_paths: elixirc_paths(Mix.env()),
elixirc_options: [warnings_as_errors: warnings_as_errors(Mix.env())],
start_permanent: Mix.env() == :prod,
compilers: [:elixir_make] ++ Mix.compilers(),
make_env: make_env(),
package: package(),
deps: deps(),
dialyzer: dialyzer(),
name: "Majic",
description: "File introspection with libmagic",
source_url: "https://git.pleroma.social/pleroma/elixir-libraries/majic",
- docs: docs()
+ docs: docs(),
+ test_coverage: [summary: false]
]
end
def application do
[extra_applications: [:logger]]
end
defp elixirc_paths(:test), do: ["lib", "test/support"]
defp elixirc_paths(_), do: ["lib"]
defp dialyzer do
[
plt_add_apps: [:mix, :iex, :ex_unit, :plug, :mime],
- flags: ~w(error_handling no_opaque race_conditions underspecs unmatched_returns)a,
+ flags: ~w(error_handling no_opaque underspecs unmatched_returns),
ignore_warnings: "dialyzer-ignore-warnings.exs",
list_unused_filters: true
]
end
defp deps do
[
- {:nimble_pool, "~> 0.2"},
+ {:nimble_pool, "~> 1.0"},
{:mime, "~> 1.0"},
{:plug, "~> 1.0", optional: true},
{:credo, "~> 1.4", only: [:dev, :test], runtime: false},
- {:dialyxir, "~> 1.0.0-rc.6", only: :dev, runtime: false},
+ {:dialyxir, "~> 1.4", only: :dev, runtime: false},
{:ex_doc, ">= 0.0.0", only: :dev, runtime: false},
- {:elixir_make, "~> 0.6.1", runtime: false}
+ {:elixir_make, "~> 0.8.4", runtime: false}
]
end
defp package do
[
licenses: ["Apache 2.0"],
- links: %{"GitHub" => "https://github.com/hrefhref/majic"},
- source_url: "https://git.pleroma.social/pleroma/elixir-libraries/majic"
+ links: %{"GitLab" => "https://git.pleroma.social/pleroma/elixir-libraries/majic"}
]
end
defp docs do
[
main: "readme",
extras: ["README.md", "CHANGELOG.md"]
]
end
defp warnings_as_errors(:dev), do: false
defp warnings_as_errors(_), do: true
defp make_env() do
otp =
:erlang.system_info(:otp_release)
|> to_string()
|> String.to_integer()
ei_incomplete = if(otp < 21.3, do: "YES", else: "NO")
%{"EI_INCOMPLETE" => ei_incomplete}
end
end
diff --git a/mix.lock b/mix.lock
index c173957..7e9695e 100644
--- a/mix.lock
+++ b/mix.lock
@@ -1,22 +1,23 @@
%{
- "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"},
- "credo": {:hex, :credo, "1.4.0", "92339d4cbadd1e88b5ee43d427b639b68a11071b6f73854e33638e30a0ea11f5", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "1fd3b70dce216574ce3c18bdf510b57e7c4c85c2ec9cad4bff854abaf7e58658"},
- "dialyxir": {:hex, :dialyxir, "1.0.0", "6a1fa629f7881a9f5aaf3a78f094b2a51a0357c843871b8bc98824e7342d00a5", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "aeb06588145fac14ca08d8061a142d52753dbc2cf7f0d00fc1013f53f8654654"},
+ "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"},
+ "credo": {:hex, :credo, "1.7.7", "771445037228f763f9b2afd612b6aa2fd8e28432a95dbbc60d8e03ce71ba4446", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8bc87496c9aaacdc3f90f01b7b0582467b69b4bd2441fe8aae3109d843cc2f2e"},
+ "dialyxir": {:hex, :dialyxir, "1.4.4", "fb3ce8741edeaea59c9ae84d5cec75da00fa89fe401c72d6e047d11a61f65f70", [:mix], [{:erlex, ">= 0.2.7", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "cd6111e8017ccd563e65621a4d9a4a1c5cd333df30cebc7face8029cacb4eff6"},
"earmark": {:hex, :earmark, "1.4.5", "62ffd3bd7722fb7a7b1ecd2419ea0b458c356e7168c1f5d65caf09b4fbdd13c8", [:mix], [], "hexpm", "b7d0e6263d83dc27141a523467799a685965bf8b13b6743413f19a7079843f4f"},
- "earmark_parser": {:hex, :earmark_parser, "1.4.18", "e1b2be73eb08a49fb032a0208bf647380682374a725dfb5b9e510def8397f6f2", [:mix], [], "hexpm", "114a0e85ec3cf9e04b811009e73c206394ffecfcc313e0b346de0d557774ee97"},
- "elixir_make": {:hex, :elixir_make, "0.6.3", "bc07d53221216838d79e03a8019d0839786703129599e9619f4ab74c8c096eac", [:mix], [], "hexpm", "f5cbd651c5678bcaabdbb7857658ee106b12509cd976c2c2fca99688e1daf716"},
- "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"},
+ "earmark_parser": {:hex, :earmark_parser, "1.4.41", "ab34711c9dc6212dda44fcd20ecb87ac3f3fce6f0ca2f28d4a00e4154f8cd599", [:mix], [], "hexpm", "a81a04c7e34b6617c2792e291b5a2e57ab316365c2644ddc553bb9ed863ebefa"},
+ "elixir_make": {:hex, :elixir_make, "0.8.4", "4960a03ce79081dee8fe119d80ad372c4e7badb84c493cc75983f9d3bc8bde0f", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.0", [hex: :certifi, repo: "hexpm", optional: true]}], "hexpm", "6e7f1d619b5f61dfabd0a20aa268e575572b542ac31723293a4c1a567d5ef040"},
+ "erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"},
"erlexec": {:hex, :erlexec, "1.10.0", "cba7924cf526097d2082ceb0ec34e7db6bca2624b8f3867fb3fa89c4cf25d227", [:rebar3], [], "hexpm"},
- "ex_doc": {:hex, :ex_doc, "0.26.0", "1922164bac0b18b02f84d6f69cab1b93bc3e870e2ad18d5dacb50a9e06b542a3", [:mix], [{:earmark_parser, "~> 1.4.0", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "2775d66e494a9a48355db7867478ffd997864c61c65a47d31c4949459281c78d"},
+ "ex_doc": {:hex, :ex_doc, "0.34.2", "13eedf3844ccdce25cfd837b99bea9ad92c4e511233199440488d217c92571e8", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "5ce5f16b41208a50106afed3de6a2ed34f4acfd65715b82a0b84b49d995f95c1"},
"exexec": {:hex, :exexec, "0.2.0", "a6ffc48cba3ac9420891b847e4dc7120692fb8c08c9e82220ebddc0bb8d96103", [:mix], [{:erlexec, "~> 1.10", [hex: :erlexec, repo: "hexpm", optional: false]}], "hexpm"},
- "jason": {:hex, :jason, "1.2.1", "12b22825e22f468c02eb3e4b9985f3d0cb8dc40b9bd704730efa11abd2708c44", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "b659b8571deedf60f79c5a608e15414085fa141344e2716fbd6988a084b5f993"},
- "makeup": {:hex, :makeup, "1.0.5", "d5a830bc42c9800ce07dd97fa94669dfb93d3bf5fcf6ea7a0c67b2e0e4a7f26c", [:mix], [{:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cfa158c02d3f5c0c665d0af11512fed3fba0144cf1aadee0f2ce17747fba2ca9"},
- "makeup_elixir": {:hex, :makeup_elixir, "0.15.2", "dc72dfe17eb240552857465cc00cce390960d9a0c055c4ccd38b70629227e97c", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.1", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "fd23ae48d09b32eff49d4ced2b43c9f086d402ee4fd4fcb2d7fad97fa8823e75"},
- "makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"},
- "mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm", "6cbe761d6a0ca5a31a0931bf4c63204bceb64538e664a8ecf784a9a6f3b875f1"},
- "nimble_parsec": {:hex, :nimble_parsec, "1.2.0", "b44d75e2a6542dcb6acf5d71c32c74ca88960421b6874777f79153bbbbd7dccc", [:mix], [], "hexpm", "52b2871a7515a5ac49b00f214e4165a40724cf99798d8e4a65e4fd64ebd002c1"},
- "nimble_pool": {:hex, :nimble_pool, "0.2.4", "1db8e9f8a53d967d595e0b32a17030cdb6c0dc4a451b8ac787bf601d3f7704c3", [:mix], [], "hexpm", "367e8071e137b787764e6a9992ccb57b276dc2282535f767a07d881951ebeac6"},
- "plug": {:hex, :plug, "1.10.3", "c9cebe917637d8db0e759039cc106adca069874e1a9034fd6e3fdd427fd3c283", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "01f9037a2a1de1d633b5a881101e6a444bcabb1d386ca1e00bb273a1f1d9d939"},
- "plug_crypto": {:hex, :plug_crypto, "1.1.2", "bdd187572cc26dbd95b87136290425f2b580a116d3fb1f564216918c9730d227", [:mix], [], "hexpm", "6b8b608f895b6ffcfad49c37c7883e8df98ae19c6a28113b02aa1e9c5b22d6b5"},
- "telemetry": {:hex, :telemetry, "0.4.1", "ae2718484892448a24470e6aa341bc847c3277bfb8d4e9289f7474d752c09c7f", [:rebar3], [], "hexpm", "4738382e36a0a9a2b6e25d67c960e40e1a2c95560b9f936d8e29de8cd858480f"},
+ "file_system": {:hex, :file_system, "1.0.0", "b689cc7dcee665f774de94b5a832e578bd7963c8e637ef940cd44327db7de2cd", [:mix], [], "hexpm", "6752092d66aec5a10e662aefeed8ddb9531d79db0bc145bb8c40325ca1d8536d"},
+ "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"},
+ "makeup": {:hex, :makeup, "1.1.2", "9ba8837913bdf757787e71c1581c21f9d2455f4dd04cfca785c70bbfff1a76a3", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cce1566b81fbcbd21eca8ffe808f33b221f9eee2cbc7a1706fc3da9ff18e6cac"},
+ "makeup_elixir": {:hex, :makeup_elixir, "0.16.2", "627e84b8e8bf22e60a2579dad15067c755531fea049ae26ef1020cad58fe9578", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "41193978704763f6bbe6cc2758b84909e62984c7752b3784bd3c218bb341706b"},
+ "makeup_erlang": {:hex, :makeup_erlang, "1.0.1", "c7f58c120b2b5aa5fd80d540a89fdf866ed42f1f3994e4fe189abebeab610839", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "8a89a1eeccc2d798d6ea15496a6e4870b75e014d1af514b1b71fa33134f57814"},
+ "mime": {:hex, :mime, "1.6.0", "dabde576a497cef4bbdd60aceee8160e02a6c89250d6c0b29e56c0dfb00db3d2", [:mix], [], "hexpm", "31a1a8613f8321143dde1dafc36006a17d28d02bdfecb9e95a880fa7aabd19a7"},
+ "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"},
+ "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"},
+ "plug": {:hex, :plug, "1.16.1", "40c74619c12f82736d2214557dedec2e9762029b2438d6d175c5074c933edc9d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a13ff6b9006b03d7e33874945b2755253841b238c34071ed85b0e86057f8cddc"},
+ "plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"},
+ "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"},
}
diff --git a/src/libmagic_port.c b/src/libmagic_port.c
index fcfd72a..84199e3 100644
--- a/src/libmagic_port.c
+++ b/src/libmagic_port.c
@@ -1,447 +1,449 @@
//
// libmagic_port: The Sorcerer’s Apprentice
//
// To use this program, compile it with dynamically linked libmagic, as mirrored
// at https://github.com/file/file. You may install it with apt-get,
// yum or brew. Refer to the Makefile for further reference.
//
// This program is designed to run interactively as a backend daemon to the
// GenMagic library.
//
// Communication is done over STDIN/STDOUT as binary packets of 2 bytes length
// plus X bytes payload, where the payload is an erlang term encoded with
// :erlang.term_to_binary/1 and decoded with :erlang.binary_to_term/1.
//
// Once the program is ready, it sends the `:ready` atom.
//
// It is then up to the Erlang side to load databases, by sending messages:
// - `{:add_database, :default | path}`
//
// If the requested database have been loaded, an `{:ok, :loaded}` message will
// follow. Otherwise, the process will exit (exit code 1).
//
// Commands are sent to the program STDIN as an erlang term of `{Operation,
// Argument}`, and response of `{:ok | :error, Response}`.
//
// The program may exit with the following exit codes:
// - 1 if libmagic handles could not be opened,
// - 2 if something went wrong with ei_*,
// - 3 if you sent invalid term format,
// - 255 if the loop exited unexpectedly.
//
// Invalid packets will cause the program to exit (exit code 3). This will
// happen if your Erlang Term format doesn't match the version the program has
// been compiled with.
//
// Commands:
// {:reload, _} :: :ready
// {:add_database, :default | String.t()} :: {:ok, _} | {:error, _}
// {:file, path :: String.t()} :: {:ok, {type, encoding, name}} | {:error,
// :badarg} | {:error, {errno :: integer(), String.t()}}
// {:bytes, binary()} :: same as :file
// {:stop, reason :: atom()} :: exit 0
#include <arpa/inet.h>
#include <ei.h>
#ifdef EI_INCOMPLETE
#include <erl_interface.h>
#endif
#include <errno.h>
#include <getopt.h>
#include <libgen.h>
#include <magic.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#define ERROR_OK 0
#define ERROR_MAGIC 1
#define ERROR_EI 2
#define ERROR_BAD_TERM 3
// We use a bigger than possible valid command length (around 4111 bytes) to
// allow more precise errors when using too long paths.
#define COMMAND_LEN 8000
#define COMMAND_BUFFER_SIZE COMMAND_LEN + 1
#define MAGIC_FLAGS_COMMON (MAGIC_CHECK | MAGIC_ERROR)
magic_t magic_setup(int flags);
#define EI_ENSURE(result) \
do { \
if (result != 0) { \
fprintf(stderr, "EI ERROR, line: %d", __LINE__); \
exit(ERROR_EI); \
} \
} while (0);
typedef char byte;
void setup_environment();
void magic_close_all();
void magic_open_all();
int magic_load_all(char *path);
void process_command(uint16_t len, byte *buf);
void process_command_file(byte *buf, int index, ei_x_buff *result);
void process_command_bytes(byte *buf, int index, ei_x_buff *result);
void process_command_load(byte *buf, int index, ei_x_buff *result);
void process_file(char *path, ei_x_buff *result);
void process_bytes(char *bytes, int size, ei_x_buff *result);
void process_load(ei_x_buff *result, char *path);
void send_and_free(ei_x_buff *result);
size_t read_cmd(byte *buf);
size_t write_cmd(byte *buf, size_t len);
void error(ei_x_buff *result, const char *error);
void handle_magic_error(magic_t handle, int errn, ei_x_buff *result);
void fdseek(uint16_t count);
static magic_t magic_mime_type; // MAGIC_MIME_TYPE
static magic_t magic_mime_encoding; // MAGIC_MIME_ENCODING
static magic_t magic_type_name; // MAGIC_NONE
bool magic_loaded = false;
int main(int argc, char **argv) {
#ifdef EI_INCOMPLETE
erl_init(NULL, -1);
#else
EI_ENSURE(ei_init());
#endif
setup_environment();
magic_open_all();
byte buf[COMMAND_BUFFER_SIZE];
uint16_t len;
while ((len = read_cmd(buf)) > 0) {
process_command(len, buf);
}
return 255;
}
void process_command(uint16_t len, byte *buf) {
ei_x_buff result;
char atom[128];
int index, version, arity;
index = 0;
// Initialize result
EI_ENSURE(ei_x_new_with_version(&result));
EI_ENSURE(ei_x_encode_tuple_header(&result, 2));
if (len >= COMMAND_LEN)
return error(&result, "badarg");
if (ei_decode_version(buf, &index, &version) != 0)
exit(ERROR_BAD_TERM);
if (ei_decode_tuple_header(buf, &index, &arity) != 0)
return error(&result, "badarg");
if (arity != 2)
return error(&result, "badarg");
if (ei_decode_atom(buf, &index, atom) != 0)
return error(&result, "badarg");
// {:file, path}
if (strlen(atom) == 4 && strcmp(atom, "file") == 0)
return process_command_file(buf, index, &result);
// {:bytes, bytes}
if (strlen(atom) == 5 && strcmp(atom, "bytes") == 0)
return process_command_bytes(buf, index, &result);
// {:add_database, path}
if (strlen(atom) == 12 && strcmp(atom, "add_database") == 0)
return process_command_load(buf, index, &result);
// {:reload, _}
if (strlen(atom) == 6 && strcmp(atom, "reload") == 0)
return magic_open_all();
// {:stop, _}
if (strlen(atom) == 4 && strcmp(atom, "stop") == 0)
exit(ERROR_OK);
error(&result, "badarg");
}
void process_command_file(byte *buf, int index, ei_x_buff *result) {
int termtype, termsize;
char path[4097];
long bin_length;
if (!magic_loaded)
return error(result, "magic_database_not_loaded");
ei_get_type(buf, &index, &termtype, &termsize);
if (termtype != ERL_BINARY_EXT)
return error(result, "badarg");
if (termsize > 4096)
return error(result, "enametoolong");
EI_ENSURE(ei_decode_binary(buf, &index, path, &bin_length));
path[termsize] = '\0';
process_file(path, result);
}
void process_command_bytes(byte *buf, int index, ei_x_buff *result) {
if (!magic_loaded)
return error(result, "magic_database_not_loaded");
int termtype, termsize;
long bin_length;
char bytes[51];
EI_ENSURE(ei_get_type(buf, &index, &termtype, &termsize));
if (termtype != ERL_BINARY_EXT)
return error(result, "badarg");
if (termsize > 50)
return error(result, "toolong");
EI_ENSURE(ei_decode_binary(buf, &index, bytes, &bin_length));
bytes[termsize] = '\0';
process_bytes(bytes, termsize, result);
}
void process_command_load(byte *buf, int index, ei_x_buff *result) {
char path[4097];
int termtype, termsize;
ei_get_type(buf, &index, &termtype, &termsize);
if (termtype == ERL_BINARY_EXT) {
if (termsize > 4096)
return error(result, "enametoolong");
long bin_length;
EI_ENSURE(ei_decode_binary(buf, &index, path, &bin_length));
path[termsize] = '\0';
return process_load(result, path);
}
if (termtype == ERL_ATOM_EXT) {
EI_ENSURE(ei_decode_atom(buf, &index, path));
if (strlen(path) == 7 && strcmp(path, "default") == 0)
return process_load(result, NULL);
}
error(result, "badarg");
}
void process_load(ei_x_buff *result, char *path) {
if (magic_load_all(path) == 0) {
EI_ENSURE(ei_x_encode_atom(result, "ok"));
EI_ENSURE(ei_x_encode_atom(result, "loaded"));
} else {
EI_ENSURE(ei_x_encode_atom(result, "error"));
EI_ENSURE(ei_x_encode_atom(result, "not_loaded"));
}
send_and_free(result);
}
void setup_environment() { opterr = 0; }
void magic_close_all() {
magic_loaded = false;
if (magic_mime_encoding) {
magic_close(magic_mime_encoding);
magic_mime_encoding = NULL;
}
if (magic_mime_type) {
magic_close(magic_mime_type);
magic_mime_type = NULL;
}
if (magic_type_name) {
magic_close(magic_type_name);
magic_type_name = NULL;
}
}
void magic_open_all() {
magic_close_all();
magic_mime_encoding = magic_open(MAGIC_FLAGS_COMMON | MAGIC_MIME_ENCODING);
magic_mime_type = magic_open(MAGIC_FLAGS_COMMON | MAGIC_MIME_TYPE);
magic_type_name = magic_open(MAGIC_FLAGS_COMMON | MAGIC_NONE);
if (magic_mime_encoding && magic_mime_type && magic_type_name) {
ei_x_buff ok_buf;
EI_ENSURE(ei_x_new_with_version(&ok_buf));
EI_ENSURE(ei_x_encode_atom(&ok_buf, "ready"));
return send_and_free(&ok_buf);
}
exit(ERROR_MAGIC);
}
int magic_load_all(char *path) {
int res;
if ((res = magic_load(magic_mime_encoding, path)) != 0)
return res;
if ((res = magic_load(magic_mime_type, path)) != 0)
return res;
if ((res = magic_load(magic_type_name, path)) != 0)
return res;
magic_loaded = true;
return 0;
}
void process_bytes(char *path, int size, ei_x_buff *result) {
const char *mime_type_result = magic_buffer(magic_mime_type, path, size);
const int mime_type_errno = magic_errno(magic_mime_type);
if (mime_type_errno > 0)
return handle_magic_error(magic_mime_type, mime_type_errno, result);
const char *mime_encoding_result =
magic_buffer(magic_mime_encoding, path, size);
int mime_encoding_errno = magic_errno(magic_mime_encoding);
if (mime_encoding_errno > 0)
return handle_magic_error(magic_mime_encoding, mime_encoding_errno, result);
const char *type_name_result = magic_buffer(magic_type_name, path, size);
int type_name_errno = magic_errno(magic_type_name);
if (type_name_errno > 0)
return handle_magic_error(magic_type_name, type_name_errno, result);
EI_ENSURE(ei_x_encode_atom(result, "ok"));
EI_ENSURE(ei_x_encode_tuple_header(result, 3));
EI_ENSURE(
ei_x_encode_binary(result, mime_type_result, strlen(mime_type_result)));
EI_ENSURE(ei_x_encode_binary(result, mime_encoding_result,
strlen(mime_encoding_result)));
EI_ENSURE(
ei_x_encode_binary(result, type_name_result, strlen(type_name_result)));
send_and_free(result);
}
void handle_magic_error(magic_t handle, int errn, ei_x_buff *result) {
const char *error = magic_error(handle);
EI_ENSURE(ei_x_encode_atom(result, "error"));
EI_ENSURE(ei_x_encode_tuple_header(result, 2));
long errlon = (long)errn;
EI_ENSURE(ei_x_encode_long(result, errlon));
EI_ENSURE(ei_x_encode_binary(result, error, strlen(error)));
send_and_free(result);
}
void process_file(char *path, ei_x_buff *result) {
const char *mime_type_result = magic_file(magic_mime_type, path);
const int mime_type_errno = magic_errno(magic_mime_type);
if (mime_type_errno > 0)
return handle_magic_error(magic_mime_type, mime_type_errno, result);
const char *mime_encoding_result = magic_file(magic_mime_encoding, path);
int mime_encoding_errno = magic_errno(magic_mime_encoding);
if (mime_encoding_errno > 0)
return handle_magic_error(magic_mime_encoding, mime_encoding_errno, result);
const char *type_name_result = magic_file(magic_type_name, path);
int type_name_errno = magic_errno(magic_type_name);
if (type_name_errno > 0)
return handle_magic_error(magic_type_name, type_name_errno, result);
EI_ENSURE(ei_x_encode_atom(result, "ok"));
EI_ENSURE(ei_x_encode_tuple_header(result, 3));
EI_ENSURE(
ei_x_encode_binary(result, mime_type_result, strlen(mime_type_result)));
EI_ENSURE(ei_x_encode_binary(result, mime_encoding_result,
strlen(mime_encoding_result)));
EI_ENSURE(
ei_x_encode_binary(result, type_name_result, strlen(type_name_result)));
send_and_free(result);
}
// Adapted from https://erlang.org/doc/tutorial/erl_interface.html
// Changed `read_cmd`, the original one was buggy given some length (due to
// endinaness).
// TODO: Check if `write_cmd` exhibits the same issue.
size_t read_exact(byte *buf, size_t len) {
- int i, got = 0;
+ int i;
+ size_t got = 0;
do {
if ((i = read(0, buf + got, len - got)) <= 0) {
return (i);
}
got += i;
} while (got < len);
return (len);
}
size_t write_exact(byte *buf, size_t len) {
- int i, wrote = 0;
+ int i;
+ size_t wrote = 0;
do {
if ((i = write(1, buf + wrote, len - wrote)) <= 0)
return (i);
wrote += i;
} while (wrote < len);
return (len);
}
size_t read_cmd(byte *buf) {
int i;
if ((i = read(0, buf, sizeof(uint16_t))) <= 0) {
return (i);
}
uint16_t len16 = *(uint16_t *)buf;
len16 = ntohs(len16);
// Buffer isn't large enough: just return possible len, without reading.
// Up to the caller of verifying the size again and return an error.
// buf left unchanged, stdin emptied of X bytes.
if (len16 > COMMAND_LEN) {
fdseek(len16);
return len16;
}
return read_exact(buf, len16);
}
size_t write_cmd(byte *buf, size_t len) {
byte li;
li = (len >> 8) & 0xff;
write_exact(&li, 1);
li = len & 0xff;
write_exact(&li, 1);
return write_exact(buf, len);
}
void send_and_free(ei_x_buff *result) {
write_cmd(result->buff, result->index);
EI_ENSURE(ei_x_free(result));
}
void error(ei_x_buff *result, const char *error) {
EI_ENSURE(ei_x_encode_atom(result, "error"));
EI_ENSURE(ei_x_encode_atom(result, error));
send_and_free(result);
}
void fdseek(uint16_t count) {
int i = 0;
while (i < count) {
getchar();
i += 1;
}
}
diff --git a/test/majic/extension_test.exs b/test/majic/extension_test.exs
index 2e375a1..be9c644 100644
--- a/test/majic/extension_test.exs
+++ b/test/majic/extension_test.exs
@@ -1,33 +1,41 @@
defmodule Majic.ExtensionTest do
use ExUnit.Case
alias Majic.Extension
test "it fixes extensions" do
assert "Makefile" == Extension.fix("Makefile.txt", "text/x-makefile")
assert "cat.webp" == Extension.fix("cat.jpeg", "image/webp")
end
test "it appends extensions" do
assert "Makefile" == Extension.fix("Makefile.txt", "text/x-makefile", append: true)
assert "cat.jpeg.webp" == Extension.fix("cat.jpeg", "image/webp", append: true)
end
+ test "it appends extensions if none exist" do
+ assert "webpage.html" == Extension.fix("webpage", "text/html", append: true)
+ end
+
+ test "it does not append extension if none exist when appending not requested" do
+ assert "webpage" == Extension.fix("webpage", "text/html")
+ end
+
test "it uses subtype as extension" do
assert "Makefile.x-makefile" ==
Extension.fix("Makefile.txt", "text/x-makefile", subtype_as_extension: true)
assert "cat.webp" == Extension.fix("cat.jpeg", "image/webp", subtype_as_extension: true)
end
test "it appends and use subtype" do
assert "Makefile.txt.x-makefile" ==
Extension.fix("Makefile.txt", "text/x-makefile",
subtype_as_extension: true,
append: true
)
assert "cat.jpeg.webp" ==
Extension.fix("cat.jpeg", "image/webp", subtype_as_extension: true, append: true)
end
end

File Metadata

Mime Type
text/x-diff
Expires
Tue, Nov 26, 11:55 PM (1 d, 11 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
40514
Default Alt Text
(36 KB)

Event Timeline