Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions lib/waffle/definition/validation.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
defmodule Waffle.Definition.Validation do
@moduledoc """
File validation by MIME types

Validation.validate("mix.exs", ["text/x-ruby"])
=> :ok

Validation process:

1. We get content type of a file by the `file` utility

2. We check that `MIME` library recognizes such content type

3. Next, we check that returnted content type matches expected
extensions list for that particular content type

4. Finally, we check if this content type is allowed

"""

def validate(filepath, :all), do: :ok

def validate(filepath, valid_content_types) do
with {:ok, content_type} <- content_type(filepath),
:ok <- mime_is_valid(content_type),
:ok <- extension_matches_mime(filepath, content_type),
:ok <- mime_is_allowed(valid_content_types, content_type) do
:ok
else
{:error, message} -> {:error, message}
end
end

def mime_is_valid(content_type) do
if MIME.valid?(content_type) do
:ok
else
{:error, ["content type is invalid"]}
end
end

def extension_matches_mime(filepath, content_type) do
# TODO add custom extensions
if MIME.extensions(content_type)
|> Enum.member?(filepath |> Path.extname() |> String.downcase()) do
:ok
else
{:error, ["content type and extension doesn't match"]}
end
end

def mime_is_allowed(valid_content_types, content_type) do
if Enum.member?(valid_content_types, content_type) do
:ok
else
{:error, ["invalid file format"]}
end
end

def content_type(filepath) do
with true <- File.exists?(filepath),
{file_utility_output, 0} <- System.cmd("file", ["--mime", "--brief", filepath]) do
content_type =
Regex.named_captures(
~r/^(?<content_type>.+);/,
file_utility_output
)["content_type"]

{:ok, content_type}
else
{error, 1} ->
{:error, error}

false ->
"inode/x-empty"
end
end
end
3 changes: 3 additions & 0 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ defmodule Waffle.Mixfile do
[
{:hackney, "~> 1.9"},

# file validation
{:mime, "~> 1.2"},

# If using Amazon S3
{:ex_aws, "~> 2.1.2", optional: true},
{:ex_aws_s3, "~> 2.0", optional: true},
Expand Down
1 change: 1 addition & 0 deletions mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"makeup_elixir": {:hex, :makeup_elixir, "0.14.1", "4f0e96847c63c17841d42c08107405a005a2680eb9c7ccadfd757bd31dabccfb", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"},
"meck": {:hex, :meck, "0.8.13", "ffedb39f99b0b99703b8601c6f17c7f76313ee12de6b646e671e3188401f7866", [:rebar3], [], "hexpm"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"},
"mime": {:hex, :mime, "1.4.0", "5066f14944b470286146047d2f73518cf5cca82f8e4815cf35d196b58cf07c47", [:mix], [], "hexpm"},
"mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm"},
"mock": {:hex, :mock, "0.3.5", "feb81f52b8dcf0a0d65001d2fec459f6b6a8c22562d94a965862f6cc066b5431", [:mix], [{:meck, "~> 0.8.13", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm"},
"nimble_parsec": {:hex, :nimble_parsec, "0.6.0", "32111b3bf39137144abd7ba1cce0914533b2d16ef35e8abc5ec8be6122944263", [:mix], [], "hexpm"},
Expand Down