Merge branch 'develop' into fix/domain-block-subplan-cost

This commit is contained in:
lain 2026-09-02 10:19:27 +00:00
commit 70d743bff9
20 changed files with 244 additions and 351 deletions

View file

@ -0,0 +1 @@
Docs: Fixed OTP install links in documentation, broke up wall of install commands into smaller chunks, fixed sudo commands execution

View file

View file

@ -1 +0,0 @@
Detect image content types for extensionless MediaProxy responses.

View file

@ -0,0 +1 @@
Oban: Enabled the Reindexer plugin to reduce oban_jobs index DB bloat

View file

View file

@ -0,0 +1 @@
Fix intermittent failure to compile syslog package

View file

@ -607,7 +607,11 @@ config :pleroma, Oban,
search_indexing: [limit: 10, paused: true], search_indexing: [limit: 10, paused: true],
slow: 5 slow: 5
], ],
plugins: [Oban.Plugins.Lazarus, {Oban.Plugins.Pruner, max_age: 900}], plugins: [
Oban.Plugins.Lazarus,
{Oban.Plugins.Pruner, max_age: 900},
{Oban.Plugins.Reindexer, schedule: "@weekly"}
],
crontab: [ crontab: [
{"0 0 * * 0", Pleroma.Workers.Cron.DigestEmailsWorker}, {"0 0 * * 0", Pleroma.Workers.Cron.DigestEmailsWorker},
{"0 0 * * *", Pleroma.Workers.Cron.NewUsersDigestWorker}, {"0 0 * * *", Pleroma.Workers.Cron.NewUsersDigestWorker},

View file

@ -76,35 +76,51 @@ $EDITOR /etc/pleroma/config.exs
``` ```
## Installing the release ## Installing the release
Before proceeding, get the flavour from [Detecting flavour](otp_en.md#detecting-flavour) section in OTP installation guide. Before proceeding, get the flavour from [Detecting flavour](otp_en.md#detecting-flavour) section in OTP installation guide.
Delete all files in pleroma user's directory:
```sh ```sh
# Delete all files in pleroma user's directory
rm -r ~pleroma/* rm -r ~pleroma/*
```
# Set the flavour environment variable to the string you got in Detecting flavour section. Set the flavour environment variable to the string you got in Detecting flavour section.
# For example if the flavour is `amd64-musl` the command will be For example if the flavour is `amd64-musl` the command will be:
```sh
export FLAVOUR="amd64-musl" export FLAVOUR="amd64-musl"
```
# Clone the release build into a temporary directory and unpack it Clone the release build into a temporary directory and unpack it.
# Replace `stable` with `unstable` if you want to run the unstable branch Replace `stable` with `develop`, if you want to run the develop branch.
sudo -Hu pleroma "
curl 'https://git.pleroma.social/api/v4/projects/2/jobs/artifacts/stable/download?job=$FLAVOUR' -o /tmp/pleroma.zip
unzip /tmp/pleroma.zip -d /tmp/
"
# Move the release to the home directory and delete temporary files ```sh
sudo -Hu pleroma " sudo -Hu pleroma curl "https://git.pleroma.social/api/packages/pleroma/generic/pleroma-otp-stable-$FLAVOUR/latest/pleroma.zip" -o /tmp/pleroma.zip
mv /tmp/release/* ~pleroma/ sudo -Hu pleroma unzip /tmp/pleroma.zip -d /tmp/
rmdir /tmp/release ```
rm /tmp/pleroma.zip
"
# Start the instance to verify that everything is working as expected Move the release to the home directory and delete temporary files:
```sh
sudo -Hu pleroma mv /tmp/release/* ~pleroma/
sudo -Hu pleroma rmdir /tmp/release
sudo -Hu pleroma rm /tmp/pleroma.zip
```
Start the instance to verify that everything is working as expected:
```sh
sudo -Hu pleroma "./bin/pleroma daemon" sudo -Hu pleroma "./bin/pleroma daemon"
```
# Wait for about 20 seconds and query the instance endpoint, if it shows your uri, name and email correctly, you are configured correctly Wait for about 20 seconds and query the instance endpoint, if it shows your uri, name and email correctly, you are configured correctly:
```sh
sleep 20 && curl http://localhost:4000/api/v1/instance sleep 20 && curl http://localhost:4000/api/v1/instance
```
# Stop the instance Stop the instance:
```sh
sudo -Hu pleroma "./bin/pleroma stop" sudo -Hu pleroma "./bin/pleroma stop"
``` ```

View file

@ -130,60 +130,90 @@ Restart PostgreSQL to apply configuration changes:
``` ```
### Installing Pleroma ### Installing Pleroma
Create a Pleroma user:
```sh ```sh
# Create a Pleroma user
adduser --system --shell /bin/false --home /opt/pleroma pleroma adduser --system --shell /bin/false --home /opt/pleroma pleroma
```
# Set the flavour environment variable to the string you got in Detecting flavour section. Set the flavour environment variable to the string you got in Detecting flavour section. For example if the flavour is `amd64-musl` the command will be:
# For example if the flavour is `amd64-musl` the command will be
```sh
export FLAVOUR="amd64-musl" export FLAVOUR="amd64-musl"
```
# Clone the release build into a temporary directory and unpack it Clone the release build into a temporary directory and unpack it:
sudo -Hu pleroma "
curl 'https://git.pleroma.social/api/v4/projects/2/jobs/artifacts/stable/download?job=$FLAVOUR' -o /tmp/pleroma.zip
unzip /tmp/pleroma.zip -d /tmp/
"
# Move the release to the home directory and delete temporary files ```sh
sudo -Hu pleroma " sudo -Hu pleroma curl "https://git.pleroma.social/api/packages/pleroma/generic/pleroma-otp-stable-$FLAVOUR/latest/pleroma.zip" -o /tmp/pleroma.zip
mv /tmp/release/* /opt/pleroma sudo -Hu pleroma unzip /tmp/pleroma.zip -d /tmp/
rmdir /tmp/release ```
rm /tmp/pleroma.zip
"
# Create uploads directory and set proper permissions (skip if planning to use a remote uploader)
# Note: It does not have to be `/var/lib/pleroma/uploads`, the config generator will ask about the upload directory later
Move the release to the home directory and delete temporary files:
```sh
sudo -Hu pleroma mv /tmp/release/* /opt/pleroma
sudo -Hu pleroma rmdir /tmp/release
sudo -Hu pleroma rm /tmp/pleroma.zip
```
Create uploads directory and set proper permissions (skip if planning to use a remote uploader).
Note: It does not have to be `/var/lib/pleroma/uploads`, the config generator will ask about the upload directory later.
```sh
mkdir -p /var/lib/pleroma/uploads mkdir -p /var/lib/pleroma/uploads
chown -R pleroma /var/lib/pleroma chown -R pleroma /var/lib/pleroma
```
# Create custom public files directory (custom emojis, frontend bundle overrides, robots.txt, etc.) Create custom public files directory (custom emojis, frontend bundle overrides, robots.txt, etc.)
# Note: It does not have to be `/var/lib/pleroma/static`, the config generator will ask about the custom public files directory later Note: It does not have to be `/var/lib/pleroma/static`, the config generator will ask about the custom public files directory later.
```sh
mkdir -p /var/lib/pleroma/static mkdir -p /var/lib/pleroma/static
chown -R pleroma /var/lib/pleroma chown -R pleroma /var/lib/pleroma
```
# Create a config directory Create a config directory:
```sh
mkdir -p /etc/pleroma mkdir -p /etc/pleroma
chown -R pleroma /etc/pleroma chown -R pleroma /etc/pleroma
```
# Run the config generator Run the config generator:
```sh
sudo -Hu pleroma "./bin/pleroma_ctl instance gen --output /etc/pleroma/config.exs --output-psql /tmp/setup_db.psql" sudo -Hu pleroma "./bin/pleroma_ctl instance gen --output /etc/pleroma/config.exs --output-psql /tmp/setup_db.psql"
```
# Create the postgres database Create the postgres database and schema:
```sh
sudo -u postgres -s $SHELL -lc "psql -f /tmp/setup_db.psql" sudo -u postgres -s $SHELL -lc "psql -f /tmp/setup_db.psql"
# Create the database schema
sudo -Hu pleroma "./bin/pleroma_ctl migrate" sudo -Hu pleroma "./bin/pleroma_ctl migrate"
```
# If you have installed RUM indexes uncommend and run (Optional) If you have installed RUM indexes:
# sudo -Hu pleroma "./bin/pleroma_ctl migrate --migrations-path priv/repo/optional_migrations/rum_indexing/"
# Start the instance to verify that everything is working as expected ```sh
sudo -Hu pleroma "./bin/pleroma_ctl migrate --migrations-path priv/repo/optional_migrations/rum_indexing/"
```
Start the instance to verify that everything is working as expected:
```sh
sudo -Hu pleroma "./bin/pleroma daemon" sudo -Hu pleroma "./bin/pleroma daemon"
```
# Wait for about 20 seconds and query the instance endpoint, if it shows your uri, name and email correctly, you are configured correctly Wait for about 20 seconds and query the instance endpoint, if it shows your uri, name and email correctly, you are configured correctly:
```sh
sleep 20 && curl http://localhost:4000/api/v1/instance sleep 20 && curl http://localhost:4000/api/v1/instance
```
# Stop the instance Stop the instance:
```sh
sudo -Hu pleroma "./bin/pleroma stop" sudo -Hu pleroma "./bin/pleroma stop"
``` ```

View file

@ -17,7 +17,6 @@ defmodule Pleroma.ReverseProxy do
@max_read_duration :timer.seconds(30) @max_read_duration :timer.seconds(30)
@max_body_length :infinity @max_body_length :infinity
@failed_request_ttl :timer.seconds(60) @failed_request_ttl :timer.seconds(60)
@sniff_bytes 8 * 1024
@methods ~w(GET HEAD) @methods ~w(GET HEAD)
@allowed_mime_types Pleroma.Config.get([Pleroma.Upload, :allowed_mime_types], []) @allowed_mime_types Pleroma.Config.get([Pleroma.Upload, :allowed_mime_types], [])
@ -70,12 +69,7 @@ defmodule Pleroma.ReverseProxy do
* `inline_content_types`: * `inline_content_types`:
* `true` will not alter `content-disposition` (up to the upstream), * `true` will not alter `content-disposition` (up to the upstream),
* `false` will add `content-disposition: attachment` to any request, * `false` will add `content-disposition: attachment` to any request,
* a list of whitelisted content types for which `content-disposition: inline` * a list of whitelisted content types
is always set (overriding any upstream header) so the media can be embedded
in pages; the filename is derived from the content type
* `sniff_content_type` (default `false`): detects an image MIME type from the first
response chunk when the upstream type is missing or `application/octet-stream`.
* `req_headers`, `resp_headers` additional headers. * `req_headers`, `resp_headers` additional headers.
@ -112,7 +106,6 @@ defmodule Pleroma.ReverseProxy do
| {:req_headers, [{String.t(), String.t()}]} | {:req_headers, [{String.t(), String.t()}]}
| {:resp_headers, [{String.t(), String.t()}]} | {:resp_headers, [{String.t(), String.t()}]}
| {:inline_content_types, boolean() | list(String.t())} | {:inline_content_types, boolean() | list(String.t())}
| {:sniff_content_type, boolean()}
| {:redirect_on_failure, boolean()} | {:redirect_on_failure, boolean()}
@spec call(Plug.Conn.t(), String.t(), list(option())) :: Plug.Conn.t() @spec call(Plug.Conn.t(), String.t(), list(option())) :: Plug.Conn.t()
@ -228,30 +221,11 @@ defmodule Pleroma.ReverseProxy do
defp response(conn, client, url, status, headers, opts) do defp response(conn, client, url, status, headers, opts) do
Logger.debug("#{__MODULE__} #{status} #{url} #{inspect(headers)}") Logger.debug("#{__MODULE__} #{status} #{url} #{inspect(headers)}")
case maybe_prefetch_for_content_type(headers, client, opts) do
{_headers, client, {:error, {:upstream, error}}} ->
Logger.warning(
"#{__MODULE__} request to #{url} failed before streaming: #{inspect(error)}"
)
client().close(client)
track_failed_url(url, error, opts)
conn
|> error_or_redirect(url, 500, "Request failed", opts)
|> halt()
{headers, client, prefetched} ->
stream_response(conn, client, url, status, headers, opts, prefetched)
end
end
defp stream_response(conn, client, url, status, headers, opts, prefetched) do
result = result =
conn conn
|> put_resp_headers(build_resp_headers(headers, opts)) |> put_resp_headers(build_resp_headers(headers, opts))
|> send_chunked(status) |> send_chunked(status)
|> chunk_reply(client, opts, prefetched) |> chunk_reply(client, opts)
case result do case result do
{:ok, conn} -> {:ok, conn} ->
@ -272,29 +246,18 @@ defmodule Pleroma.ReverseProxy do
end end
end end
defp chunk_reply(conn, client, opts, :none) do defp chunk_reply(conn, client, opts) do
chunk_reply(conn, client, opts, 0, 0) chunk_reply(conn, client, opts, 0, 0)
end end
defp chunk_reply(conn, _client, _opts, :done), do: {:ok, conn}
defp chunk_reply(conn, _client, _opts, {:error, error}), do: {:error, error, conn}
defp chunk_reply(conn, client, opts, {:ok, data, duration}) do
with :ok <-
body_size_constraint(
byte_size(data),
Keyword.get(opts, :max_body_length, @max_body_length)
),
{:ok, conn} <- write_chunk(conn, data) do
chunk_reply(conn, client, opts, byte_size(data), duration)
else
{:error, {:downstream, error}} -> {:error, {:downstream, error}, conn}
{:error, error} -> {:error, {:upstream, error}, conn}
end
end
defp chunk_reply(conn, client, opts, sent_so_far, duration) do defp chunk_reply(conn, client, opts, sent_so_far, duration) do
with {:ok, data, client, duration} <- read_chunk(client, duration, opts), with {:ok, duration} <-
check_read_duration(
duration,
Keyword.get(opts, :max_read_duration, @max_read_duration)
),
{:ok, data, client} <- client().stream_body(client),
{:ok, duration} <- increase_read_duration(duration),
sent_so_far = sent_so_far + byte_size(data), sent_so_far = sent_so_far + byte_size(data),
:ok <- :ok <-
body_size_constraint( body_size_constraint(
@ -310,24 +273,6 @@ defmodule Pleroma.ReverseProxy do
end end
end end
defp read_chunk(client, duration, opts) do
result =
with {:ok, timer} <-
check_read_duration(
duration,
Keyword.get(opts, :max_read_duration, @max_read_duration)
),
{:ok, data, client} <- client().stream_body(client),
{:ok, duration} <- increase_read_duration(timer) do
{:ok, data, client, duration}
end
case result do
{:error, error} -> {:error, {:upstream, error}}
result -> result
end
end
defp write_chunk(conn, data) do defp write_chunk(conn, data) do
case chunk(conn, data) do case chunk(conn, data) do
{:error, error} -> {:error, {:downstream, error}} {:error, error} -> {:error, {:downstream, error}}
@ -335,54 +280,6 @@ defmodule Pleroma.ReverseProxy do
end end
end end
defp maybe_prefetch_for_content_type(headers, client, opts) do
if Keyword.get(opts, :sniff_content_type, false) and generic_content_type?(headers) do
case read_chunk(client, 0, opts) do
{:ok, data, client, duration} ->
{maybe_put_image_content_type(headers, data), client, {:ok, data, duration}}
:done ->
{headers, client, :done}
{:error, error} ->
{headers, client, {:error, error}}
end
else
{headers, client, :none}
end
end
defp generic_content_type?(headers) do
headers
|> get_content_type()
|> String.trim()
|> String.downcase()
|> then(&(&1 in ["", "application/octet-stream"]))
end
defp maybe_put_image_content_type(headers, data) do
with false <- data == "",
{:ok, %{mime_type: "image/" <> _ = content_type}} <-
Majic.perform({:bytes, binary_part(data, 0, min(byte_size(data), @sniff_bytes))},
pool: Pleroma.MajicPool
) do
[
{"content-type", content_type}
| Enum.reject(headers, fn {key, _} -> key == "content-type" end)
]
else
_ -> headers
end
rescue
error ->
Logger.debug("#{__MODULE__}: content-type sniffing failed: #{Exception.message(error)}")
headers
catch
kind, reason ->
Logger.debug("#{__MODULE__}: content-type sniffing failed: #{kind}: #{inspect(reason)}")
headers
end
defp head_response(conn, url, code, headers, opts) do defp head_response(conn, url, code, headers, opts) do
Logger.debug("#{__MODULE__} #{code} #{url} #{inspect(headers)}") Logger.debug("#{__MODULE__} #{code} #{url} #{inspect(headers)}")
@ -430,33 +327,16 @@ defmodule Pleroma.ReverseProxy do
|> build_req_range_or_encoding_header(opts) |> build_req_range_or_encoding_header(opts)
|> build_req_user_agent_header(opts) |> build_req_user_agent_header(opts)
|> merge_headers(Keyword.get(opts, :req_headers, [])) |> merge_headers(Keyword.get(opts, :req_headers, []))
|> maybe_force_identity_encoding(opts)
end
defp merge_headers(headers, extra_headers) do
Enum.reduce(extra_headers, headers, fn {key, value}, headers ->
List.keystore(headers, String.downcase(key), 0, {String.downcase(key), value})
end)
end
defp maybe_force_identity_encoding(headers, opts) do
if Keyword.get(opts, :sniff_content_type, false) do
List.keystore(headers, "accept-encoding", 0, {"accept-encoding", "identity"})
else
headers
end
end end
# Disable content-encoding if any @range_headers are requested (see #1823). # Disable content-encoding if any @range_headers are requested (see #1823).
defp build_req_range_or_encoding_header(headers, _opts) do defp build_req_range_or_encoding_header(headers, _opts) do
range? = Enum.any?(headers, fn {header, _} -> Enum.member?(@range_headers, header) end) range? = Enum.any?(headers, fn {header, _} -> Enum.member?(@range_headers, header) end)
cond do if range? && List.keymember?(headers, "accept-encoding", 0) do
range? && List.keymember?(headers, "accept-encoding", 0) -> List.keydelete(headers, "accept-encoding", 0)
List.keydelete(headers, "accept-encoding", 0) else
headers
true ->
headers
end end
end end
@ -479,6 +359,12 @@ defmodule Pleroma.ReverseProxy do
|> ensure_no_transform() |> ensure_no_transform()
end end
defp merge_headers(headers, extra_headers) do
Enum.reduce(extra_headers, headers, fn {key, value}, headers ->
List.keystore(headers, String.downcase(key), 0, {String.downcase(key), value})
end)
end
defp ensure_no_transform(headers) do defp ensure_no_transform(headers) do
{_, cache_control} = {_, cache_control} =
List.keyfind(headers, "cache-control", 0, {"cache-control", @default_cache_control_header}) List.keyfind(headers, "cache-control", 0, {"cache-control", @default_cache_control_header})
@ -568,20 +454,7 @@ defmodule Pleroma.ReverseProxy do
replace_header(headers, "content-disposition", disposition) replace_header(headers, "content-disposition", disposition)
else else
if opt == true do headers
headers
else
name = inline_filename(content_type)
disposition =
if name do
"inline; filename=\"#{name}\""
else
"inline"
end
replace_header(headers, "content-disposition", disposition)
end
end end
end end
@ -589,13 +462,6 @@ defmodule Pleroma.ReverseProxy do
[{key, value} | Enum.reject(headers, fn {header, _} -> header == key end)] [{key, value} | Enum.reject(headers, fn {header, _} -> header == key end)]
end end
defp inline_filename(content_type) do
case MIME.extensions(content_type) do
[ext | _] when ext != "" -> "inline.#{ext}"
_ -> nil
end
end
defp header_length_constraint(headers, limit) do defp header_length_constraint(headers, limit) do
lengths = lengths =
for {"content-length", value} <- headers, for {"content-length", value} <- headers,
@ -640,9 +506,7 @@ defmodule Pleroma.ReverseProxy do
end end
end end
defp check_read_duration(_, _), do: {:ok, :no_duration_limit} defp check_read_duration(_, _), do: {:ok, :no_duration_limit, :no_duration_limit}
defp increase_read_duration(:no_duration_limit), do: {:ok, :no_duration_limit}
defp increase_read_duration({previous_duration, started}) defp increase_read_duration({previous_duration, started})
when is_integer(previous_duration) and is_integer(started) do when is_integer(previous_duration) and is_integer(started) do

View file

@ -8,6 +8,7 @@ defmodule Pleroma.Web.ApiSpec.NotificationOperation do
alias Pleroma.Web.ApiSpec.Schemas.Account alias Pleroma.Web.ApiSpec.Schemas.Account
alias Pleroma.Web.ApiSpec.Schemas.ApiError alias Pleroma.Web.ApiSpec.Schemas.ApiError
alias Pleroma.Web.ApiSpec.Schemas.BooleanLike alias Pleroma.Web.ApiSpec.Schemas.BooleanLike
alias Pleroma.Web.ApiSpec.Schemas.ChatMessage
alias Pleroma.Web.ApiSpec.Schemas.Status alias Pleroma.Web.ApiSpec.Schemas.Status
alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope
@ -304,7 +305,25 @@ defmodule Pleroma.Web.ApiSpec.NotificationOperation do
page_max_id: %Schema{type: :string, nullable: true}, page_max_id: %Schema{type: :string, nullable: true},
latest_page_notification_at: %Schema{type: :string, format: :"date-time", nullable: true}, latest_page_notification_at: %Schema{type: :string, format: :"date-time", nullable: true},
sample_account_ids: %Schema{type: :array, items: %Schema{type: :string}}, sample_account_ids: %Schema{type: :array, items: %Schema{type: :string}},
status_id: %Schema{type: :string, nullable: true} status_id: %Schema{type: :string, nullable: true},
target_id: %Schema{
type: :string,
description: "ID of the target account for a move notification"
},
emoji: %Schema{
type: :string,
description: "Emoji used for an emoji reaction notification"
},
emoji_url: %Schema{
type: :string,
nullable: true,
description: "URL of the custom emoji used for an emoji reaction notification"
},
chat_message: ChatMessage,
report: %Schema{
type: :object,
description: "Report embedded in a report notification"
}
}, },
required: [ required: [
:group_key, :group_key,

View file

@ -101,13 +101,13 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do
|> Enum.filter(& &1) |> Enum.filter(& &1)
|> Enum.uniq_by(& &1[:id]) |> Enum.uniq_by(& &1[:id])
actors = accounts =
notification_groups notification_groups
|> List.flatten() |> List.flatten()
|> notification_actors() |> notification_accounts()
%{ %{
accounts: AccountView.render("index.json", %{users: actors, for: reading_user}), accounts: AccountView.render("index.json", %{users: accounts, for: reading_user}),
statuses: statuses, statuses: statuses,
notification_groups: notification_groups:
Enum.map( Enum.map(
@ -191,7 +191,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do
defp render_group( defp render_group(
[%Notification{} = notification | _] = notifications, [%Notification{} = notification | _] = notifications,
_reading_user, reading_user,
grouped_types, grouped_types,
notification_group_counts, notification_group_counts,
notification_group_bounds, notification_group_bounds,
@ -237,13 +237,49 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do
response response
end end
if status_activity do response =
Map.put(response, :status_id, to_string(status_activity.id)) if status_activity do
else Map.put(response, :status_id, to_string(status_activity.id))
response else
response
end
put_group_details(response, notification, reading_user)
end
defp put_group_details(response, %Notification{type: "move", activity: activity}, _reading_user) do
case User.get_cached_by_ap_id(activity.data["target"]) do
%User{id: id} -> Map.put(response, :target_id, to_string(id))
_ -> response
end end
end end
defp put_group_details(
response,
%Notification{type: "pleroma:emoji_reaction", activity: activity},
_reading_user
) do
put_emoji(response, activity)
end
defp put_group_details(
response,
%Notification{type: "pleroma:chat_mention", activity: activity},
reading_user
) do
put_chat_message(response, activity, reading_user, %{})
end
defp put_group_details(
response,
%Notification{type: "pleroma:report", activity: activity},
_reading_user
) do
put_report(response, activity)
end
defp put_group_details(response, _notification, _reading_user), do: response
defp status_activity_for_group([%Notification{} = notification | _], grouped_types) do defp status_activity_for_group([%Notification{} = notification | _], grouped_types) do
status_activity_for(notification, grouped_types) status_activity_for(notification, grouped_types)
end end
@ -287,6 +323,19 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do
|> Enum.uniq_by(& &1.id) |> Enum.uniq_by(& &1.id)
end end
defp notification_accounts(notifications) do
move_targets =
notifications
|> Enum.filter(&(&1.type == "move"))
|> Enum.map(&User.get_cached_by_ap_id(&1.activity.data["target"]))
|> Enum.filter(& &1)
notifications
|> notification_actors()
|> Kernel.++(move_targets)
|> Enum.uniq_by(& &1.id)
end
defp put_emoji(response, activity) do defp put_emoji(response, activity) do
response response
|> Map.put(:emoji, activity.data["content"]) |> Map.put(:emoji, activity.data["content"])

View file

@ -631,7 +631,6 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
String.contains?(media_type, "image") -> "image" String.contains?(media_type, "image") -> "image"
String.contains?(media_type, "video") -> "video" String.contains?(media_type, "video") -> "video"
String.contains?(media_type, "audio") -> "audio" String.contains?(media_type, "audio") -> "audio"
attachment["type"] == "Image" -> "image"
true -> "unknown" true -> "unknown"
end end

View file

@ -208,7 +208,6 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do
defp media_proxy_opts do defp media_proxy_opts do
Config.get([:media_proxy, :proxy_opts], []) Config.get([:media_proxy, :proxy_opts], [])
|> Keyword.put_new(:sniff_content_type, true)
end end
defp sandbox(conn, _params) do defp sandbox(conn, _params) do

View file

@ -172,6 +172,14 @@ defmodule Pleroma.Mixfile do
{:gen_smtp, "~> 0.15"}, {:gen_smtp, "~> 0.15"},
{:mua, "~> 0.2.4"}, {:mua, "~> 0.2.4"},
{:mail, "~> 0.3.1"}, {:mail, "~> 0.3.1"},
# Fixes intermittent failure to compile due to an old pc version,
# upstream hasn't made a release with the fix yet.
# https://github.com/Vagabond/erlang-syslog/pull/44
{:syslog,
git: "https://github.com/Vagabond/erlang-syslog",
ref: "614a1fde7a8654bee2815b599bbdaae1bb0323b5",
override: true,
runtime: false},
{:ex_syslogger, "~> 1.5"}, {:ex_syslogger, "~> 1.5"},
{:floki, "~> 0.38"}, {:floki, "~> 0.38"},
{:timex, "~> 3.7"}, {:timex, "~> 3.7"},

View file

@ -139,7 +139,7 @@
"statistex": {:hex, :statistex, "1.1.0", "7fec1eb2f580a0d2c1a05ed27396a084ab064a40cfc84246dbfb0c72a5c761e5", [:mix], [], "hexpm", "f5950ea26ad43246ba2cce54324ac394a4e7408fdcf98b8e230f503a0cba9cf5"}, "statistex": {:hex, :statistex, "1.1.0", "7fec1eb2f580a0d2c1a05ed27396a084ab064a40cfc84246dbfb0c72a5c761e5", [:mix], [], "hexpm", "f5950ea26ad43246ba2cce54324ac394a4e7408fdcf98b8e230f503a0cba9cf5"},
"sweet_xml": {:hex, :sweet_xml, "0.7.5", "803a563113981aaac202a1dbd39771562d0ad31004ddbfc9b5090bdcd5605277", [:mix], [], "hexpm", "193b28a9b12891cae351d81a0cead165ffe67df1b73fe5866d10629f4faefb12"}, "sweet_xml": {:hex, :sweet_xml, "0.7.5", "803a563113981aaac202a1dbd39771562d0ad31004ddbfc9b5090bdcd5605277", [:mix], [], "hexpm", "193b28a9b12891cae351d81a0cead165ffe67df1b73fe5866d10629f4faefb12"},
"swoosh": {:hex, :swoosh, "1.16.12", "cbb24ad512f2f7f24c7a469661c188a00a8c2cd64e0ab54acd1520f132092dfd", [:mix], [{:bandit, ">= 1.0.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mua, "~> 0.2.3", [hex: :mua, repo: "hexpm", optional: true]}, {:multipart, "~> 0.4", [hex: :multipart, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:req, "~> 0.5 or ~> 1.0", [hex: :req, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "0e262df1ae510d59eeaaa3db42189a2aa1b3746f73771eb2616fc3f7ee63cc20"}, "swoosh": {:hex, :swoosh, "1.16.12", "cbb24ad512f2f7f24c7a469661c188a00a8c2cd64e0ab54acd1520f132092dfd", [:mix], [{:bandit, ">= 1.0.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mua, "~> 0.2.3", [hex: :mua, repo: "hexpm", optional: true]}, {:multipart, "~> 0.4", [hex: :multipart, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:req, "~> 0.5 or ~> 1.0", [hex: :req, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "0e262df1ae510d59eeaaa3db42189a2aa1b3746f73771eb2616fc3f7ee63cc20"},
"syslog": {:hex, :syslog, "1.1.0", "6419a232bea84f07b56dc575225007ffe34d9fdc91abe6f1b2f254fd71d8efc2", [:rebar3], [], "hexpm", "4c6a41373c7e20587be33ef841d3de6f3beba08519809329ecc4d27b15b659e1"}, "syslog": {:git, "https://github.com/Vagabond/erlang-syslog", "614a1fde7a8654bee2815b599bbdaae1bb0323b5", [ref: "614a1fde7a8654bee2815b599bbdaae1bb0323b5"]},
"table_rex": {:hex, :table_rex, "4.1.0", "fbaa8b1ce154c9772012bf445bfb86b587430fb96f3b12022d3f35ee4a68c918", [:mix], [], "hexpm", "95932701df195d43bc2d1c6531178fc8338aa8f38c80f098504d529c43bc2601"}, "table_rex": {:hex, :table_rex, "4.1.0", "fbaa8b1ce154c9772012bf445bfb86b587430fb96f3b12022d3f35ee4a68c918", [:mix], [], "hexpm", "95932701df195d43bc2d1c6531178fc8338aa8f38c80f098504d529c43bc2601"},
"telemetry": {:hex, :telemetry, "1.0.0", "0f453a102cdf13d506b7c0ab158324c337c41f1cc7548f0bc0e130bbf0ae9452", [:rebar3], [], "hexpm", "73bc09fa59b4a0284efb4624335583c528e07ec9ae76aca96ea0673850aec57a"}, "telemetry": {:hex, :telemetry, "1.0.0", "0f453a102cdf13d506b7c0ab158324c337c41f1cc7548f0bc0e130bbf0ae9452", [:rebar3], [], "hexpm", "73bc09fa59b4a0284efb4624335583c528e07ec9ae76aca96ea0673850aec57a"},
"telemetry_metrics": {:hex, :telemetry_metrics, "0.6.2", "2caabe9344ec17eafe5403304771c3539f3b6e2f7fb6a6f602558c825d0d0bfb", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9b43db0dc33863930b9ef9d27137e78974756f5f198cae18409970ed6fa5b561"}, "telemetry_metrics": {:hex, :telemetry_metrics, "0.6.2", "2caabe9344ec17eafe5403304771c3539f3b6e2f7fb6a6f602558c825d0d0bfb", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9b43db0dc33863930b9ef9d27137e78974756f5f198cae18409970ed6fa5b561"},

View file

@ -579,56 +579,6 @@ defmodule Pleroma.ReverseProxyTest do
assert Conn.get_resp_header(conn, "content-type") == ["application/octet-stream"] assert Conn.get_resp_header(conn, "content-type") == ["application/octet-stream"]
end end
test "sniffs and preserves an image body with a generic content type", %{conn: conn} do
body = File.read!("test/fixtures/image.jpg")
ClientMock
|> expect(:request, fn :get, "/extensionless", headers, _, _ ->
assert {"accept-encoding", "identity"} in headers
{:ok, 200, [{"content-type", "application/octet-stream"}], %{body: body}}
end)
|> expect(:stream_body, fn %{body: ^body} = client ->
{:ok, body, Map.delete(client, :body)}
end)
|> expect(:stream_body, fn %{} -> :done end)
conn =
ReverseProxy.call(conn, "/extensionless",
sniff_content_type: true,
max_read_duration: :infinity,
req_headers: [{"accept-encoding", "gzip"}]
)
assert conn.resp_body == body
assert Conn.get_resp_header(conn, "content-type") == ["image/jpeg"]
assert Conn.get_resp_header(conn, "content-disposition") == [
"inline; filename=\"inline.jpg\""
]
end
test "redirects a content sniffing failure before committing the response", %{conn: conn} do
url = "https://example.com/extensionless"
ClientMock
|> expect(:request, fn :get, ^url, _, _, _ ->
{:ok, 200, [{"content-type", "application/octet-stream"}], %{url: url}}
end)
|> expect(:stream_body, fn _ -> {:error, :closed} end)
|> expect(:close, fn _ -> :ok end)
conn =
ReverseProxy.call(conn, url,
sniff_content_type: true,
redirect_on_failure: true
)
assert conn.status == 302
assert Conn.get_resp_header(conn, "location") == [url]
assert conn.state == :sent
assert Cachex.get(:failed_proxy_url_cache, url) == {:ok, true}
end
test "preserves a generic content type for a non-image body", %{conn: conn} do test "preserves a generic content type for a non-image body", %{conn: conn} do
body = "not an image" body = "not an image"
@ -823,24 +773,6 @@ defmodule Pleroma.ReverseProxyTest do
conn = ReverseProxy.call(conn, "/disposition") conn = ReverseProxy.call(conn, "/disposition")
assert {"content-type", "image/gif"} in conn.resp_headers assert {"content-type", "image/gif"} in conn.resp_headers
assert {"content-disposition", "inline; filename=\"inline.gif\""} in conn.resp_headers
end
test "forces inline for inline content types overriding upstream attachment", %{
conn: conn
} do
disposition_headers_mock([
{"content-type", "image/png"},
{"content-disposition", "attachment; filename=\"filename.png\""},
{"content-disposition", "attachment; filename=\"duplicate.png\""},
{"content-length", "0"}
])
conn = ReverseProxy.call(conn, "/disposition")
[disposition] = Conn.get_resp_header(conn, "content-disposition")
assert String.starts_with?(disposition, "inline")
refute String.starts_with?(disposition, "attachment")
end end
test "with content-disposition header", %{conn: conn} do test "with content-disposition header", %{conn: conn} do
@ -853,50 +785,6 @@ defmodule Pleroma.ReverseProxyTest do
assert {"content-disposition", "attachment; filename=\"filename.jpg\""} in conn.resp_headers assert {"content-disposition", "attachment; filename=\"filename.jpg\""} in conn.resp_headers
end end
test "with inline_content_types: true leaves upstream headers untouched", %{
conn: conn
} do
# opt == true: the proxy must not synthesise or rewrite content-disposition.
disposition_headers_mock([
{"content-type", "image/png"},
{"content-disposition", "attachment; filename=\"upstream.png\""},
{"content-length", "0"}
])
conn = ReverseProxy.call(conn, "/disposition", inline_content_types: true)
assert {"content-disposition", "attachment; filename=\"upstream.png\""} in conn.resp_headers
end
test "forces bare inline for a whitelisted type with no MIME extension", %{
conn: conn
} do
# image/x-foo-bar has no entry in MIME's database, so inline_filename/1
# returns nil and the disposition should be the bare token "inline".
disposition_headers_mock([
{"content-type", "image/x-foo-bar"},
{"content-length", "0"}
])
conn = ReverseProxy.call(conn, "/disposition", inline_content_types: ["image/x-foo-bar"])
[disposition] = Conn.get_resp_header(conn, "content-disposition")
assert disposition == "inline"
end
test "serves modern browser image types inline", %{conn: conn} do
disposition_headers_mock([
{"content-type", "image/webp"},
{"content-length", "0"}
])
conn = ReverseProxy.call(conn, "/disposition")
assert Conn.get_resp_header(conn, "content-disposition") == [
"inline; filename=\"inline.webp\""
]
end
end end
describe "content-type sanitisation" do describe "content-type sanitisation" do

View file

@ -65,6 +65,15 @@ defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do
} }
test_notifications_rendering([notification], recipient, [expected]) test_notifications_rendering([notification], recipient, [expected])
grouped =
NotificationView.render("grouped_index.json", %{
notifications: [notification],
for: recipient
})
assert [group] = grouped.notification_groups
assert group.chat_message == expected.chat_message
end end
test "Mention notification" do test "Mention notification" do
@ -182,6 +191,16 @@ defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do
} }
test_notifications_rendering([notification], follower, [expected]) test_notifications_rendering([notification], follower, [expected])
grouped =
NotificationView.render("grouped_index.json", %{
notifications: [notification],
for: follower
})
assert [group] = grouped.notification_groups
assert group.target_id == new_user.id
assert Enum.any?(grouped.accounts, &(&1.id == new_user.id))
end end
test "EmojiReact notification" do test "EmojiReact notification" do
@ -210,6 +229,13 @@ defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do
} }
test_notifications_rendering([notification], user, [expected]) test_notifications_rendering([notification], user, [expected])
grouped =
NotificationView.render("grouped_index.json", %{notifications: [notification], for: user})
assert [group] = grouped.notification_groups
assert group.emoji == expected.emoji
assert group.emoji_url == expected.emoji_url
end end
test "EmojiReact custom emoji notification" do test "EmojiReact custom emoji notification" do
@ -295,6 +321,15 @@ defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do
} }
test_notifications_rendering([notification], moderator_user, [expected]) test_notifications_rendering([notification], moderator_user, [expected])
grouped =
NotificationView.render("grouped_index.json", %{
notifications: [notification],
for: moderator_user
})
assert [group] = grouped.notification_groups
assert group.report == expected.report
end end
test "Edit notification" do test "Edit notification" do

View file

@ -668,23 +668,6 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
assert expected == StatusView.render("attachment.json", %{attachment: object}) assert expected == StatusView.render("attachment.json", %{attachment: object})
assert_schema(expected, "Attachment", api_spec) assert_schema(expected, "Attachment", api_spec)
end end
test "uses the ActivityStreams image type when the MIME type is generic" do
object = %{
"type" => "Image",
"url" => [
%{
"mediaType" => "application/octet-stream",
"href" => "https://example.com/extensionless"
}
]
}
attachment = StatusView.render("attachment.json", %{attachment: object})
assert attachment.type == "image"
assert attachment.pleroma.mime_type == "application/octet-stream"
end
end end
test "put the url advertised in the Activity in to the url attribute" do test "put the url advertised in the Activity in to the url attribute" do

View file

@ -71,10 +71,7 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyControllerTest do
test "it performs ReverseProxy.call with valid signature", %{conn: conn, url: url} do test "it performs ReverseProxy.call with valid signature", %{conn: conn, url: url} do
with_mock Pleroma.ReverseProxy, with_mock Pleroma.ReverseProxy,
call: fn _conn, _url, opts -> call: fn _conn, _url, _opts -> %Conn{status: :success} end do
assert opts[:sniff_content_type]
%Conn{status: :success}
end do
assert %Conn{status: :success} = get(conn, url) assert %Conn{status: :success} = get(conn, url)
end end
end end