Convert StealEmoji to pack.json

This will decouple filenames from shortcodes and
allow more image formats to work instead of only
those included in the auto-load glob. (Albeit we
still saved other formats to disk, wasting space)

Furthermore, this will allow us to make
final URL paths infeasible to predict.
This commit is contained in:
Oneric 2024-03-08 03:06:40 +01:00
parent fa98b44acf
commit d1c4d07404
2 changed files with 56 additions and 18 deletions

View file

@ -92,7 +92,7 @@ defp unpack_zip_emojies(zip_files) do
end) end)
end end
@spec add_file(t(), String.t(), Path.t(), Plug.Upload.t()) :: @spec add_file(t(), String.t(), Path.t(), Plug.Upload.t() | binary()) ::
{:ok, t()} {:ok, t()}
| {:error, File.posix() | atom()} | {:error, File.posix() | atom()}
def add_file(%Pack{} = pack, _, _, %Plug.Upload{content_type: "application/zip"} = file) do def add_file(%Pack{} = pack, _, _, %Plug.Upload{content_type: "application/zip"} = file) do
@ -140,6 +140,14 @@ def add_file(%Pack{} = pack, _, _, %Plug.Upload{content_type: "application/zip"}
end end
def add_file(%Pack{} = pack, shortcode, filename, %Plug.Upload{} = file) do def add_file(%Pack{} = pack, shortcode, filename, %Plug.Upload{} = file) do
try_add_file(pack, shortcode, filename, file)
end
def add_file(%Pack{} = pack, shortcode, filename, filedata) when is_binary(filedata) do
try_add_file(pack, shortcode, filename, filedata)
end
defp try_add_file(%Pack{} = pack, shortcode, filename, file) do
with :ok <- validate_not_empty([shortcode, filename]), with :ok <- validate_not_empty([shortcode, filename]),
:ok <- validate_emoji_not_exists(shortcode), :ok <- validate_emoji_not_exists(shortcode),
{:ok, updated_pack} <- do_add_file(pack, shortcode, filename, file) do {:ok, updated_pack} <- do_add_file(pack, shortcode, filename, file) do
@ -485,6 +493,12 @@ defp save_file(%Plug.Upload{path: upload_path}, pack, filename) do
end end
end end
defp save_file(file_data, pack, filename) when is_binary(file_data) do
file_path = Path.join(pack.path, filename)
create_subdirs(file_path)
File.write(file_path, file_data, [:binary])
end
defp put_emoji(pack, shortcode, filename) do defp put_emoji(pack, shortcode, filename) do
files = Map.put(pack.files, shortcode, filename) files = Map.put(pack.files, shortcode, filename)
%{pack | files: files, files_count: length(Map.keys(files))} %{pack | files: files, files_count: length(Map.keys(files))}

View file

@ -6,10 +6,41 @@ defmodule Pleroma.Web.ActivityPub.MRF.StealEmojiPolicy do
require Logger require Logger
alias Pleroma.Config alias Pleroma.Config
alias Pleroma.Emoji.Pack
@moduledoc "Detect new emojis by their shortcode and steals them" @moduledoc "Detect new emojis by their shortcode and steals them"
@behaviour Pleroma.Web.ActivityPub.MRF.Policy @behaviour Pleroma.Web.ActivityPub.MRF.Policy
@pack_name "stolen"
defp create_pack() do
with {:ok, pack} = Pack.create(@pack_name) do
Pack.save_metadata(
%{
"description" => "Collection of emoji auto-stolen from other instances",
"homepage" => Pleroma.Web.Endpoint.url(),
"can-download" => false,
"share-files" => false
},
pack
)
end
end
defp load_or_create_pack() do
case Pack.load_pack(@pack_name) do
{:ok, pack} -> {:ok, pack}
{:error, :enoent} -> create_pack()
e -> e
end
end
defp add_emoji(shortcode, extension, filedata) do
{:ok, pack} = load_or_create_pack()
filename = shortcode <> "." <> extension
Pack.add_file(pack, shortcode, filename, filedata)
end
defp accept_host?(host), do: host in Config.get([:mrf_steal_emoji, :hosts], []) defp accept_host?(host), do: host in Config.get([:mrf_steal_emoji, :hosts], [])
defp shortcode_matches?(shortcode, pattern) when is_binary(pattern) do defp shortcode_matches?(shortcode, pattern) when is_binary(pattern) do
@ -33,15 +64,16 @@ defp reject_emoji?({shortcode, _url}, installed_emoji) do
!valid_shortcode? or rejected_shortcode? or emoji_installed? !valid_shortcode? or rejected_shortcode? or emoji_installed?
end end
defp steal_emoji(%{} = response, {shortcode, extension}, emoji_dir_path) do defp steal_emoji(%{} = response, {shortcode, extension}) do
file_path = Path.join(emoji_dir_path, shortcode <> "." <> extension) case add_emoji(shortcode, extension, response.body) do
{:ok, _} ->
case File.write(file_path, response.body) do
:ok ->
shortcode shortcode
e -> e ->
Logger.warning("MRF.StealEmojiPolicy: Failed to write to #{file_path}: #{inspect(e)}") Logger.warning(
"MRF.StealEmojiPolicy: Failed to add #{shortcode}.#{extension}: #{inspect(e)}"
)
nil nil
end end
end end
@ -56,7 +88,7 @@ defp get_extension_if_safe(response) do
end end
end end
defp maybe_steal_emoji({shortcode, url}, emoji_dir_path) do defp maybe_steal_emoji({shortcode, url}) do
url = Pleroma.Web.MediaProxy.url(url) url = Pleroma.Web.MediaProxy.url(url)
with {:ok, %{status: status} = response} when status in 200..299 <- Pleroma.HTTP.get(url) do with {:ok, %{status: status} = response} when status in 200..299 <- Pleroma.HTTP.get(url) do
@ -64,7 +96,7 @@ defp maybe_steal_emoji({shortcode, url}, emoji_dir_path) do
extension = get_extension_if_safe(response) extension = get_extension_if_safe(response)
if byte_size(response.body) <= size_limit and extension do if byte_size(response.body) <= size_limit and extension do
steal_emoji(response, {shortcode, extension}, emoji_dir_path) steal_emoji(response, {shortcode, extension})
else else
Logger.debug( Logger.debug(
"MRF.StealEmojiPolicy: :#{shortcode}: at #{url} (#{byte_size(response.body)} B) over size limit (#{size_limit} B)" "MRF.StealEmojiPolicy: :#{shortcode}: at #{url} (#{byte_size(response.body)} B) over size limit (#{size_limit} B)"
@ -86,18 +118,10 @@ def filter(%{"object" => %{"emoji" => foreign_emojis, "actor" => actor}} = messa
if host != Pleroma.Web.Endpoint.host() and accept_host?(host) do if host != Pleroma.Web.Endpoint.host() and accept_host?(host) do
installed_emoji = Pleroma.Emoji.get_all() |> Enum.map(fn {k, _} -> k end) installed_emoji = Pleroma.Emoji.get_all() |> Enum.map(fn {k, _} -> k end)
emoji_dir_path =
Config.get(
[:mrf_steal_emoji, :path],
Path.join(Config.get([:instance, :static_dir]), "emoji/stolen")
)
File.mkdir_p(emoji_dir_path)
new_emojis = new_emojis =
foreign_emojis foreign_emojis
|> Enum.reject(&reject_emoji?(&1, installed_emoji)) |> Enum.reject(&reject_emoji?(&1, installed_emoji))
|> Enum.map(&maybe_steal_emoji(&1, emoji_dir_path)) |> Enum.map(&maybe_steal_emoji(&1))
|> Enum.filter(& &1) |> Enum.filter(& &1)
if !Enum.empty?(new_emojis) do if !Enum.empty?(new_emojis) do