configurable media upload limits Closes #118 See merge request pleroma/pleroma!401tags/v0.9.9
@@ -85,6 +85,9 @@ config :pleroma, :instance, | |||||
description: "A Pleroma instance, an alternative fediverse server", | description: "A Pleroma instance, an alternative fediverse server", | ||||
limit: 5000, | limit: 5000, | ||||
upload_limit: 16_000_000, | upload_limit: 16_000_000, | ||||
avatar_upload_limit: 2_000_000, | |||||
background_upload_limit: 4_000_000, | |||||
banner_upload_limit: 4_000_000, | |||||
registrations_open: true, | registrations_open: true, | ||||
federating: true, | federating: true, | ||||
allow_relay: true, | allow_relay: true, | ||||
@@ -4,61 +4,76 @@ defmodule Pleroma.Upload do | |||||
@storage_backend Application.get_env(:pleroma, Pleroma.Upload) | @storage_backend Application.get_env(:pleroma, Pleroma.Upload) | ||||
|> Keyword.fetch!(:uploader) | |> Keyword.fetch!(:uploader) | ||||
def store(%Plug.Upload{} = file, should_dedupe) do | |||||
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 | |||||
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) | content_type = get_content_type(file.path) | ||||
uuid = get_uuid(file, should_dedupe) | |||||
name = get_name(file, uuid, content_type, should_dedupe) | |||||
strip_exif_data(content_type, file.path) | |||||
{:ok, url_path} = | |||||
@storage_backend.put_file(name, uuid, file.path, content_type, should_dedupe) | |||||
%{ | |||||
"type" => "Document", | |||||
"url" => [ | |||||
%{ | |||||
"type" => "Link", | |||||
"mediaType" => content_type, | |||||
"href" => url_path | |||||
} | |||||
], | |||||
"name" => name | |||||
} | |||||
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} = | |||||
@storage_backend.put_file(name, uuid, file.path, content_type, should_dedupe) | |||||
%{ | |||||
"type" => "Document", | |||||
"url" => [ | |||||
%{ | |||||
"type" => "Link", | |||||
"mediaType" => content_type, | |||||
"href" => url_path | |||||
} | |||||
], | |||||
"name" => name | |||||
} | |||||
else | |||||
_e -> nil | |||||
end | |||||
end | end | ||||
def store(%{"img" => "data:image/" <> image_data}, should_dedupe) do | |||||
def store(%{"img" => "data:image/" <> image_data}, should_dedupe, size_limit) do | |||||
parsed = Regex.named_captures(~r/(?<filetype>jpeg|png|gif);base64,(?<data>.*)/, image_data) | parsed = Regex.named_captures(~r/(?<filetype>jpeg|png|gif);base64,(?<data>.*)/, image_data) | ||||
data = Base.decode64!(parsed["data"], ignore: :whitespace) | data = Base.decode64!(parsed["data"], ignore: :whitespace) | ||||
tmp_path = tempfile_for_image(data) | |||||
uuid = UUID.generate() | |||||
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 | |||||
) | |||||
{:ok, url_path} = @storage_backend.put_file(name, uuid, tmp_path, content_type, should_dedupe) | |||||
%{ | |||||
"type" => "Image", | |||||
"url" => [ | |||||
%{ | |||||
"type" => "Link", | |||||
"mediaType" => content_type, | |||||
"href" => url_path | |||||
} | |||||
], | |||||
"name" => name | |||||
} | |||||
with tmp_path <- tempfile_for_image(data), | |||||
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 | |||||
) | |||||
{:ok, url_path} = | |||||
@storage_backend.put_file(name, uuid, tmp_path, content_type, should_dedupe) | |||||
%{ | |||||
"type" => "Image", | |||||
"url" => [ | |||||
%{ | |||||
"type" => "Link", | |||||
"mediaType" => content_type, | |||||
"href" => url_path | |||||
} | |||||
], | |||||
"name" => name | |||||
} | |||||
else | |||||
_e -> nil | |||||
end | |||||
end | end | ||||
@doc """ | @doc """ | ||||
@@ -575,9 +575,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do | |||||
|> Enum.reverse() | |> Enum.reverse() | ||||
end | end | ||||
def upload(file) do | |||||
data = Upload.store(file, Application.get_env(:pleroma, :instance)[:dedupe_media]) | |||||
Repo.insert(%Object{data: data}) | |||||
def upload(file, size_limit \\ nil) do | |||||
with data <- | |||||
Upload.store(file, Application.get_env(:pleroma, :instance)[:dedupe_media], size_limit), | |||||
false <- is_nil(data) do | |||||
Repo.insert(%Object{data: data}) | |||||
end | |||||
end | end | ||||
def user_data_from_user_object(data) do | def user_data_from_user_object(data) do | ||||
@@ -35,6 +35,14 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do | |||||
def update_credentials(%{assigns: %{user: user}} = conn, params) do | def update_credentials(%{assigns: %{user: user}} = conn, params) do | ||||
original_user = user | original_user = user | ||||
avatar_upload_limit = | |||||
Application.get_env(:pleroma, :instance) | |||||
|> Keyword.fetch(:avatar_upload_limit) | |||||
banner_upload_limit = | |||||
Application.get_env(:pleroma, :instance) | |||||
|> Keyword.fetch(:banner_upload_limit) | |||||
params = | params = | ||||
if bio = params["note"] do | if bio = params["note"] do | ||||
Map.put(params, "bio", bio) | Map.put(params, "bio", bio) | ||||
@@ -52,7 +60,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do | |||||
user = | user = | ||||
if avatar = params["avatar"] do | if avatar = params["avatar"] do | ||||
with %Plug.Upload{} <- avatar, | with %Plug.Upload{} <- avatar, | ||||
{:ok, object} <- ActivityPub.upload(avatar), | |||||
{:ok, object} <- ActivityPub.upload(avatar, avatar_upload_limit), | |||||
change = Ecto.Changeset.change(user, %{avatar: object.data}), | change = Ecto.Changeset.change(user, %{avatar: object.data}), | ||||
{:ok, user} = User.update_and_set_cache(change) do | {:ok, user} = User.update_and_set_cache(change) do | ||||
user | user | ||||
@@ -66,7 +74,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do | |||||
user = | user = | ||||
if banner = params["header"] do | if banner = params["header"] do | ||||
with %Plug.Upload{} <- banner, | with %Plug.Upload{} <- banner, | ||||
{:ok, object} <- ActivityPub.upload(banner), | |||||
{:ok, object} <- ActivityPub.upload(banner, banner_upload_limit), | |||||
new_info <- Map.put(user.info, "banner", object.data), | new_info <- Map.put(user.info, "banner", object.data), | ||||
change <- User.info_changeset(user, %{info: new_info}), | change <- User.info_changeset(user, %{info: new_info}), | ||||
{:ok, user} <- User.update_and_set_cache(change) do | {:ok, user} <- User.update_and_set_cache(change) do | ||||
@@ -113,6 +113,12 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do | |||||
staffAccounts: staff_accounts, | staffAccounts: staff_accounts, | ||||
federation: federation_response, | federation: federation_response, | ||||
postFormats: Keyword.get(instance, :allowed_post_formats), | postFormats: Keyword.get(instance, :allowed_post_formats), | ||||
uploadLimits: %{ | |||||
general: Keyword.get(instance, :upload_limit), | |||||
avatar: Keyword.get(instance, :avatar_upload_limit), | |||||
banner: Keyword.get(instance, :banner_upload_limit), | |||||
background: Keyword.get(instance, :background_upload_limit) | |||||
}, | |||||
features: features | features: features | ||||
} | } | ||||
} | } | ||||
@@ -263,7 +263,11 @@ defmodule Pleroma.Web.TwitterAPI.Controller do | |||||
end | end | ||||
def update_avatar(%{assigns: %{user: user}} = conn, params) do | def update_avatar(%{assigns: %{user: user}} = conn, params) do | ||||
{:ok, object} = ActivityPub.upload(params) | |||||
upload_limit = | |||||
Application.get_env(:pleroma, :instance) | |||||
|> Keyword.fetch(:avatar_upload_limit) | |||||
{:ok, object} = ActivityPub.upload(params, upload_limit) | |||||
change = Changeset.change(user, %{avatar: object.data}) | change = Changeset.change(user, %{avatar: object.data}) | ||||
{:ok, user} = User.update_and_set_cache(change) | {:ok, user} = User.update_and_set_cache(change) | ||||
CommonAPI.update(user) | CommonAPI.update(user) | ||||
@@ -272,7 +276,11 @@ defmodule Pleroma.Web.TwitterAPI.Controller do | |||||
end | end | ||||
def update_banner(%{assigns: %{user: user}} = conn, params) do | def update_banner(%{assigns: %{user: user}} = conn, params) do | ||||
with {:ok, object} <- ActivityPub.upload(%{"img" => params["banner"]}), | |||||
upload_limit = | |||||
Application.get_env(:pleroma, :instance) | |||||
|> Keyword.fetch(:banner_upload_limit) | |||||
with {:ok, object} <- ActivityPub.upload(%{"img" => params["banner"]}, upload_limit), | |||||
new_info <- Map.put(user.info, "banner", object.data), | new_info <- Map.put(user.info, "banner", object.data), | ||||
change <- User.info_changeset(user, %{info: new_info}), | change <- User.info_changeset(user, %{info: new_info}), | ||||
{:ok, user} <- User.update_and_set_cache(change) do | {:ok, user} <- User.update_and_set_cache(change) do | ||||
@@ -286,7 +294,11 @@ defmodule Pleroma.Web.TwitterAPI.Controller do | |||||
end | end | ||||
def update_background(%{assigns: %{user: user}} = conn, params) do | def update_background(%{assigns: %{user: user}} = conn, params) do | ||||
with {:ok, object} <- ActivityPub.upload(params), | |||||
upload_limit = | |||||
Application.get_env(:pleroma, :instance) | |||||
|> Keyword.fetch(:background_upload_limit) | |||||
with {:ok, object} <- ActivityPub.upload(params, upload_limit), | |||||
new_info <- Map.put(user.info, "background", object.data), | new_info <- Map.put(user.info, "background", object.data), | ||||
change <- User.info_changeset(user, %{info: new_info}), | change <- User.info_changeset(user, %{info: new_info}), | ||||
{:ok, _user} <- User.update_and_set_cache(change) do | {:ok, _user} <- User.update_and_set_cache(change) do | ||||