Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F85628350
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Size
48 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/lib/pleroma/emoji/pack.ex b/lib/pleroma/emoji/pack.ex
index 2dca21c93..787ff8141 100644
--- a/lib/pleroma/emoji/pack.ex
+++ b/lib/pleroma/emoji/pack.ex
@@ -1,571 +1,571 @@
defmodule Pleroma.Emoji.Pack do
@derive {Jason.Encoder, only: [:files, :pack, :files_count]}
defstruct files: %{},
files_count: 0,
pack_file: nil,
path: nil,
pack: %{},
name: nil
@type t() :: %__MODULE__{
files: %{String.t() => Path.t()},
files_count: non_neg_integer(),
pack_file: Path.t(),
path: Path.t(),
pack: map(),
name: String.t()
}
alias Pleroma.Emoji
@spec create(String.t()) :: {:ok, t()} | {:error, File.posix()} | {:error, :empty_values}
def create(name) do
with :ok <- validate_not_empty([name]),
dir <- Path.join(emoji_path(), name),
:ok <- File.mkdir(dir) do
%__MODULE__{pack_file: Path.join(dir, "pack.json")}
|> save_pack()
end
end
defp paginate(entities, 1, page_size), do: Enum.take(entities, page_size)
defp paginate(entities, page, page_size) do
entities
- |> Enum.take(page * page_size)
- |> Enum.take(-page_size)
+ |> Enum.chunk_every(page_size)
+ |> Enum.at(page - 1)
end
@spec show(keyword()) :: {:ok, t()} | {:error, atom()}
def show(opts) do
name = opts[:name]
with :ok <- validate_not_empty([name]),
{:ok, pack} <- load_pack(name) do
shortcodes =
pack.files
|> Map.keys()
|> paginate(opts[:page], opts[:page_size])
pack = Map.put(pack, :files, Map.take(pack.files, shortcodes))
{:ok, validate_pack(pack)}
end
end
@spec delete(String.t()) ::
{:ok, [binary()]} | {:error, File.posix(), binary()} | {:error, :empty_values}
def delete(name) do
with :ok <- validate_not_empty([name]) do
emoji_path()
|> Path.join(name)
|> File.rm_rf()
end
end
@spec add_file(String.t(), String.t(), Path.t(), Plug.Upload.t() | String.t()) ::
{:ok, t()} | {:error, File.posix() | atom()}
def add_file(name, shortcode, filename, file) do
with :ok <- validate_not_empty([name, shortcode, filename]),
:ok <- validate_emoji_not_exists(shortcode),
{:ok, pack} <- load_pack(name),
:ok <- save_file(file, pack, filename),
{:ok, updated_pack} <- pack |> put_emoji(shortcode, filename) |> save_pack() do
Emoji.reload()
{:ok, updated_pack}
end
end
@spec delete_file(String.t(), String.t()) ::
{:ok, t()} | {:error, File.posix() | atom()}
def delete_file(name, shortcode) do
with :ok <- validate_not_empty([name, shortcode]),
{:ok, pack} <- load_pack(name),
:ok <- remove_file(pack, shortcode),
{:ok, updated_pack} <- pack |> delete_emoji(shortcode) |> save_pack() do
Emoji.reload()
{:ok, updated_pack}
end
end
@spec update_file(String.t(), String.t(), String.t(), String.t(), boolean()) ::
{:ok, t()} | {:error, File.posix() | atom()}
def update_file(name, shortcode, new_shortcode, new_filename, force) do
with :ok <- validate_not_empty([name, shortcode, new_shortcode, new_filename]),
{:ok, pack} <- load_pack(name),
{:ok, filename} <- get_filename(pack, shortcode),
:ok <- validate_emoji_not_exists(new_shortcode, force),
:ok <- rename_file(pack, filename, new_filename),
{:ok, updated_pack} <-
pack
|> delete_emoji(shortcode)
|> put_emoji(new_shortcode, new_filename)
|> save_pack() do
Emoji.reload()
{:ok, updated_pack}
end
end
@spec import_from_filesystem() :: {:ok, [String.t()]} | {:error, File.posix() | atom()}
def import_from_filesystem do
emoji_path = emoji_path()
with {:ok, %{access: :read_write}} <- File.stat(emoji_path),
{:ok, results} <- File.ls(emoji_path) do
names =
results
|> Enum.map(&Path.join(emoji_path, &1))
|> Enum.reject(fn path ->
File.dir?(path) and File.exists?(Path.join(path, "pack.json"))
end)
|> Enum.map(&write_pack_contents/1)
|> Enum.reject(&is_nil/1)
{:ok, names}
else
{:ok, %{access: _}} -> {:error, :no_read_write}
e -> e
end
end
@spec list_remote(String.t()) :: {:ok, map()} | {:error, atom()}
def list_remote(url) do
uri = url |> String.trim() |> URI.parse()
with :ok <- validate_shareable_packs_available(uri) do
uri
|> URI.merge("/api/pleroma/emoji/packs")
|> http_get()
end
end
@spec list_local(keyword()) :: {:ok, map(), non_neg_integer()}
def list_local(opts) do
with {:ok, results} <- list_packs_dir() do
all_packs =
results
|> Enum.map(fn name ->
case load_pack(name) do
{:ok, pack} -> pack
_ -> nil
end
end)
|> Enum.reject(&is_nil/1)
packs =
all_packs
|> paginate(opts[:page], opts[:page_size])
|> Map.new(fn pack -> {pack.name, validate_pack(pack)} end)
{:ok, packs, length(all_packs)}
end
end
@spec get_archive(String.t()) :: {:ok, binary()} | {:error, atom()}
def get_archive(name) do
with {:ok, pack} <- load_pack(name),
:ok <- validate_downloadable(pack) do
{:ok, fetch_archive(pack)}
end
end
@spec download(String.t(), String.t(), String.t()) :: {:ok, t()} | {:error, atom()}
def download(name, url, as) do
uri = url |> String.trim() |> URI.parse()
with :ok <- validate_shareable_packs_available(uri),
{:ok, remote_pack} <- uri |> URI.merge("/api/pleroma/emoji/packs/#{name}") |> http_get(),
{:ok, %{sha: sha, url: url} = pack_info} <- fetch_pack_info(remote_pack, uri, name),
{:ok, archive} <- download_archive(url, sha),
pack <- copy_as(remote_pack, as || name),
{:ok, _} = unzip(archive, pack_info, remote_pack, pack) do
# Fallback can't contain a pack.json file, since that would cause the fallback-src-sha256
# in it to depend on itself
if pack_info[:fallback] do
save_pack(pack)
else
{:ok, pack}
end
end
end
@spec save_metadata(map(), t()) :: {:ok, t()} | {:error, File.posix()}
def save_metadata(metadata, %__MODULE__{} = pack) do
pack
|> Map.put(:pack, metadata)
|> save_pack()
end
@spec update_metadata(String.t(), map()) :: {:ok, t()} | {:error, File.posix()}
def update_metadata(name, data) do
with {:ok, pack} <- load_pack(name) do
if fallback_sha_changed?(pack, data) do
update_sha_and_save_metadata(pack, data)
else
save_metadata(data, pack)
end
end
end
@spec load_pack(String.t()) :: {:ok, t()} | {:error, :not_found}
def load_pack(name) do
pack_file = Path.join([emoji_path(), name, "pack.json"])
if File.exists?(pack_file) do
pack =
pack_file
|> File.read!()
|> from_json()
|> Map.put(:pack_file, pack_file)
|> Map.put(:path, Path.dirname(pack_file))
|> Map.put(:name, name)
files_count =
pack.files
|> Map.keys()
|> length()
{:ok, Map.put(pack, :files_count, files_count)}
else
{:error, :not_found}
end
end
@spec emoji_path() :: Path.t()
defp emoji_path do
[:instance, :static_dir]
|> Pleroma.Config.get!()
|> Path.join("emoji")
end
defp validate_emoji_not_exists(shortcode, force \\ false)
defp validate_emoji_not_exists(_shortcode, true), do: :ok
defp validate_emoji_not_exists(shortcode, _) do
case Emoji.get(shortcode) do
nil -> :ok
_ -> {:error, :already_exists}
end
end
defp write_pack_contents(path) do
pack = %__MODULE__{
files: files_from_path(path),
path: path,
pack_file: Path.join(path, "pack.json")
}
case save_pack(pack) do
{:ok, _pack} -> Path.basename(path)
_ -> nil
end
end
defp files_from_path(path) do
txt_path = Path.join(path, "emoji.txt")
if File.exists?(txt_path) do
# There's an emoji.txt file, it's likely from a pack installed by the pack manager.
# Make a pack.json file from the contents of that emoji.txt file
# FIXME: Copy-pasted from Pleroma.Emoji/load_from_file_stream/2
# Create a map of shortcodes to filenames from emoji.txt
txt_path
|> File.read!()
|> String.split("\n")
|> Enum.map(&String.trim/1)
|> Enum.map(fn line ->
case String.split(line, ~r/,\s*/) do
# This matches both strings with and without tags
# and we don't care about tags here
[name, file | _] ->
file_dir_name = Path.dirname(file)
if String.ends_with?(path, file_dir_name) do
{name, Path.basename(file)}
else
{name, file}
end
_ ->
nil
end
end)
|> Enum.reject(&is_nil/1)
|> Map.new()
else
# If there's no emoji.txt, assume all files
# that are of certain extensions from the config are emojis and import them all
pack_extensions = Pleroma.Config.get!([:emoji, :pack_extensions])
Emoji.Loader.make_shortcode_to_file_map(path, pack_extensions)
end
end
defp validate_pack(pack) do
info =
if downloadable?(pack) do
archive = fetch_archive(pack)
archive_sha = :crypto.hash(:sha256, archive) |> Base.encode16()
pack.pack
|> Map.put("can-download", true)
|> Map.put("download-sha256", archive_sha)
else
Map.put(pack.pack, "can-download", false)
end
Map.put(pack, :pack, info)
end
defp downloadable?(pack) do
# If the pack is set as shared, check if it can be downloaded
# That means that when asked, the pack can be packed and sent to the remote
# Otherwise, they'd have to download it from external-src
pack.pack["share-files"] &&
Enum.all?(pack.files, fn {_, file} ->
pack.path
|> Path.join(file)
|> File.exists?()
end)
end
defp create_archive_and_cache(pack, hash) do
files = ['pack.json' | Enum.map(pack.files, fn {_, file} -> to_charlist(file) end)]
{:ok, {_, result}} =
:zip.zip('#{pack.name}.zip', files, [:memory, cwd: to_charlist(pack.path)])
ttl_per_file = Pleroma.Config.get!([:emoji, :shared_pack_cache_seconds_per_file])
overall_ttl = :timer.seconds(ttl_per_file * Enum.count(files))
Cachex.put!(
:emoji_packs_cache,
pack.name,
# if pack.json MD5 changes, the cache is not valid anymore
%{hash: hash, pack_data: result},
# Add a minute to cache time for every file in the pack
ttl: overall_ttl
)
result
end
defp save_pack(pack) do
with {:ok, json} <- Jason.encode(pack, pretty: true),
:ok <- File.write(pack.pack_file, json) do
{:ok, pack}
end
end
defp from_json(json) do
map = Jason.decode!(json)
struct(__MODULE__, %{files: map["files"], pack: map["pack"]})
end
defp validate_shareable_packs_available(uri) do
with {:ok, %{"links" => links}} <- uri |> URI.merge("/.well-known/nodeinfo") |> http_get(),
# Get the actual nodeinfo address and fetch it
{:ok, %{"metadata" => %{"features" => features}}} <-
links |> List.last() |> Map.get("href") |> http_get() do
if Enum.member?(features, "shareable_emoji_packs") do
:ok
else
{:error, :not_shareable}
end
end
end
defp validate_not_empty(list) do
if Enum.all?(list, fn i -> is_binary(i) and i != "" end) do
:ok
else
{:error, :empty_values}
end
end
defp save_file(file, pack, filename) do
file_path = Path.join(pack.path, filename)
create_subdirs(file_path)
case file do
%Plug.Upload{path: upload_path} ->
# Copy the uploaded file from the temporary directory
with {:ok, _} <- File.copy(upload_path, file_path), do: :ok
url when is_binary(url) ->
# Download and write the file
file_contents = Tesla.get!(url).body
File.write(file_path, file_contents)
end
end
defp put_emoji(pack, shortcode, filename) do
files = Map.put(pack.files, shortcode, filename)
%{pack | files: files}
end
defp delete_emoji(pack, shortcode) do
files = Map.delete(pack.files, shortcode)
%{pack | files: files}
end
defp rename_file(pack, filename, new_filename) do
old_path = Path.join(pack.path, filename)
new_path = Path.join(pack.path, new_filename)
create_subdirs(new_path)
with :ok <- File.rename(old_path, new_path) do
remove_dir_if_empty(old_path, filename)
end
end
defp create_subdirs(file_path) do
if String.contains?(file_path, "/") do
file_path
|> Path.dirname()
|> File.mkdir_p!()
end
end
defp remove_file(pack, shortcode) do
with {:ok, filename} <- get_filename(pack, shortcode),
emoji <- Path.join(pack.path, filename),
:ok <- File.rm(emoji) do
remove_dir_if_empty(emoji, filename)
end
end
defp remove_dir_if_empty(emoji, filename) do
dir = Path.dirname(emoji)
if String.contains?(filename, "/") and File.ls!(dir) == [] do
File.rmdir!(dir)
else
:ok
end
end
defp get_filename(pack, shortcode) do
with %{^shortcode => filename} when is_binary(filename) <- pack.files,
true <- pack.path |> Path.join(filename) |> File.exists?() do
{:ok, filename}
else
_ -> {:error, :doesnt_exist}
end
end
defp http_get(%URI{} = url), do: url |> to_string() |> http_get()
defp http_get(url) do
with {:ok, %{body: body}} <- url |> Pleroma.HTTP.get() do
Jason.decode(body)
end
end
defp list_packs_dir do
emoji_path = emoji_path()
# Create the directory first if it does not exist. This is probably the first request made
# with the API so it should be sufficient
with {:create_dir, :ok} <- {:create_dir, File.mkdir_p(emoji_path)},
{:ls, {:ok, results}} <- {:ls, File.ls(emoji_path)} do
- {:ok, results}
+ {:ok, Enum.sort(results)}
else
{:create_dir, {:error, e}} -> {:error, :create_dir, e}
{:ls, {:error, e}} -> {:error, :ls, e}
end
end
defp validate_downloadable(pack) do
if downloadable?(pack), do: :ok, else: {:error, :cant_download}
end
defp copy_as(remote_pack, local_name) do
path = Path.join(emoji_path(), local_name)
%__MODULE__{
name: local_name,
path: path,
files: remote_pack["files"],
pack_file: Path.join(path, "pack.json")
}
end
defp unzip(archive, pack_info, remote_pack, local_pack) do
with :ok <- File.mkdir_p!(local_pack.path) do
files = Enum.map(remote_pack["files"], fn {_, path} -> to_charlist(path) end)
# Fallback cannot contain a pack.json file
files = if pack_info[:fallback], do: files, else: ['pack.json' | files]
:zip.unzip(archive, cwd: to_charlist(local_pack.path), file_list: files)
end
end
defp fetch_pack_info(remote_pack, uri, name) do
case remote_pack["pack"] do
%{"share-files" => true, "can-download" => true, "download-sha256" => sha} ->
{:ok,
%{
sha: sha,
url: URI.merge(uri, "/api/pleroma/emoji/packs/#{name}/archive") |> to_string()
}}
%{"fallback-src" => src, "fallback-src-sha256" => sha} when is_binary(src) ->
{:ok,
%{
sha: sha,
url: src,
fallback: true
}}
_ ->
{:error, "The pack was not set as shared and there is no fallback src to download from"}
end
end
defp download_archive(url, sha) do
with {:ok, %{body: archive}} <- Tesla.get(url) do
if Base.decode16!(sha) == :crypto.hash(:sha256, archive) do
{:ok, archive}
else
{:error, :invalid_checksum}
end
end
end
defp fetch_archive(pack) do
hash = :crypto.hash(:md5, File.read!(pack.pack_file))
case Cachex.get!(:emoji_packs_cache, pack.name) do
%{hash: ^hash, pack_data: archive} -> archive
_ -> create_archive_and_cache(pack, hash)
end
end
defp fallback_sha_changed?(pack, data) do
is_binary(data[:"fallback-src"]) and data[:"fallback-src"] != pack.pack["fallback-src"]
end
defp update_sha_and_save_metadata(pack, data) do
with {:ok, %{body: zip}} <- Tesla.get(data[:"fallback-src"]),
:ok <- validate_has_all_files(pack, zip) do
fallback_sha = :sha256 |> :crypto.hash(zip) |> Base.encode16()
data
|> Map.put("fallback-src-sha256", fallback_sha)
|> save_metadata(pack)
end
end
defp validate_has_all_files(pack, zip) do
with {:ok, f_list} <- :zip.unzip(zip, [:memory]) do
# Check if all files from the pack.json are in the archive
pack.files
|> Enum.all?(fn {_, from_manifest} ->
List.keyfind(f_list, to_charlist(from_manifest), 0)
end)
|> if(do: :ok, else: {:error, :incomplete})
end
end
end
diff --git a/test/web/pleroma_api/controllers/emoji_pack_controller_test.exs b/test/web/pleroma_api/controllers/emoji_pack_controller_test.exs
index 91312c832..df58a5eb6 100644
--- a/test/web/pleroma_api/controllers/emoji_pack_controller_test.exs
+++ b/test/web/pleroma_api/controllers/emoji_pack_controller_test.exs
@@ -1,840 +1,854 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.PleromaAPI.EmojiPackControllerTest do
use Pleroma.Web.ConnCase
import Tesla.Mock
import Pleroma.Factory
@emoji_path Path.join(
Pleroma.Config.get!([:instance, :static_dir]),
"emoji"
)
setup do: clear_config([:auth, :enforce_oauth_admin_scope_usage], false)
setup do
admin = insert(:user, is_admin: true)
token = insert(:oauth_admin_token, user: admin)
admin_conn =
build_conn()
|> assign(:user, admin)
|> assign(:token, token)
Pleroma.Emoji.reload()
{:ok, %{admin_conn: admin_conn}}
end
test "GET /api/pleroma/emoji/packs", %{conn: conn} do
resp = conn |> get("/api/pleroma/emoji/packs") |> json_response_and_validate_schema(200)
assert resp["count"] == 3
+
+ assert resp["packs"]
+ |> Map.keys()
+ |> length() == 3
+
shared = resp["packs"]["test_pack"]
assert shared["files"] == %{"blank" => "blank.png", "blank2" => "blank2.png"}
assert Map.has_key?(shared["pack"], "download-sha256")
assert shared["pack"]["can-download"]
assert shared["pack"]["share-files"]
non_shared = resp["packs"]["test_pack_nonshared"]
assert non_shared["pack"]["share-files"] == false
assert non_shared["pack"]["can-download"] == false
resp =
conn
|> get("/api/pleroma/emoji/packs?page_size=1")
|> json_response_and_validate_schema(200)
assert resp["count"] == 3
- [pack1] = Map.keys(resp["packs"])
+
+ packs = Map.keys(resp["packs"])
+
+ assert length(packs) == 1
+
+ [pack1] = packs
resp =
conn
|> get("/api/pleroma/emoji/packs?page_size=1&page=2")
|> json_response_and_validate_schema(200)
assert resp["count"] == 3
- [pack2] = Map.keys(resp["packs"])
+ packs = Map.keys(resp["packs"])
+ assert length(packs) == 1
+ [pack2] = packs
resp =
conn
|> get("/api/pleroma/emoji/packs?page_size=1&page=3")
|> json_response_and_validate_schema(200)
assert resp["count"] == 3
- [pack3] = Map.keys(resp["packs"])
+ packs = Map.keys(resp["packs"])
+ assert length(packs) == 1
+ [pack3] = packs
assert [pack1, pack2, pack3] |> Enum.uniq() |> length() == 3
end
describe "GET /api/pleroma/emoji/packs/remote" do
test "shareable instance", %{admin_conn: admin_conn, conn: conn} do
resp =
conn
|> get("/api/pleroma/emoji/packs")
|> json_response_and_validate_schema(200)
mock(fn
%{method: :get, url: "https://example.com/.well-known/nodeinfo"} ->
json(%{links: [%{href: "https://example.com/nodeinfo/2.1.json"}]})
%{method: :get, url: "https://example.com/nodeinfo/2.1.json"} ->
json(%{metadata: %{features: ["shareable_emoji_packs"]}})
%{method: :get, url: "https://example.com/api/pleroma/emoji/packs"} ->
json(resp)
end)
assert admin_conn
|> get("/api/pleroma/emoji/packs/remote?url=https://example.com")
|> json_response_and_validate_schema(200) == resp
end
test "non shareable instance", %{admin_conn: admin_conn} do
mock(fn
%{method: :get, url: "https://example.com/.well-known/nodeinfo"} ->
json(%{links: [%{href: "https://example.com/nodeinfo/2.1.json"}]})
%{method: :get, url: "https://example.com/nodeinfo/2.1.json"} ->
json(%{metadata: %{features: []}})
end)
assert admin_conn
|> get("/api/pleroma/emoji/packs/remote?url=https://example.com")
|> json_response_and_validate_schema(500) == %{
"error" => "The requested instance does not support sharing emoji packs"
}
end
end
describe "GET /api/pleroma/emoji/packs/:name/archive" do
test "download shared pack", %{conn: conn} do
resp =
conn
|> get("/api/pleroma/emoji/packs/test_pack/archive")
|> response(200)
{:ok, arch} = :zip.unzip(resp, [:memory])
assert Enum.find(arch, fn {n, _} -> n == 'pack.json' end)
assert Enum.find(arch, fn {n, _} -> n == 'blank.png' end)
end
test "non existing pack", %{conn: conn} do
assert conn
|> get("/api/pleroma/emoji/packs/test_pack_for_import/archive")
|> json_response_and_validate_schema(:not_found) == %{
"error" => "Pack test_pack_for_import does not exist"
}
end
test "non downloadable pack", %{conn: conn} do
assert conn
|> get("/api/pleroma/emoji/packs/test_pack_nonshared/archive")
|> json_response_and_validate_schema(:forbidden) == %{
"error" =>
"Pack test_pack_nonshared cannot be downloaded from this instance, either pack sharing was disabled for this pack or some files are missing"
}
end
end
describe "POST /api/pleroma/emoji/packs/download" do
test "shared pack from remote and non shared from fallback-src", %{
admin_conn: admin_conn,
conn: conn
} do
mock(fn
%{method: :get, url: "https://example.com/.well-known/nodeinfo"} ->
json(%{links: [%{href: "https://example.com/nodeinfo/2.1.json"}]})
%{method: :get, url: "https://example.com/nodeinfo/2.1.json"} ->
json(%{metadata: %{features: ["shareable_emoji_packs"]}})
%{
method: :get,
url: "https://example.com/api/pleroma/emoji/packs/test_pack"
} ->
conn
|> get("/api/pleroma/emoji/packs/test_pack")
|> json_response_and_validate_schema(200)
|> json()
%{
method: :get,
url: "https://example.com/api/pleroma/emoji/packs/test_pack/archive"
} ->
conn
|> get("/api/pleroma/emoji/packs/test_pack/archive")
|> response(200)
|> text()
%{
method: :get,
url: "https://example.com/api/pleroma/emoji/packs/test_pack_nonshared"
} ->
conn
|> get("/api/pleroma/emoji/packs/test_pack_nonshared")
|> json_response_and_validate_schema(200)
|> json()
%{
method: :get,
url: "https://nonshared-pack"
} ->
text(File.read!("#{@emoji_path}/test_pack_nonshared/nonshared.zip"))
end)
assert admin_conn
|> put_req_header("content-type", "multipart/form-data")
|> post("/api/pleroma/emoji/packs/download", %{
url: "https://example.com",
name: "test_pack",
as: "test_pack2"
})
|> json_response_and_validate_schema(200) == "ok"
assert File.exists?("#{@emoji_path}/test_pack2/pack.json")
assert File.exists?("#{@emoji_path}/test_pack2/blank.png")
assert admin_conn
|> delete("/api/pleroma/emoji/packs/test_pack2")
|> json_response_and_validate_schema(200) == "ok"
refute File.exists?("#{@emoji_path}/test_pack2")
assert admin_conn
|> put_req_header("content-type", "multipart/form-data")
|> post(
"/api/pleroma/emoji/packs/download",
%{
url: "https://example.com",
name: "test_pack_nonshared",
as: "test_pack_nonshared2"
}
)
|> json_response_and_validate_schema(200) == "ok"
assert File.exists?("#{@emoji_path}/test_pack_nonshared2/pack.json")
assert File.exists?("#{@emoji_path}/test_pack_nonshared2/blank.png")
assert admin_conn
|> delete("/api/pleroma/emoji/packs/test_pack_nonshared2")
|> json_response_and_validate_schema(200) == "ok"
refute File.exists?("#{@emoji_path}/test_pack_nonshared2")
end
test "nonshareable instance", %{admin_conn: admin_conn} do
mock(fn
%{method: :get, url: "https://old-instance/.well-known/nodeinfo"} ->
json(%{links: [%{href: "https://old-instance/nodeinfo/2.1.json"}]})
%{method: :get, url: "https://old-instance/nodeinfo/2.1.json"} ->
json(%{metadata: %{features: []}})
end)
assert admin_conn
|> put_req_header("content-type", "multipart/form-data")
|> post(
"/api/pleroma/emoji/packs/download",
%{
url: "https://old-instance",
name: "test_pack",
as: "test_pack2"
}
)
|> json_response_and_validate_schema(500) == %{
"error" => "The requested instance does not support sharing emoji packs"
}
end
test "checksum fail", %{admin_conn: admin_conn} do
mock(fn
%{method: :get, url: "https://example.com/.well-known/nodeinfo"} ->
json(%{links: [%{href: "https://example.com/nodeinfo/2.1.json"}]})
%{method: :get, url: "https://example.com/nodeinfo/2.1.json"} ->
json(%{metadata: %{features: ["shareable_emoji_packs"]}})
%{
method: :get,
url: "https://example.com/api/pleroma/emoji/packs/pack_bad_sha"
} ->
{:ok, pack} = Pleroma.Emoji.Pack.load_pack("pack_bad_sha")
%Tesla.Env{status: 200, body: Jason.encode!(pack)}
%{
method: :get,
url: "https://example.com/api/pleroma/emoji/packs/pack_bad_sha/archive"
} ->
%Tesla.Env{
status: 200,
body: File.read!("test/instance_static/emoji/pack_bad_sha/pack_bad_sha.zip")
}
end)
assert admin_conn
|> put_req_header("content-type", "multipart/form-data")
|> post("/api/pleroma/emoji/packs/download", %{
url: "https://example.com",
name: "pack_bad_sha",
as: "pack_bad_sha2"
})
|> json_response_and_validate_schema(:internal_server_error) == %{
"error" => "SHA256 for the pack doesn't match the one sent by the server"
}
end
test "other error", %{admin_conn: admin_conn} do
mock(fn
%{method: :get, url: "https://example.com/.well-known/nodeinfo"} ->
json(%{links: [%{href: "https://example.com/nodeinfo/2.1.json"}]})
%{method: :get, url: "https://example.com/nodeinfo/2.1.json"} ->
json(%{metadata: %{features: ["shareable_emoji_packs"]}})
%{
method: :get,
url: "https://example.com/api/pleroma/emoji/packs/test_pack"
} ->
{:ok, pack} = Pleroma.Emoji.Pack.load_pack("test_pack")
%Tesla.Env{status: 200, body: Jason.encode!(pack)}
end)
assert admin_conn
|> put_req_header("content-type", "multipart/form-data")
|> post("/api/pleroma/emoji/packs/download", %{
url: "https://example.com",
name: "test_pack",
as: "test_pack2"
})
|> json_response_and_validate_schema(:internal_server_error) == %{
"error" =>
"The pack was not set as shared and there is no fallback src to download from"
}
end
end
describe "PATCH /api/pleroma/emoji/packs/:name" do
setup do
pack_file = "#{@emoji_path}/test_pack/pack.json"
original_content = File.read!(pack_file)
on_exit(fn ->
File.write!(pack_file, original_content)
end)
{:ok,
pack_file: pack_file,
new_data: %{
"license" => "Test license changed",
"homepage" => "https://pleroma.social",
"description" => "Test description",
"share-files" => false
}}
end
test "for a pack without a fallback source", ctx do
assert ctx[:admin_conn]
|> put_req_header("content-type", "multipart/form-data")
|> patch("/api/pleroma/emoji/packs/test_pack", %{"metadata" => ctx[:new_data]})
|> json_response_and_validate_schema(200) == ctx[:new_data]
assert Jason.decode!(File.read!(ctx[:pack_file]))["pack"] == ctx[:new_data]
end
test "for a pack with a fallback source", ctx do
mock(fn
%{
method: :get,
url: "https://nonshared-pack"
} ->
text(File.read!("#{@emoji_path}/test_pack_nonshared/nonshared.zip"))
end)
new_data = Map.put(ctx[:new_data], "fallback-src", "https://nonshared-pack")
new_data_with_sha =
Map.put(
new_data,
"fallback-src-sha256",
"1967BB4E42BCC34BCC12D57BE7811D3B7BE52F965BCE45C87BD377B9499CE11D"
)
assert ctx[:admin_conn]
|> put_req_header("content-type", "multipart/form-data")
|> patch("/api/pleroma/emoji/packs/test_pack", %{metadata: new_data})
|> json_response_and_validate_schema(200) == new_data_with_sha
assert Jason.decode!(File.read!(ctx[:pack_file]))["pack"] == new_data_with_sha
end
test "when the fallback source doesn't have all the files", ctx do
mock(fn
%{
method: :get,
url: "https://nonshared-pack"
} ->
{:ok, {'empty.zip', empty_arch}} = :zip.zip('empty.zip', [], [:memory])
text(empty_arch)
end)
new_data = Map.put(ctx[:new_data], "fallback-src", "https://nonshared-pack")
assert ctx[:admin_conn]
|> put_req_header("content-type", "multipart/form-data")
|> patch("/api/pleroma/emoji/packs/test_pack", %{metadata: new_data})
|> json_response_and_validate_schema(:bad_request) == %{
"error" => "The fallback archive does not have all files specified in pack.json"
}
end
end
describe "POST/PATCH/DELETE /api/pleroma/emoji/packs/:name/files" do
setup do
pack_file = "#{@emoji_path}/test_pack/pack.json"
original_content = File.read!(pack_file)
on_exit(fn ->
File.write!(pack_file, original_content)
end)
:ok
end
test "create shortcode exists", %{admin_conn: admin_conn} do
assert admin_conn
|> put_req_header("content-type", "multipart/form-data")
|> post("/api/pleroma/emoji/packs/test_pack/files", %{
shortcode: "blank",
filename: "dir/blank.png",
file: %Plug.Upload{
filename: "blank.png",
path: "#{@emoji_path}/test_pack/blank.png"
}
})
|> json_response_and_validate_schema(:conflict) == %{
"error" => "An emoji with the \"blank\" shortcode already exists"
}
end
test "don't rewrite old emoji", %{admin_conn: admin_conn} do
on_exit(fn -> File.rm_rf!("#{@emoji_path}/test_pack/dir/") end)
assert admin_conn
|> put_req_header("content-type", "multipart/form-data")
|> post("/api/pleroma/emoji/packs/test_pack/files", %{
shortcode: "blank3",
filename: "dir/blank.png",
file: %Plug.Upload{
filename: "blank.png",
path: "#{@emoji_path}/test_pack/blank.png"
}
})
|> json_response_and_validate_schema(200) == %{
"blank" => "blank.png",
"blank2" => "blank2.png",
"blank3" => "dir/blank.png"
}
assert File.exists?("#{@emoji_path}/test_pack/dir/blank.png")
assert admin_conn
|> put_req_header("content-type", "multipart/form-data")
|> patch("/api/pleroma/emoji/packs/test_pack/files", %{
shortcode: "blank",
new_shortcode: "blank2",
new_filename: "dir_2/blank_3.png"
})
|> json_response_and_validate_schema(:conflict) == %{
"error" =>
"New shortcode \"blank2\" is already used. If you want to override emoji use 'force' option"
}
end
test "rewrite old emoji with force option", %{admin_conn: admin_conn} do
on_exit(fn -> File.rm_rf!("#{@emoji_path}/test_pack/dir_2/") end)
assert admin_conn
|> put_req_header("content-type", "multipart/form-data")
|> post("/api/pleroma/emoji/packs/test_pack/files", %{
shortcode: "blank3",
filename: "dir/blank.png",
file: %Plug.Upload{
filename: "blank.png",
path: "#{@emoji_path}/test_pack/blank.png"
}
})
|> json_response_and_validate_schema(200) == %{
"blank" => "blank.png",
"blank2" => "blank2.png",
"blank3" => "dir/blank.png"
}
assert File.exists?("#{@emoji_path}/test_pack/dir/blank.png")
assert admin_conn
|> put_req_header("content-type", "multipart/form-data")
|> patch("/api/pleroma/emoji/packs/test_pack/files", %{
shortcode: "blank3",
new_shortcode: "blank4",
new_filename: "dir_2/blank_3.png",
force: true
})
|> json_response_and_validate_schema(200) == %{
"blank" => "blank.png",
"blank2" => "blank2.png",
"blank4" => "dir_2/blank_3.png"
}
assert File.exists?("#{@emoji_path}/test_pack/dir_2/blank_3.png")
end
test "with empty filename", %{admin_conn: admin_conn} do
assert admin_conn
|> put_req_header("content-type", "multipart/form-data")
|> post("/api/pleroma/emoji/packs/test_pack/files", %{
shortcode: "blank2",
filename: "",
file: %Plug.Upload{
filename: "blank.png",
path: "#{@emoji_path}/test_pack/blank.png"
}
})
|> json_response_and_validate_schema(:bad_request) == %{
"error" => "pack name, shortcode or filename cannot be empty"
}
end
test "add file with not loaded pack", %{admin_conn: admin_conn} do
assert admin_conn
|> put_req_header("content-type", "multipart/form-data")
|> post("/api/pleroma/emoji/packs/not_loaded/files", %{
shortcode: "blank3",
filename: "dir/blank.png",
file: %Plug.Upload{
filename: "blank.png",
path: "#{@emoji_path}/test_pack/blank.png"
}
})
|> json_response_and_validate_schema(:bad_request) == %{
"error" => "pack \"not_loaded\" is not found"
}
end
test "remove file with not loaded pack", %{admin_conn: admin_conn} do
assert admin_conn
|> delete("/api/pleroma/emoji/packs/not_loaded/files?shortcode=blank3")
|> json_response_and_validate_schema(:bad_request) == %{
"error" => "pack \"not_loaded\" is not found"
}
end
test "remove file with empty shortcode", %{admin_conn: admin_conn} do
assert admin_conn
|> delete("/api/pleroma/emoji/packs/not_loaded/files?shortcode=")
|> json_response_and_validate_schema(:bad_request) == %{
"error" => "pack name or shortcode cannot be empty"
}
end
test "update file with not loaded pack", %{admin_conn: admin_conn} do
assert admin_conn
|> put_req_header("content-type", "multipart/form-data")
|> patch("/api/pleroma/emoji/packs/not_loaded/files", %{
shortcode: "blank4",
new_shortcode: "blank3",
new_filename: "dir_2/blank_3.png"
})
|> json_response_and_validate_schema(:bad_request) == %{
"error" => "pack \"not_loaded\" is not found"
}
end
test "new with shortcode as file with update", %{admin_conn: admin_conn} do
assert admin_conn
|> put_req_header("content-type", "multipart/form-data")
|> post("/api/pleroma/emoji/packs/test_pack/files", %{
shortcode: "blank4",
filename: "dir/blank.png",
file: %Plug.Upload{
filename: "blank.png",
path: "#{@emoji_path}/test_pack/blank.png"
}
})
|> json_response_and_validate_schema(200) == %{
"blank" => "blank.png",
"blank4" => "dir/blank.png",
"blank2" => "blank2.png"
}
assert File.exists?("#{@emoji_path}/test_pack/dir/blank.png")
assert admin_conn
|> put_req_header("content-type", "multipart/form-data")
|> patch("/api/pleroma/emoji/packs/test_pack/files", %{
shortcode: "blank4",
new_shortcode: "blank3",
new_filename: "dir_2/blank_3.png"
})
|> json_response_and_validate_schema(200) == %{
"blank3" => "dir_2/blank_3.png",
"blank" => "blank.png",
"blank2" => "blank2.png"
}
refute File.exists?("#{@emoji_path}/test_pack/dir/")
assert File.exists?("#{@emoji_path}/test_pack/dir_2/blank_3.png")
assert admin_conn
|> delete("/api/pleroma/emoji/packs/test_pack/files?shortcode=blank3")
|> json_response_and_validate_schema(200) == %{
"blank" => "blank.png",
"blank2" => "blank2.png"
}
refute File.exists?("#{@emoji_path}/test_pack/dir_2/")
on_exit(fn -> File.rm_rf!("#{@emoji_path}/test_pack/dir") end)
end
test "new with shortcode from url", %{admin_conn: admin_conn} do
mock(fn
%{
method: :get,
url: "https://test-blank/blank_url.png"
} ->
text(File.read!("#{@emoji_path}/test_pack/blank.png"))
end)
assert admin_conn
|> put_req_header("content-type", "multipart/form-data")
|> post("/api/pleroma/emoji/packs/test_pack/files", %{
shortcode: "blank_url",
file: "https://test-blank/blank_url.png"
})
|> json_response_and_validate_schema(200) == %{
"blank_url" => "blank_url.png",
"blank" => "blank.png",
"blank2" => "blank2.png"
}
assert File.exists?("#{@emoji_path}/test_pack/blank_url.png")
on_exit(fn -> File.rm_rf!("#{@emoji_path}/test_pack/blank_url.png") end)
end
test "new without shortcode", %{admin_conn: admin_conn} do
on_exit(fn -> File.rm_rf!("#{@emoji_path}/test_pack/shortcode.png") end)
assert admin_conn
|> put_req_header("content-type", "multipart/form-data")
|> post("/api/pleroma/emoji/packs/test_pack/files", %{
file: %Plug.Upload{
filename: "shortcode.png",
path: "#{Pleroma.Config.get([:instance, :static_dir])}/add/shortcode.png"
}
})
|> json_response_and_validate_schema(200) == %{
"shortcode" => "shortcode.png",
"blank" => "blank.png",
"blank2" => "blank2.png"
}
end
test "remove non existing shortcode in pack.json", %{admin_conn: admin_conn} do
assert admin_conn
|> delete("/api/pleroma/emoji/packs/test_pack/files?shortcode=blank3")
|> json_response_and_validate_schema(:bad_request) == %{
"error" => "Emoji \"blank3\" does not exist"
}
end
test "update non existing emoji", %{admin_conn: admin_conn} do
assert admin_conn
|> put_req_header("content-type", "multipart/form-data")
|> patch("/api/pleroma/emoji/packs/test_pack/files", %{
shortcode: "blank3",
new_shortcode: "blank4",
new_filename: "dir_2/blank_3.png"
})
|> json_response_and_validate_schema(:bad_request) == %{
"error" => "Emoji \"blank3\" does not exist"
}
end
test "update with empty shortcode", %{admin_conn: admin_conn} do
assert %{
"error" => "Missing field: new_shortcode."
} =
admin_conn
|> put_req_header("content-type", "multipart/form-data")
|> patch("/api/pleroma/emoji/packs/test_pack/files", %{
shortcode: "blank",
new_filename: "dir_2/blank_3.png"
})
|> json_response_and_validate_schema(:bad_request)
end
end
describe "POST/DELETE /api/pleroma/emoji/packs/:name" do
test "creating and deleting a pack", %{admin_conn: admin_conn} do
assert admin_conn
|> post("/api/pleroma/emoji/packs/test_created")
|> json_response_and_validate_schema(200) == "ok"
assert File.exists?("#{@emoji_path}/test_created/pack.json")
assert Jason.decode!(File.read!("#{@emoji_path}/test_created/pack.json")) == %{
"pack" => %{},
"files" => %{},
"files_count" => 0
}
assert admin_conn
|> delete("/api/pleroma/emoji/packs/test_created")
|> json_response_and_validate_schema(200) == "ok"
refute File.exists?("#{@emoji_path}/test_created/pack.json")
end
test "if pack exists", %{admin_conn: admin_conn} do
path = Path.join(@emoji_path, "test_created")
File.mkdir(path)
pack_file = Jason.encode!(%{files: %{}, pack: %{}})
File.write!(Path.join(path, "pack.json"), pack_file)
assert admin_conn
|> post("/api/pleroma/emoji/packs/test_created")
|> json_response_and_validate_schema(:conflict) == %{
"error" => "A pack named \"test_created\" already exists"
}
on_exit(fn -> File.rm_rf(path) end)
end
test "with empty name", %{admin_conn: admin_conn} do
assert admin_conn
|> post("/api/pleroma/emoji/packs/ ")
|> json_response_and_validate_schema(:bad_request) == %{
"error" => "pack name cannot be empty"
}
end
end
test "deleting nonexisting pack", %{admin_conn: admin_conn} do
assert admin_conn
|> delete("/api/pleroma/emoji/packs/non_existing")
|> json_response_and_validate_schema(:not_found) == %{
"error" => "Pack non_existing does not exist"
}
end
test "deleting with empty name", %{admin_conn: admin_conn} do
assert admin_conn
|> delete("/api/pleroma/emoji/packs/ ")
|> json_response_and_validate_schema(:bad_request) == %{
"error" => "pack name cannot be empty"
}
end
test "filesystem import", %{admin_conn: admin_conn, conn: conn} do
on_exit(fn ->
File.rm!("#{@emoji_path}/test_pack_for_import/emoji.txt")
File.rm!("#{@emoji_path}/test_pack_for_import/pack.json")
end)
resp = conn |> get("/api/pleroma/emoji/packs") |> json_response_and_validate_schema(200)
refute Map.has_key?(resp["packs"], "test_pack_for_import")
assert admin_conn
|> get("/api/pleroma/emoji/packs/import")
|> json_response_and_validate_schema(200) == ["test_pack_for_import"]
resp = conn |> get("/api/pleroma/emoji/packs") |> json_response_and_validate_schema(200)
assert resp["packs"]["test_pack_for_import"]["files"] == %{"blank" => "blank.png"}
File.rm!("#{@emoji_path}/test_pack_for_import/pack.json")
refute File.exists?("#{@emoji_path}/test_pack_for_import/pack.json")
emoji_txt_content = """
blank, blank.png, Fun
blank2, blank.png
foo, /emoji/test_pack_for_import/blank.png
bar
"""
File.write!("#{@emoji_path}/test_pack_for_import/emoji.txt", emoji_txt_content)
assert admin_conn
|> get("/api/pleroma/emoji/packs/import")
|> json_response_and_validate_schema(200) == ["test_pack_for_import"]
resp = conn |> get("/api/pleroma/emoji/packs") |> json_response_and_validate_schema(200)
assert resp["packs"]["test_pack_for_import"]["files"] == %{
"blank" => "blank.png",
"blank2" => "blank.png",
"foo" => "blank.png"
}
end
describe "GET /api/pleroma/emoji/packs/:name" do
test "shows pack.json", %{conn: conn} do
assert %{
"files" => files,
"files_count" => 2,
"pack" => %{
"can-download" => true,
"description" => "Test description",
"download-sha256" => _,
"homepage" => "https://pleroma.social",
"license" => "Test license",
"share-files" => true
}
} =
conn
|> get("/api/pleroma/emoji/packs/test_pack")
|> json_response_and_validate_schema(200)
assert files == %{"blank" => "blank.png", "blank2" => "blank2.png"}
assert %{
"files" => files,
"files_count" => 2
} =
conn
|> get("/api/pleroma/emoji/packs/test_pack?page_size=1")
|> json_response_and_validate_schema(200)
assert files |> Map.keys() |> length() == 1
assert %{
"files" => files,
"files_count" => 2
} =
conn
|> get("/api/pleroma/emoji/packs/test_pack?page_size=1&page=2")
|> json_response_and_validate_schema(200)
assert files |> Map.keys() |> length() == 1
end
test "non existing pack", %{conn: conn} do
assert conn
|> get("/api/pleroma/emoji/packs/non_existing")
|> json_response_and_validate_schema(:not_found) == %{
"error" => "Pack non_existing does not exist"
}
end
test "error name", %{conn: conn} do
assert conn
|> get("/api/pleroma/emoji/packs/ ")
|> json_response_and_validate_schema(:bad_request) == %{
"error" => "pack name cannot be empty"
}
end
end
end
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sat, Aug 8, 12:22 PM (1 d, 10 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1723226
Default Alt Text
(48 KB)
Attached To
Mode
rPUBE pleroma-upstream
Attached
Detach File
Event Timeline
Log In to Comment