diff options
Diffstat (limited to 'lib/pleroma/upload.ex')
-rw-r--r-- | lib/pleroma/upload.ex | 186 |
1 files changed, 112 insertions, 74 deletions
diff --git a/lib/pleroma/upload.ex b/lib/pleroma/upload.ex index 238630bf3..16043a264 100644 --- a/lib/pleroma/upload.ex +++ b/lib/pleroma/upload.ex @@ -1,81 +1,102 @@ defmodule Pleroma.Upload do alias Ecto.UUID + require Logger + + @type upload_option :: + {:dedupe, boolean()} | {:size_limit, non_neg_integer()} | {:uploader, module()} + @type upload_source :: + Plug.Upload.t() | data_uri_string() :: + String.t() | {:from_local, name :: String.t(), uuid :: String.t(), path :: String.t()} + + @spec store(upload_source, options :: [upload_option()]) :: {:ok, Map.t()} | {:error, any()} + def store(upload, opts \\ []) do + opts = get_opts(opts) + + with {:ok, name, uuid, path, content_type} <- process_upload(upload, opts), + _ <- strip_exif_data(content_type, path), + {:ok, url_spec} <- opts.uploader.put_file(name, uuid, path, content_type, opts) do + {:ok, + %{ + "type" => "Image", + "url" => [ + %{ + "type" => "Link", + "mediaType" => content_type, + "href" => url_from_spec(url_spec) + } + ], + "name" => name + }} + else + {:error, error} -> + Logger.error( + "#{__MODULE__} store (using #{inspect(opts.uploader)}) failed: #{inspect(error)}" + ) - def check_file_size(path, nil), do: true - - def check_file_size(path, size_limit) do - {:ok, %{size: size}} = File.stat(path) - size <= size_limit + {:error, error} + end end - def store(file, should_dedupe, size_limit \\ nil) - - def store(%Plug.Upload{} = file, should_dedupe, size_limit) do - content_type = get_content_type(file.path) - - with uuid <- get_uuid(file, should_dedupe), - name <- get_name(file, uuid, content_type, should_dedupe), - true <- check_file_size(file.path, size_limit) do - strip_exif_data(content_type, file.path) - - {:ok, url_path} = uploader().put_file(name, uuid, file.path, content_type, should_dedupe) + defp get_opts(opts) do + %{ + dedupe: Keyword.get(opts, :dedupe, Pleroma.Config.get([:instance, :dedupe_media])), + size_limit: Keyword.get(opts, :size_limit, Pleroma.Config.get([:instance, :upload_limit])), + uploader: Keyword.get(opts, :uploader, Pleroma.Config.get([__MODULE__, :uploader])) + } + end - %{ - "type" => "Document", - "url" => [ - %{ - "type" => "Link", - "mediaType" => content_type, - "href" => url_path - } - ], - "name" => name - } - else - _e -> nil + defp process_upload(%Plug.Upload{} = file, opts) do + with :ok <- check_file_size(file.path, opts.size_limit), + uuid <- get_uuid(file, opts.dedupe), + content_type <- get_content_type(file.path), + name <- get_name(file, uuid, content_type, opts.dedupe) do + {:ok, name, uuid, file.path, content_type} end end - def store(%{"img" => "data:image/" <> image_data}, should_dedupe, size_limit) do + defp process_upload(%{"img" => "data:image/" <> image_data}, opts) do parsed = Regex.named_captures(~r/(?<filetype>jpeg|png|gif);base64,(?<data>.*)/, image_data) data = Base.decode64!(parsed["data"], ignore: :whitespace) + hash = String.downcase(Base.encode16(:crypto.hash(:sha256, data))) - with tmp_path <- tempfile_for_image(data), + with :ok <- check_binary_size(data, opts.size_limit), + tmp_path <- tempfile_for_image(data), + content_type <- get_content_type(tmp_path), uuid <- UUID.generate(), - true <- check_file_size(tmp_path, size_limit) do - content_type = get_content_type(tmp_path) - strip_exif_data(content_type, tmp_path) - - name = - create_name( - String.downcase(Base.encode16(:crypto.hash(:sha256, data))), - parsed["filetype"], - content_type - ) + name <- create_name(hash, parsed["filetype"], content_type) do + {:ok, name, uuid, tmp_path, content_type} + end + end + + # For Mix.Tasks.MigrateLocalUploads + defp process_upload({:from_local, name, uuid, path}, _opts) do + with content_type <- get_content_type(path) do + {:ok, name, uuid, path, content_type} + end + end + + defp check_binary_size(binary, size_limit) + when is_integer(size_limit) and size_limit > 0 and byte_size(binary) >= size_limit do + {:error, :file_too_large} + end + + defp check_binary_size(_, _), do: :ok - {:ok, url_path} = uploader().put_file(name, uuid, tmp_path, content_type, should_dedupe) - - %{ - "type" => "Image", - "url" => [ - %{ - "type" => "Link", - "mediaType" => content_type, - "href" => url_path - } - ], - "name" => name - } + defp check_file_size(path, size_limit) when is_integer(size_limit) and size_limit > 0 do + with {:ok, %{size: size}} <- File.stat(path), + true <- size <= size_limit do + :ok else - _e -> nil + false -> {:error, :file_too_large} + error -> error end end - @doc """ - Creates a tempfile using the Plug.Upload Genserver which cleans them up - automatically. - """ - def tempfile_for_image(data) do + defp check_file_size(_, _), do: :ok + + # Creates a tempfile using the Plug.Upload Genserver which cleans them up + # automatically. + defp tempfile_for_image(data) do {:ok, tmp_path} = Plug.Upload.random_file("profile_pics") {:ok, tmp_file} = File.open(tmp_path, [:write, :raw, :binary]) IO.binwrite(tmp_file, data) @@ -83,7 +104,7 @@ defmodule Pleroma.Upload do tmp_path end - def strip_exif_data(content_type, file) do + defp strip_exif_data(content_type, file) do settings = Application.get_env(:pleroma, Pleroma.Upload) do_strip = Keyword.fetch!(settings, :strip_exif) [filetype, _ext] = String.split(content_type, "/") @@ -94,16 +115,20 @@ defmodule Pleroma.Upload do end defp create_name(uuid, ext, type) do - case type do - "application/octet-stream" -> - String.downcase(Enum.join([uuid, ext], ".")) + extension = + cond do + type == "application/octect-stream" -> ext + ext = mime_extension(ext) -> ext + true -> String.split(type, "/") |> List.last() + end - "audio/mpeg" -> - String.downcase(Enum.join([uuid, "mp3"], ".")) + [uuid, extension] + |> Enum.join(".") + |> String.downcase() + end - _ -> - String.downcase(Enum.join([uuid, List.last(String.split(type, "/"))], ".")) - end + defp mime_extension(type) do + List.first(MIME.extensions(type)) end defp get_uuid(file, should_dedupe) do @@ -127,11 +152,15 @@ defmodule Pleroma.Upload do Enum.join(parts) end - case type do - "application/octet-stream" -> file.filename - "audio/mpeg" -> new_filename <> ".mp3" - "image/jpeg" -> new_filename <> ".jpg" - _ -> Enum.join([new_filename, String.split(type, "/") |> List.last()], ".") + cond do + type == "application/octet-stream" -> + file.filename + + ext = mime_extension(type) -> + new_filename <> "." <> ext + + true -> + Enum.join([new_filename, String.split(type, "/") |> List.last()], ".") end end end @@ -187,4 +216,13 @@ defmodule Pleroma.Upload do defp uploader() do Pleroma.Config.get!([Pleroma.Upload, :uploader]) end + + defp url_from_spec({:file, path}) do + [Pleroma.Web.base_url(), "media", path] + |> Path.join() + end + + defp url_from_spec({:url, url}) do + url + end end |