mirror of
https://git.pleroma.social/pleroma/pleroma.git
synced 2025-03-12 22:52:41 +00:00
Emoji, Pack, Backup, Frontend: Use SafeZip
This commit is contained in:
parent
b89070a6ad
commit
2fcb90f369
4 changed files with 71 additions and 83 deletions
|
@ -93,6 +93,7 @@ defmodule Mix.Tasks.Pleroma.Emoji do
|
||||||
)
|
)
|
||||||
|
|
||||||
files = fetch_and_decode!(files_loc)
|
files = fetch_and_decode!(files_loc)
|
||||||
|
files_to_unzip = for({_, f} <- files, do: f)
|
||||||
|
|
||||||
IO.puts(IO.ANSI.format(["Unpacking ", :bright, pack_name]))
|
IO.puts(IO.ANSI.format(["Unpacking ", :bright, pack_name]))
|
||||||
|
|
||||||
|
@ -103,17 +104,7 @@ defmodule Mix.Tasks.Pleroma.Emoji do
|
||||||
pack_name
|
pack_name
|
||||||
])
|
])
|
||||||
|
|
||||||
files_to_unzip =
|
{:ok, _} = Pleroma.SafeZip.unzip_data(binary_archive, pack_path, files_to_unzip)
|
||||||
Enum.map(
|
|
||||||
files,
|
|
||||||
fn {_, f} -> to_charlist(f) end
|
|
||||||
)
|
|
||||||
|
|
||||||
{:ok, _} =
|
|
||||||
:zip.unzip(binary_archive,
|
|
||||||
cwd: String.to_charlist(pack_path),
|
|
||||||
file_list: files_to_unzip
|
|
||||||
)
|
|
||||||
|
|
||||||
IO.puts(IO.ANSI.format(["Writing pack.json for ", :bright, pack_name]))
|
IO.puts(IO.ANSI.format(["Writing pack.json for ", :bright, pack_name]))
|
||||||
|
|
||||||
|
@ -201,7 +192,7 @@ defmodule Mix.Tasks.Pleroma.Emoji do
|
||||||
|
|
||||||
tmp_pack_dir = Path.join(System.tmp_dir!(), "emoji-pack-#{name}")
|
tmp_pack_dir = Path.join(System.tmp_dir!(), "emoji-pack-#{name}")
|
||||||
|
|
||||||
{:ok, _} = :zip.unzip(binary_archive, cwd: String.to_charlist(tmp_pack_dir))
|
{:ok, _} = Pleroma.SafeZip.unzip_data(binary_archive, tmp_pack_dir)
|
||||||
|
|
||||||
emoji_map = Pleroma.Emoji.Loader.make_shortcode_to_file_map(tmp_pack_dir, exts)
|
emoji_map = Pleroma.Emoji.Loader.make_shortcode_to_file_map(tmp_pack_dir, exts)
|
||||||
|
|
||||||
|
|
|
@ -25,11 +25,12 @@ defmodule Pleroma.Emoji.Pack do
|
||||||
alias Pleroma.Emoji
|
alias Pleroma.Emoji
|
||||||
alias Pleroma.Emoji.Pack
|
alias Pleroma.Emoji.Pack
|
||||||
alias Pleroma.Utils
|
alias Pleroma.Utils
|
||||||
|
alias Pleroma.SafeZip
|
||||||
|
|
||||||
@spec create(String.t()) :: {:ok, t()} | {:error, File.posix()} | {:error, :empty_values}
|
@spec create(String.t()) :: {:ok, t()} | {:error, File.posix()} | {:error, :empty_values}
|
||||||
def create(name) do
|
def create(name) do
|
||||||
with :ok <- validate_not_empty([name]),
|
with :ok <- validate_not_empty([name]),
|
||||||
dir <- Path.join(emoji_path(), name),
|
dir <- path_join_name_safe(emoji_path(), name),
|
||||||
:ok <- File.mkdir(dir) do
|
:ok <- File.mkdir(dir) do
|
||||||
save_pack(%__MODULE__{pack_file: Path.join(dir, "pack.json")})
|
save_pack(%__MODULE__{pack_file: Path.join(dir, "pack.json")})
|
||||||
end
|
end
|
||||||
|
@ -65,43 +66,21 @@ defmodule Pleroma.Emoji.Pack do
|
||||||
{:ok, [binary()]} | {:error, File.posix(), binary()} | {:error, :empty_values}
|
{:ok, [binary()]} | {:error, File.posix(), binary()} | {:error, :empty_values}
|
||||||
def delete(name) do
|
def delete(name) do
|
||||||
with :ok <- validate_not_empty([name]),
|
with :ok <- validate_not_empty([name]),
|
||||||
pack_path <- Path.join(emoji_path(), name) do
|
pack_path <- path_join_name_safe(emoji_path(), name) do
|
||||||
File.rm_rf(pack_path)
|
File.rm_rf(pack_path)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec unpack_zip_emojies(list(tuple())) :: list(map())
|
|
||||||
defp unpack_zip_emojies(zip_files) do
|
|
||||||
Enum.reduce(zip_files, [], fn
|
|
||||||
{_, path, s, _, _, _}, acc when elem(s, 2) == :regular ->
|
|
||||||
with(
|
|
||||||
filename <- Path.basename(path),
|
|
||||||
shortcode <- Path.basename(filename, Path.extname(filename)),
|
|
||||||
false <- Emoji.exist?(shortcode)
|
|
||||||
) do
|
|
||||||
[%{path: path, filename: path, shortcode: shortcode} | acc]
|
|
||||||
else
|
|
||||||
_ -> acc
|
|
||||||
end
|
|
||||||
|
|
||||||
_, acc ->
|
|
||||||
acc
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
@spec add_file(t(), String.t(), Path.t(), Plug.Upload.t()) ::
|
@spec add_file(t(), String.t(), Path.t(), Plug.Upload.t()) ::
|
||||||
{: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
|
||||||
with {:ok, zip_files} <- :zip.table(to_charlist(file.path)),
|
with {:ok, zip_files} <- SafeZip.list_dir_file(file.path),
|
||||||
[_ | _] = emojies <- unpack_zip_emojies(zip_files),
|
[_ | _] = emojies <- map_zip_emojies(zip_files),
|
||||||
{:ok, tmp_dir} <- Utils.tmp_dir("emoji") do
|
{:ok, tmp_dir} <- Utils.tmp_dir("emoji") do
|
||||||
try do
|
try do
|
||||||
{:ok, _emoji_files} =
|
{:ok, _emoji_files} =
|
||||||
:zip.unzip(
|
SafeZip.unzip_file(file.path, tmp_dir, Enum.map(emojies, & &1[:path]))
|
||||||
to_charlist(file.path),
|
|
||||||
[{:file_list, Enum.map(emojies, & &1[:path])}, {:cwd, String.to_charlist(tmp_dir)}]
|
|
||||||
)
|
|
||||||
|
|
||||||
{_, updated_pack} =
|
{_, updated_pack} =
|
||||||
Enum.map_reduce(emojies, pack, fn item, emoji_pack ->
|
Enum.map_reduce(emojies, pack, fn item, emoji_pack ->
|
||||||
|
@ -292,7 +271,7 @@ defmodule Pleroma.Emoji.Pack do
|
||||||
@spec load_pack(String.t()) :: {:ok, t()} | {:error, :file.posix()}
|
@spec load_pack(String.t()) :: {:ok, t()} | {:error, :file.posix()}
|
||||||
def load_pack(name) do
|
def load_pack(name) do
|
||||||
name = Path.basename(name)
|
name = Path.basename(name)
|
||||||
pack_file = Path.join([emoji_path(), name, "pack.json"])
|
pack_file = path_join_name_safe(emoji_path(), name) |> Path.join("pack.json")
|
||||||
|
|
||||||
with {:ok, _} <- File.stat(pack_file),
|
with {:ok, _} <- File.stat(pack_file),
|
||||||
{:ok, pack_data} <- File.read(pack_file) do
|
{:ok, pack_data} <- File.read(pack_file) do
|
||||||
|
@ -416,10 +395,9 @@ defmodule Pleroma.Emoji.Pack do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp create_archive_and_cache(pack, hash) do
|
defp create_archive_and_cache(pack, hash) do
|
||||||
files = [~c"pack.json" | Enum.map(pack.files, fn {_, file} -> to_charlist(file) end)]
|
pack_file_list = Enum.into(pack.files, [], fn {_, f} -> f end)
|
||||||
|
files = ["pack.json" | pack_file_list]
|
||||||
{:ok, {_, result}} =
|
{:ok, {_, result}} = SafeZip.zip("#{pack.name}.zip", files, pack.path, true)
|
||||||
:zip.zip(~c"#{pack.name}.zip", files, [:memory, cwd: to_charlist(pack.path)])
|
|
||||||
|
|
||||||
ttl_per_file = Pleroma.Config.get!([:emoji, :shared_pack_cache_seconds_per_file])
|
ttl_per_file = Pleroma.Config.get!([:emoji, :shared_pack_cache_seconds_per_file])
|
||||||
overall_ttl = :timer.seconds(ttl_per_file * Enum.count(files))
|
overall_ttl = :timer.seconds(ttl_per_file * Enum.count(files))
|
||||||
|
@ -478,7 +456,7 @@ defmodule Pleroma.Emoji.Pack do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp save_file(%Plug.Upload{path: upload_path}, pack, filename) do
|
defp save_file(%Plug.Upload{path: upload_path}, pack, filename) do
|
||||||
file_path = Path.join(pack.path, filename)
|
file_path = path_join_safe(pack.path, filename)
|
||||||
create_subdirs(file_path)
|
create_subdirs(file_path)
|
||||||
|
|
||||||
with {:ok, _} <- File.copy(upload_path, file_path) do
|
with {:ok, _} <- File.copy(upload_path, file_path) do
|
||||||
|
@ -497,8 +475,8 @@ defmodule Pleroma.Emoji.Pack do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp rename_file(pack, filename, new_filename) do
|
defp rename_file(pack, filename, new_filename) do
|
||||||
old_path = Path.join(pack.path, filename)
|
old_path = path_join_safe(pack.path, filename)
|
||||||
new_path = Path.join(pack.path, new_filename)
|
new_path = path_join_safe(pack.path, new_filename)
|
||||||
create_subdirs(new_path)
|
create_subdirs(new_path)
|
||||||
|
|
||||||
with :ok <- File.rename(old_path, new_path) do
|
with :ok <- File.rename(old_path, new_path) do
|
||||||
|
@ -516,7 +494,7 @@ defmodule Pleroma.Emoji.Pack do
|
||||||
|
|
||||||
defp remove_file(pack, shortcode) do
|
defp remove_file(pack, shortcode) do
|
||||||
with {:ok, filename} <- get_filename(pack, shortcode),
|
with {:ok, filename} <- get_filename(pack, shortcode),
|
||||||
emoji <- Path.join(pack.path, filename),
|
emoji <- path_join_safe(pack.path, filename),
|
||||||
:ok <- File.rm(emoji) do
|
:ok <- File.rm(emoji) do
|
||||||
remove_dir_if_empty(emoji, filename)
|
remove_dir_if_empty(emoji, filename)
|
||||||
end
|
end
|
||||||
|
@ -534,7 +512,7 @@ defmodule Pleroma.Emoji.Pack do
|
||||||
|
|
||||||
defp get_filename(pack, shortcode) do
|
defp get_filename(pack, shortcode) do
|
||||||
with %{^shortcode => filename} when is_binary(filename) <- pack.files,
|
with %{^shortcode => filename} when is_binary(filename) <- pack.files,
|
||||||
file_path <- Path.join(pack.path, filename),
|
file_path <- path_join_safe(pack.path, filename),
|
||||||
{:ok, _} <- File.stat(file_path) do
|
{:ok, _} <- File.stat(file_path) do
|
||||||
{:ok, filename}
|
{:ok, filename}
|
||||||
else
|
else
|
||||||
|
@ -584,11 +562,10 @@ defmodule Pleroma.Emoji.Pack do
|
||||||
|
|
||||||
defp unzip(archive, pack_info, remote_pack, local_pack) do
|
defp unzip(archive, pack_info, remote_pack, local_pack) do
|
||||||
with :ok <- File.mkdir_p!(local_pack.path) do
|
with :ok <- File.mkdir_p!(local_pack.path) do
|
||||||
files = Enum.map(remote_pack["files"], fn {_, path} -> to_charlist(path) end)
|
files = Enum.map(remote_pack["files"], fn {_, path} -> path end)
|
||||||
# Fallback cannot contain a pack.json file
|
# Fallback cannot contain a pack.json file
|
||||||
files = if pack_info[:fallback], do: files, else: [~c"pack.json" | files]
|
files = if pack_info[:fallback], do: files, else: ["pack.json" | files]
|
||||||
|
SafeZip.unzip_data(archive, local_pack.path, files)
|
||||||
:zip.unzip(archive, cwd: to_charlist(local_pack.path), file_list: files)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -649,13 +626,43 @@ defmodule Pleroma.Emoji.Pack do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp validate_has_all_files(pack, zip) do
|
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
|
||||||
# Check if all files from the pack.json are in the archive
|
eset =
|
||||||
pack.files
|
Enum.reduce(pack.files, MapSet.new(), fn
|
||||||
|> Enum.all?(fn {_, from_manifest} ->
|
{_, file}, s -> MapSet.put(s, to_charlist(file))
|
||||||
List.keyfind(f_list, to_charlist(from_manifest), 0)
|
|
||||||
end)
|
end)
|
||||||
|> if(do: :ok, else: {:error, :incomplete})
|
|
||||||
|
if SafeZip.contains_all_data?(zip, eset),
|
||||||
|
do: :ok,
|
||||||
|
else: {:error, :incomplete}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp path_join_name_safe(dir, name) do
|
||||||
|
if to_string(name) != Path.basename(name) or name in ["..", ".", ""] do
|
||||||
|
raise "Invalid or malicious pack name: #{name}"
|
||||||
|
else
|
||||||
|
Path.join(dir, name)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp path_join_safe(dir, path) do
|
||||||
|
{:ok, safe_path} = Path.safe_relative(path)
|
||||||
|
Path.join(dir, safe_path)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp map_zip_emojies(zip_files) do
|
||||||
|
Enum.reduce(zip_files, [], fn path, acc ->
|
||||||
|
with(
|
||||||
|
filename <- Path.basename(path),
|
||||||
|
shortcode <- Path.basename(filename, Path.extname(filename)),
|
||||||
|
# note: this only checks the shortcode, if an emoji already exists on the same path, but
|
||||||
|
# with a different shortcode, the existing one will be degraded to an alias of the new
|
||||||
|
false <- Emoji.exist?(shortcode)
|
||||||
|
) do
|
||||||
|
[%{path: path, filename: path, shortcode: shortcode} | acc]
|
||||||
|
else
|
||||||
|
_ -> acc
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -65,24 +65,12 @@ defmodule Pleroma.Frontend do
|
||||||
end
|
end
|
||||||
|
|
||||||
def unzip(zip, dest) do
|
def unzip(zip, dest) do
|
||||||
with {:ok, unzipped} <- :zip.unzip(zip, [:memory]) do
|
File.rm_rf!(dest)
|
||||||
File.rm_rf!(dest)
|
File.mkdir_p!(dest)
|
||||||
File.mkdir_p!(dest)
|
|
||||||
|
|
||||||
Enum.each(unzipped, fn {filename, data} ->
|
case Pleroma.SafeZip.unzip_data(zip, dest) do
|
||||||
path = filename
|
{:ok, _} -> :ok
|
||||||
|
error -> error
|
||||||
new_file_path = Path.join(dest, path)
|
|
||||||
|
|
||||||
path
|
|
||||||
|> Path.dirname()
|
|
||||||
|> then(&Path.join(dest, &1))
|
|
||||||
|> File.mkdir_p!()
|
|
||||||
|
|
||||||
if not File.dir?(new_file_path) do
|
|
||||||
File.write!(new_file_path, data)
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,8 @@ defmodule Pleroma.User.Backup do
|
||||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||||
alias Pleroma.Web.ActivityPub.UserView
|
alias Pleroma.Web.ActivityPub.UserView
|
||||||
alias Pleroma.Workers.BackupWorker
|
alias Pleroma.Workers.BackupWorker
|
||||||
|
alias Pleroma.SafeZip
|
||||||
|
alias Pleroma.Upload
|
||||||
|
|
||||||
@type t :: %__MODULE__{}
|
@type t :: %__MODULE__{}
|
||||||
|
|
||||||
|
@ -179,12 +181,12 @@ defmodule Pleroma.User.Backup do
|
||||||
end
|
end
|
||||||
|
|
||||||
@files [
|
@files [
|
||||||
~c"actor.json",
|
"actor.json",
|
||||||
~c"outbox.json",
|
"outbox.json",
|
||||||
~c"likes.json",
|
"likes.json",
|
||||||
~c"bookmarks.json",
|
"bookmarks.json",
|
||||||
~c"followers.json",
|
"followers.json",
|
||||||
~c"following.json"
|
"following.json"
|
||||||
]
|
]
|
||||||
|
|
||||||
@spec run(t()) :: {:ok, t()} | {:error, :failed}
|
@spec run(t()) :: {:ok, t()} | {:error, :failed}
|
||||||
|
@ -200,7 +202,7 @@ defmodule Pleroma.User.Backup do
|
||||||
{_, :ok} <- {:followers, followers(backup.tempdir, backup.user)},
|
{_, :ok} <- {:followers, followers(backup.tempdir, backup.user)},
|
||||||
{_, :ok} <- {:following, following(backup.tempdir, backup.user)},
|
{_, :ok} <- {:following, following(backup.tempdir, backup.user)},
|
||||||
{_, {:ok, _zip_path}} <-
|
{_, {:ok, _zip_path}} <-
|
||||||
{:zip, :zip.create(to_charlist(tempfile), @files, cwd: to_charlist(backup.tempdir))},
|
{:zip, SafeZip.zip(tempfile, @files, backup.tempdir)},
|
||||||
{_, {:ok, %File.Stat{size: zip_size}}} <- {:filestat, File.stat(tempfile)},
|
{_, {:ok, %File.Stat{size: zip_size}}} <- {:filestat, File.stat(tempfile)},
|
||||||
{:ok, updated_backup} <- update_record(backup, %{file_size: zip_size}) do
|
{:ok, updated_backup} <- update_record(backup, %{file_size: zip_size}) do
|
||||||
{:ok, updated_backup}
|
{:ok, updated_backup}
|
||||||
|
|
Loading…
Reference in a new issue