From 71f5a493f3be15308959a77ad368a61de48721e2 Mon Sep 17 00:00:00 2001 From: Lain Soykaf Date: Thu, 1 Jan 2026 10:02:37 +0400 Subject: [PATCH 01/38] Search: Better sorting for user searches. --- lib/pleroma/user/search.ex | 48 +++++++- test/pleroma/user/search_test.exs | 195 ++++++++++++++++++++++++++++++ 2 files changed, 242 insertions(+), 1 deletion(-) create mode 100644 test/pleroma/user/search_test.exs diff --git a/lib/pleroma/user/search.ex b/lib/pleroma/user/search.ex index 851745714..abc45f62a 100644 --- a/lib/pleroma/user/search.ex +++ b/lib/pleroma/user/search.ex @@ -4,6 +4,7 @@ defmodule Pleroma.User.Search do alias Pleroma.EctoType.ActivityPub.ObjectValidators.Uri, as: UriType + alias Pleroma.Instances.Instance alias Pleroma.Pagination alias Pleroma.User @@ -88,12 +89,13 @@ defmodule Pleroma.User.Search do |> filter_invisible_users() |> filter_internal_users() |> filter_blocked_domains(for_user) + |> filter_unreachable_users() |> fts_search(query_string) |> select_top_users(top_user_ids) |> trigram_rank(query_string) |> boost_search_rank(for_user, top_user_ids) |> subquery() - |> order_by(desc: :search_rank) + |> order_by_search_rank(for_user) |> maybe_restrict_local(for_user) |> maybe_restrict_accepting_chat_messages(capabilities) |> filter_deactivated_users() @@ -196,6 +198,14 @@ defmodule Pleroma.User.Search do defp filter_blocked_domains(query, _), do: query + defp filter_unreachable_users(query) do + from(u in query, + left_join: i in Instance, + on: i.host == fragment("substring(? from '.*://([^/]*)')", u.ap_id), + where: is_nil(i.unreachable_since) + ) + end + defp maybe_resolve(true, user, query) do case {limit(), user} do {:all, _} -> :noop @@ -236,6 +246,16 @@ defmodule Pleroma.User.Search do from(u in subquery(query), select_merge: %{ + search_type: + fragment( + """ + CASE WHEN (?) THEN 2 + WHEN (?) THEN 1 + ELSE 0 END + """, + u.id in ^top_user_ids, + u.id in ^friends_ids or u.id in ^followers_ids + ), search_rank: fragment( """ @@ -261,6 +281,14 @@ defmodule Pleroma.User.Search do defp boost_search_rank(query, _for_user, top_user_ids) do from(u in subquery(query), select_merge: %{ + search_type: + fragment( + """ + CASE WHEN (?) THEN 2 + ELSE 0 END + """, + u.id in ^top_user_ids + ), search_rank: fragment( """ @@ -273,4 +301,22 @@ defmodule Pleroma.User.Search do } ) end + + defp order_by_search_rank(query, %User{}) do + order_by( + query, + [u], + desc: u.search_type, + desc_nulls_last: + fragment( + "CASE WHEN ? = 1 THEN COALESCE(?, ?) ELSE NULL END", + u.search_type, + u.last_status_at, + u.last_active_at + ), + desc: u.search_rank + ) + end + + defp order_by_search_rank(query, _), do: order_by(query, desc: :search_rank) end diff --git a/test/pleroma/user/search_test.exs b/test/pleroma/user/search_test.exs new file mode 100644 index 000000000..c1aca90bc --- /dev/null +++ b/test/pleroma/user/search_test.exs @@ -0,0 +1,195 @@ +# Pleroma: A lightweight social networking server +# Copyright © Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.User.SearchTest do + use Pleroma.DataCase, async: false + + import Pleroma.Factory + + alias Pleroma.Instances + alias Pleroma.Repo + alias Pleroma.User + + describe "search/2 mention suggestions" do + test "prioritizes followed/follower users before others" do + user = insert(:user) + + related = + insert(:user, + local: false, + nickname: "hj@real.example", + ap_id: "https://real.example/users/hj", + last_status_at: ~N[2020-01-01 00:00:00] + ) + + other = insert(:user, nickname: "hj", last_status_at: ~N[2020-01-02 00:00:00]) + + {:ok, _related, _user} = User.follow(related, user) + + results = User.search("hj", for_user: user) |> Enum.map(& &1.id) + + assert results == [related.id, other.id] + end + + test "orders followed/follower users by most recent activity" do + user = insert(:user) + + older = + insert(:user, + local: false, + nickname: "ali@remote.example", + ap_id: "https://remote.example/users/ali", + last_status_at: ~N[2020-01-01 00:00:00] + ) + + newer = + insert(:user, + local: false, + nickname: "alia@remote.example", + ap_id: "https://remote.example/users/alia", + last_status_at: ~N[2020-01-02 00:00:00] + ) + + {:ok, _user, _older} = User.follow(user, older) + {:ok, _user, _newer} = User.follow(user, newer) + + assert [newer.id, older.id] == + User.search("ali", for_user: user) + |> Enum.map(& &1.id) + end + + test "groups followed/follower users first and sorts them by recency" do + user = insert(:user) + + following_newest = + insert(:user, + local: false, + nickname: "mentiontesta@related.example", + ap_id: "https://related.example/users/mentiontesta", + last_status_at: ~N[2020-01-03 00:00:00] + ) + + follower_middle = + insert(:user, + local: false, + nickname: "mentiontestb@related.example", + ap_id: "https://related.example/users/mentiontestb", + last_status_at: ~N[2020-01-02 00:00:00] + ) + + mutual_oldest = + insert(:user, + local: false, + nickname: "mentiontestc@related.example", + ap_id: "https://related.example/users/mentiontestc", + last_status_at: ~N[2020-01-01 00:00:00] + ) + + unrelated_newer = + insert(:user, + local: false, + nickname: "mentiontestd@unrelated.example", + ap_id: "https://unrelated.example/users/mentiontestd", + last_status_at: ~N[2020-01-04 00:00:00] + ) + + {:ok, _user, _following_newest} = User.follow(user, following_newest) + {:ok, _follower_middle, _user} = User.follow(follower_middle, user) + + {:ok, _user, _mutual_oldest} = User.follow(user, mutual_oldest) + {:ok, _mutual_oldest, _user} = User.follow(mutual_oldest, user) + + results = User.search("mentiontest", for_user: user) + + assert Enum.map(results, & &1.id) == + [following_newest.id, follower_middle.id, mutual_oldest.id, unrelated_newer.id] + end + + test "uses last_active_at when last_status_at is missing" do + user = insert(:user) + + older = + insert(:user, + local: false, + nickname: "activefallbacka@remote.example", + ap_id: "https://remote.example/users/activefallbacka", + last_status_at: nil, + last_active_at: ~N[2020-01-01 00:00:00] + ) + + newer = + insert(:user, + local: false, + nickname: "activefallbackb@remote.example", + ap_id: "https://remote.example/users/activefallbackb", + last_status_at: nil, + last_active_at: ~N[2020-01-02 00:00:00] + ) + + {:ok, _user, _older} = User.follow(user, older) + {:ok, _user, _newer} = User.follow(user, newer) + + assert [newer.id, older.id] == + User.search("activefallback", for_user: user) + |> Enum.map(& &1.id) + end + + test "does not return deactivated users even if related" do + user = insert(:user) + + active = + insert(:user, + local: false, + nickname: "deactivatedtesta@remote.example", + ap_id: "https://remote.example/users/deactivatedtesta", + last_status_at: ~N[2020-01-02 00:00:00] + ) + + deactivated = + insert(:user, + local: false, + nickname: "deactivatedtestb@remote.example", + ap_id: "https://remote.example/users/deactivatedtestb", + last_status_at: ~N[2020-01-03 00:00:00] + ) + + {:ok, _user, _active} = User.follow(user, active) + {:ok, _user, _deactivated} = User.follow(user, deactivated) + Repo.update!(Ecto.Changeset.change(deactivated, is_active: false)) + + results = User.search("deactivatedtest", for_user: user) |> Enum.map(& &1.id) + + assert results == [active.id] + end + + test "does not return users from unreachable instances" do + user = insert(:user) + + {:ok, _instance} = Instances.set_unreachable("dead.example") + + dead = + insert(:user, + local: false, + nickname: "ali@dead.example", + ap_id: "https://dead.example/users/ali", + last_status_at: ~N[2020-01-02 00:00:00] + ) + + alive = + insert(:user, + local: false, + nickname: "ali@alive.example", + ap_id: "https://alive.example/users/ali", + last_status_at: ~N[2020-01-02 00:00:00] + ) + + {:ok, _user, _alive} = User.follow(user, alive) + {:ok, _user, _dead} = User.follow(user, dead) + + results = User.search("ali", for_user: user) |> Enum.map(& &1.id) + + assert results == [alive.id] + end + end +end From 3903f12c78f9192ad27defe9738ca9669473c4c9 Mon Sep 17 00:00:00 2001 From: Lain Soykaf Date: Thu, 1 Jan 2026 10:04:55 +0400 Subject: [PATCH 02/38] Add changelog --- changelog.d/user-search-sorting.change | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/user-search-sorting.change diff --git a/changelog.d/user-search-sorting.change b/changelog.d/user-search-sorting.change new file mode 100644 index 000000000..def27e955 --- /dev/null +++ b/changelog.d/user-search-sorting.change @@ -0,0 +1 @@ +Improve user search / autocompletion ordering. From be327ca982654a37d0dc0f977a8e59ded9df1c33 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Wed, 14 Jan 2026 03:17:50 +0100 Subject: [PATCH 03/38] Switch Phoenix back to upstream See for `:sec_websocket_protocol` -> `:sec_websocket_headers` --- changelog.d/phoenix-sec-websocket-headers.change | 1 + lib/pleroma/web.ex | 2 +- lib/pleroma/web/endpoint.ex | 2 +- lib/pleroma/web/mastodon_api/websocket_handler.ex | 12 +++++++----- mix.exs | 3 +-- mix.lock | 2 +- 6 files changed, 12 insertions(+), 10 deletions(-) create mode 100644 changelog.d/phoenix-sec-websocket-headers.change diff --git a/changelog.d/phoenix-sec-websocket-headers.change b/changelog.d/phoenix-sec-websocket-headers.change new file mode 100644 index 000000000..b3cb4ad3f --- /dev/null +++ b/changelog.d/phoenix-sec-websocket-headers.change @@ -0,0 +1 @@ +Switch patched Phoenix 1.7.14 back to upstream with Phoenix 1.8.0+ diff --git a/lib/pleroma/web.ex b/lib/pleroma/web.ex index e7e7e96f9..1c1333aac 100644 --- a/lib/pleroma/web.ex +++ b/lib/pleroma/web.ex @@ -30,7 +30,7 @@ defmodule Pleroma.Web do def controller do quote do - use Phoenix.Controller, namespace: Pleroma.Web + use Phoenix.Controller, formats: [json: "View", html: "View"] import Plug.Conn diff --git a/lib/pleroma/web/endpoint.ex b/lib/pleroma/web/endpoint.ex index bab3c9fd0..fa0bd6c12 100644 --- a/lib/pleroma/web/endpoint.ex +++ b/lib/pleroma/web/endpoint.ex @@ -14,7 +14,7 @@ defmodule Pleroma.Web.Endpoint do websocket: [ path: "/", compress: false, - connect_info: [:sec_websocket_protocol], + connect_info: [:sec_websocket_headers], error_handler: {Pleroma.Web.MastodonAPI.WebsocketHandler, :handle_error, []}, fullsweep_after: 20 ] diff --git a/lib/pleroma/web/mastodon_api/websocket_handler.ex b/lib/pleroma/web/mastodon_api/websocket_handler.ex index 3ed1cdd6c..616f35912 100644 --- a/lib/pleroma/web/mastodon_api/websocket_handler.ex +++ b/lib/pleroma/web/mastodon_api/websocket_handler.ex @@ -245,12 +245,14 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do Plug.Conn.send_resp(conn, 404, "Not Found") end - defp find_access_token(%{ - connect_info: %{sec_websocket_protocol: [token]} - }), - do: token - defp find_access_token(%{params: %{"access_token" => token}}), do: token + defp find_access_token(%{connect_info: %{sec_websocket_headers: sec_headers}}), + do: + Enum.find_value(sec_headers, fn + {"sec-websocket-protocol", v} -> v + _ -> nil + end) + defp find_access_token(_), do: nil end diff --git a/mix.exs b/mix.exs index 48ec9b68f..9f45864e2 100644 --- a/mix.exs +++ b/mix.exs @@ -123,8 +123,7 @@ defmodule Pleroma.Mixfile do # Type `mix help deps` for examples and options. defp deps do [ - {:phoenix, - git: "https://github.com/feld/phoenix", branch: "v1.7.14-websocket-headers", override: true}, + {:phoenix, "~> 1.8.0"}, {:phoenix_ecto, "~> 4.4"}, {:ecto_sql, "~> 3.10"}, {:ecto_enum, "~> 1.4"}, diff --git a/mix.lock b/mix.lock index c469f4f01..60fa6f6b4 100644 --- a/mix.lock +++ b/mix.lock @@ -102,7 +102,7 @@ "open_api_spex": {:hex, :open_api_spex, "3.22.0", "fbf90dc82681dc042a4ee79853c8e989efbba73d9e87439085daf849bbf8bc20", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 3.0 or ~> 4.0 or ~> 5.0 or ~> 6.0", [hex: :poison, repo: "hexpm", optional: true]}, {:ymlr, "~> 2.0 or ~> 3.0 or ~> 4.0 or ~> 5.0", [hex: :ymlr, repo: "hexpm", optional: true]}], "hexpm", "dd751ddbdd709bb4a5313e9a24530da6e66594773c7242a0c2592cbd9f589063"}, "parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"}, "pbkdf2_elixir": {:hex, :pbkdf2_elixir, "1.2.1", "9cbe354b58121075bd20eb83076900a3832324b7dd171a6895fab57b6bb2752c", [:mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}], "hexpm", "d3b40a4a4630f0b442f19eca891fcfeeee4c40871936fed2f68e1c4faa30481f"}, - "phoenix": {:git, "https://github.com/feld/phoenix", "fb6dc76c657422e49600896c64aab4253fceaef6", [branch: "v1.7.14-websocket-headers"]}, + "phoenix": {:hex, :phoenix, "1.8.3", "49ac5e485083cb1495a905e47eb554277bdd9c65ccb4fc5100306b350151aa95", [:mix], [{:bandit, "~> 1.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "36169f95cc2e155b78be93d9590acc3f462f1e5438db06e6248613f27c80caec"}, "phoenix_ecto": {:hex, :phoenix_ecto, "4.6.5", "c4ef322acd15a574a8b1a08eff0ee0a85e73096b53ce1403b6563709f15e1cea", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.1", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "26ec3208eef407f31b748cadd044045c6fd485fbff168e35963d2f9dfff28d4b"}, "phoenix_html": {:hex, :phoenix_html, "3.3.4", "42a09fc443bbc1da37e372a5c8e6755d046f22b9b11343bf885067357da21cb3", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "0249d3abec3714aff3415e7ee3d9786cb325be3151e6c4b3021502c585bf53fb"}, "phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.8.7", "405880012cb4b706f26dd1c6349125bfc903fb9e44d1ea668adaf4e04d4884b7", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:ecto_sqlite3_extras, "~> 1.1.7 or ~> 1.2.0", [hex: :ecto_sqlite3_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.19 or ~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "3a8625cab39ec261d48a13b7468dc619c0ede099601b084e343968309bd4d7d7"}, From 6f86883cca5024c57ba8b0ddecd6ca1161d62dd5 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Wed, 14 Jan 2026 05:15:28 +0100 Subject: [PATCH 04/38] Web: remove legacy :set_put_layout plug Phoenix 1.8 requires a View module with put_layout so can't set it that early. It was introduced in 2019 with commit 1097ce6d9f06a7552652c5990cee12e7b7b3cc59 but nothing seems to provide app.html (anymore?) and it would likely better be set by something like OAuthController / OAuthView. --- lib/pleroma/web.ex | 6 ------ 1 file changed, 6 deletions(-) diff --git a/lib/pleroma/web.ex b/lib/pleroma/web.ex index 1c1333aac..bf7fa86a3 100644 --- a/lib/pleroma/web.ex +++ b/lib/pleroma/web.ex @@ -39,12 +39,6 @@ defmodule Pleroma.Web do alias Pleroma.Web.Router.Helpers, as: Routes - plug(:set_put_layout) - - defp set_put_layout(conn, _) do - put_layout(conn, Pleroma.Config.get(:app_layout, "app.html")) - end - # Marks plugs intentionally skipped and blocks their execution if present in plugs chain defp skip_plug(conn, plug_modules) do plug_modules From 592be493c825e324b9a161e984ad39d412575184 Mon Sep 17 00:00:00 2001 From: Lain Soykaf Date: Mon, 11 May 2026 19:12:03 +0400 Subject: [PATCH 05/38] Use published Pleroma MFM parser package --- mix.exs | 4 +--- mix.lock | 2 +- test/pleroma/web/common_api_test.exs | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/mix.exs b/mix.exs index 88f98d54e..db9a9b5ae 100644 --- a/mix.exs +++ b/mix.exs @@ -160,9 +160,7 @@ defmodule Pleroma.Mixfile do {:sweet_xml, "~> 0.7.5"}, {:earmark, "1.4.46"}, {:bbcode_pleroma, "~> 0.2.0"}, - {:mfm_parser, - git: "https://akkoma.dev/AkkomaGang/mfm-parser.git", - ref: "360a30267a847810a63ab48f606ba227b2ca05f0"}, + {:pleroma_mfm_parser, "~> 0.2.1"}, {:cors_plug, "~> 2.0"}, {:web_push_encryption, "~> 0.3.1"}, {:swoosh, "~> 1.16.12"}, diff --git a/mix.lock b/mix.lock index 976a856b0..af566967c 100644 --- a/mix.lock +++ b/mix.lock @@ -79,7 +79,6 @@ "makeup_erlang": {:hex, :makeup_erlang, "1.0.2", "03e1804074b3aa64d5fad7aa64601ed0fb395337b982d9bcf04029d68d51b6a7", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "af33ff7ef368d5893e4a267933e7744e46ce3cf1f61e2dccf53a111ed3aa3727"}, "meck": {:hex, :meck, "0.9.2", "85ccbab053f1db86c7ca240e9fc718170ee5bda03810a6292b5306bf31bae5f5", [:rebar3], [], "hexpm", "81344f561357dc40a8344afa53767c32669153355b626ea9fcbc8da6b3045826"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, - "mfm_parser": {:git, "https://akkoma.dev/AkkomaGang/mfm-parser.git", "360a30267a847810a63ab48f606ba227b2ca05f0", [ref: "360a30267a847810a63ab48f606ba227b2ca05f0"]}, "mime": {:hex, :mime, "1.6.0", "dabde576a497cef4bbdd60aceee8160e02a6c89250d6c0b29e56c0dfb00db3d2", [:mix], [], "hexpm", "31a1a8613f8321143dde1dafc36006a17d28d02bdfecb9e95a880fa7aabd19a7"}, "mimerl": {:hex, :mimerl, "1.5.0", "f35aca6f23242339b3666e0ac0702379e362b469d0aea167f6cc713547e777ed", [:rebar3], [], "hexpm", "db648ce065bae14ea84ca8b5dd123f42f49417cef693541110bf6f9e9be9ecc4"}, "mint": {:hex, :mint, "1.7.1", "113fdb2b2f3b59e47c7955971854641c61f378549d73e829e1768de90fc1abf1", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "fceba0a4d0f24301ddee3024ae116df1c3f4bb7a563a731f45fdfeb9d39a231b"}, @@ -113,6 +112,7 @@ "phoenix_swoosh": {:hex, :phoenix_swoosh, "1.2.1", "b74ccaa8046fbc388a62134360ee7d9742d5a8ae74063f34eb050279de7a99e1", [:mix], [{:finch, "~> 0.8", [hex: :finch, repo: "hexpm", optional: true]}, {:hackney, "~> 1.10", [hex: :hackney, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6", [hex: :phoenix, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_view, "~> 1.0 or ~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:swoosh, "~> 1.5", [hex: :swoosh, repo: "hexpm", optional: false]}], "hexpm", "4000eeba3f9d7d1a6bf56d2bd56733d5cadf41a7f0d8ffe5bb67e7d667e204a2"}, "phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"}, "phoenix_view": {:hex, :phoenix_view, "2.0.4", "b45c9d9cf15b3a1af5fb555c674b525391b6a1fe975f040fb4d913397b31abf4", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "4e992022ce14f31fe57335db27a28154afcc94e9983266835bb3040243eb620b"}, + "pleroma_mfm_parser": {:hex, :pleroma_mfm_parser, "0.2.1", "87a40fec4e2e035447df34e1c98e1b816b6a448d49baea73eb2273531ac5ce66", [:mix], [], "hexpm", "ec4845461da95d63ab3d592086d65114c45057a1f2d48d768f3def45b0d7696f"}, "plug": {:hex, :plug, "1.19.1", "09bac17ae7a001a68ae393658aa23c7e38782be5c5c00c80be82901262c394c0", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "560a0017a8f6d5d30146916862aaf9300b7280063651dd7e532b8be168511e62"}, "plug_cowboy": {:hex, :plug_cowboy, "2.7.4", "729c752d17cf364e2b8da5bdb34fb5804f56251e88bb602aff48ae0bd8673d11", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "9b85632bd7012615bae0a5d70084deb1b25d2bcbb32cab82d1e9a1e023168aa3"}, "plug_crypto": {:hex, :plug_crypto, "2.1.1", "19bda8184399cb24afa10be734f84a16ea0a2bc65054e23a62bb10f06bc89491", [:mix], [], "hexpm", "6470bce6ffe41c8bd497612ffde1a7e4af67f36a15eea5f921af71cf3e11247c"}, diff --git a/test/pleroma/web/common_api_test.exs b/test/pleroma/web/common_api_test.exs index ea1795c0b..5d0ad4572 100644 --- a/test/pleroma/web/common_api_test.exs +++ b/test/pleroma/web/common_api_test.exs @@ -740,7 +740,7 @@ defmodule Pleroma.Web.CommonAPITest do {:ok, activity} = CommonAPI.post(user, %{ - status: "$[spin.speed=1s=boom malformed]", + status: "$[spin malformed", content_type: "text/x.misskeymarkdown" }) From 61feb3dfcdebf29186e214a92bd62c6cb1ac9583 Mon Sep 17 00:00:00 2001 From: Lain Soykaf Date: Mon, 11 May 2026 21:01:59 +0400 Subject: [PATCH 06/38] Update http_signatures to 0.1.3 --- mix.exs | 2 +- mix.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mix.exs b/mix.exs index db9a9b5ae..9c10cacc0 100644 --- a/mix.exs +++ b/mix.exs @@ -173,7 +173,7 @@ defmodule Pleroma.Mixfile do {:timex, "~> 3.7"}, {:ueberauth, "~> 0.10"}, {:linkify, "~> 0.5.3"}, - {:http_signatures, "~> 0.1.2"}, + {:http_signatures, "~> 0.1.3"}, {:telemetry, "~> 1.0.0", override: true}, {:poolboy, "~> 1.5"}, {:prom_ex, "~> 1.9"}, diff --git a/mix.lock b/mix.lock index af566967c..da0c1c60c 100644 --- a/mix.lock +++ b/mix.lock @@ -62,7 +62,7 @@ "hackney": {:hex, :hackney, "1.20.1", "8d97aec62ddddd757d128bfd1df6c5861093419f8f7a4223823537bad5d064e2", [:rebar3], [{:certifi, "~> 2.12.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "fe9094e5f1a2a2c0a7d10918fee36bfec0ec2a979994cff8cfe8058cd9af38e3"}, "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, "html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"}, - "http_signatures": {:hex, :http_signatures, "0.1.2", "ed1cc7043abcf5bb4f30d68fb7bad9d618ec1a45c4ff6c023664e78b67d9c406", [:mix], [], "hexpm", "f08aa9ac121829dae109d608d83c84b940ef2f183ae50f2dd1e9a8bc619d8be7"}, + "http_signatures": {:hex, :http_signatures, "0.1.3", "19f26aee35b4684e37efdce3ac4638605e6e41a04368186bd39d2b6138a60ea9", [:mix], [{:plug, "~> 1.18", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "20313a65516db88006f85b090f6f76cc5b04e9609b45943657e6781eb91174f4"}, "httpoison": {:hex, :httpoison, "1.8.2", "9eb9c63ae289296a544842ef816a85d881d4a31f518a0fec089aaa744beae290", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "2bb350d26972e30c96e2ca74a1aaf8293d61d0742ff17f01e0279fef11599921"}, "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, "inet_cidr": {:hex, :inet_cidr, "1.0.8", "d26bb7bdbdf21ae401ead2092bf2bb4bf57fe44a62f5eaa5025280720ace8a40", [:mix], [], "hexpm", "d5b26da66603bb56c933c65214c72152f0de9a6ea53618b56d63302a68f6a90e"}, From 7f4890b6a9beb60c8daeb59a54d19113386ebdd8 Mon Sep 17 00:00:00 2001 From: Lain Soykaf Date: Mon, 11 May 2026 21:12:34 +0400 Subject: [PATCH 07/38] Add changelog for http_signatures update --- changelog.d/http-signatures-0.1.3.fix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/http-signatures-0.1.3.fix diff --git a/changelog.d/http-signatures-0.1.3.fix b/changelog.d/http-signatures-0.1.3.fix new file mode 100644 index 000000000..3be1b9e9f --- /dev/null +++ b/changelog.d/http-signatures-0.1.3.fix @@ -0,0 +1 @@ +Fix compatibility with timestamped HTTP Signatures used by GoToSocial From ab9fd33762cb8a0cada9e526296d1e96f039b10b Mon Sep 17 00:00:00 2001 From: Lain Soykaf Date: Mon, 11 May 2026 22:10:08 +0400 Subject: [PATCH 08/38] Fix Phoenix upstream migration regressions --- lib/pleroma/web.ex | 9 ++++++++ .../web/mastodon_api/websocket_handler.ex | 22 ++++++++++++------- .../integration/mastodon_websocket_test.exs | 20 +++++++++++++++++ .../static_fe/static_fe_controller_test.exs | 9 ++++++++ 4 files changed, 52 insertions(+), 8 deletions(-) diff --git a/lib/pleroma/web.ex b/lib/pleroma/web.ex index bf7fa86a3..70e7f6458 100644 --- a/lib/pleroma/web.ex +++ b/lib/pleroma/web.ex @@ -39,6 +39,15 @@ defmodule Pleroma.Web do alias Pleroma.Web.Router.Helpers, as: Routes + plug(:set_put_layout) + + defp set_put_layout(conn, _) do + case Pleroma.Config.get(:app_layout, "app.html") do + false -> put_layout(conn, false) + layout -> put_layout(conn, {Pleroma.Web.LayoutView, layout}) + end + end + # Marks plugs intentionally skipped and blocks their execution if present in plugs chain defp skip_plug(conn, plug_modules) do plug_modules diff --git a/lib/pleroma/web/mastodon_api/websocket_handler.ex b/lib/pleroma/web/mastodon_api/websocket_handler.ex index 616f35912..2b698bd5d 100644 --- a/lib/pleroma/web/mastodon_api/websocket_handler.ex +++ b/lib/pleroma/web/mastodon_api/websocket_handler.ex @@ -245,14 +245,20 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do Plug.Conn.send_resp(conn, 404, "Not Found") end - defp find_access_token(%{params: %{"access_token" => token}}), do: token + defp find_access_token(%{connect_info: %{sec_websocket_headers: sec_headers}} = transport_info) do + find_sec_websocket_protocol(sec_headers) || find_access_token_from_params(transport_info) + end - defp find_access_token(%{connect_info: %{sec_websocket_headers: sec_headers}}), - do: - Enum.find_value(sec_headers, fn - {"sec-websocket-protocol", v} -> v - _ -> nil - end) + defp find_access_token(transport_info), do: find_access_token_from_params(transport_info) - defp find_access_token(_), do: nil + defp find_sec_websocket_protocol(sec_headers) do + Enum.find_value(sec_headers, fn + {"sec-websocket-protocol", token} -> token + _ -> nil + end) + end + + defp find_access_token_from_params(%{params: %{"access_token" => token}}), do: token + + defp find_access_token_from_params(_), do: nil end diff --git a/test/pleroma/integration/mastodon_websocket_test.exs b/test/pleroma/integration/mastodon_websocket_test.exs index 47f6f5f76..604382be2 100644 --- a/test/pleroma/integration/mastodon_websocket_test.exs +++ b/test/pleroma/integration/mastodon_websocket_test.exs @@ -279,6 +279,26 @@ defmodule Pleroma.Integration.MastodonWebsocketTest do end) end + test "accepts Phoenix 1.8 sec-websocket headers connect info before query params", %{ + token: token, + user: user + } do + assert {:ok, state} = + Pleroma.Web.MastodonAPI.WebsocketHandler.connect(%{ + params: %{"stream" => "user", "access_token" => "invalid"}, + connect_info: %{ + sec_websocket_headers: [ + {"sec-websocket-version", "13"}, + {"sec-websocket-protocol", token.token} + ] + } + }) + + assert state.user.id == user.id + assert state.oauth_token.id == token.id + assert state.topics != [] + end + test "accepts valid token on client-sent event", %{token: token} do assert {:ok, pid} = start_socket() diff --git a/test/pleroma/web/static_fe/static_fe_controller_test.exs b/test/pleroma/web/static_fe/static_fe_controller_test.exs index 2fae83305..68ded6906 100644 --- a/test/pleroma/web/static_fe/static_fe_controller_test.exs +++ b/test/pleroma/web/static_fe/static_fe_controller_test.exs @@ -28,6 +28,15 @@ defmodule Pleroma.Web.StaticFE.StaticFEControllerTest do assert html_response(conn, 200) =~ user.nickname end + test "renders profile HTML inside the default app layout", %{conn: conn, user: user} do + conn = get(conn, "/users/#{user.nickname}") + + html = html_response(conn, 200) + assert html =~ "" + assert html =~ ~s(class="instance-header") + assert html =~ ~s() + end + test "404 when user not found", %{conn: conn} do conn = get(conn, "/users/limpopo") From 8a56cf5c0fe58904ad2e6ae03ee7cddf7d2adc09 Mon Sep 17 00:00:00 2001 From: Lain Soykaf Date: Mon, 11 May 2026 22:19:06 +0400 Subject: [PATCH 09/38] Clarify websocket token precedence test --- test/pleroma/integration/mastodon_websocket_test.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/pleroma/integration/mastodon_websocket_test.exs b/test/pleroma/integration/mastodon_websocket_test.exs index 604382be2..078bb643c 100644 --- a/test/pleroma/integration/mastodon_websocket_test.exs +++ b/test/pleroma/integration/mastodon_websocket_test.exs @@ -279,7 +279,7 @@ defmodule Pleroma.Integration.MastodonWebsocketTest do end) end - test "accepts Phoenix 1.8 sec-websocket headers connect info before query params", %{ + test "prefers sec-websocket-protocol token over query access_token", %{ token: token, user: user } do From 71afba4825eaa009e506a74e11dfece99dc08e39 Mon Sep 17 00:00:00 2001 From: Lain Soykaf Date: Tue, 12 May 2026 08:52:42 +0400 Subject: [PATCH 10/38] Signature: Treat HTTP signature errors as invalid --- lib/pleroma/signature.ex | 2 +- test/pleroma/signature_test.exs | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/signature.ex b/lib/pleroma/signature.ex index fca61799b..382cf9db3 100644 --- a/lib/pleroma/signature.ex +++ b/lib/pleroma/signature.ex @@ -104,7 +104,7 @@ defmodule Pleroma.Signature do |> put_req_header("(request-target)", request_target) |> put_req_header("@request-target", request_target) - @http_signatures_impl.validate_conn(conn) + @http_signatures_impl.validate_conn(conn) == true end @spec validate_signature(Plug.Conn.t()) :: boolean() diff --git a/test/pleroma/signature_test.exs b/test/pleroma/signature_test.exs index 572d7acc3..0c7c4c840 100644 --- a/test/pleroma/signature_test.exs +++ b/test/pleroma/signature_test.exs @@ -11,6 +11,7 @@ defmodule Pleroma.SignatureTest do import Mock alias Pleroma.Signature + alias Pleroma.StubbedHTTPSignaturesMock, as: HTTPSignaturesMock setup do mock(fn env -> apply(HttpRequestMock, :request, [env]) end) @@ -103,6 +104,18 @@ defmodule Pleroma.SignatureTest do end end + describe "validate_signature/1" do + test "treats HTTP signature errors as failed validation" do + conn = %Plug.Conn{method: "GET", request_path: "/inbox", req_headers: []} + + Mox.expect(HTTPSignaturesMock, :validate_conn, fn _conn -> + {:error, :request_target_header} + end) + + assert Signature.validate_signature(conn) == false + end + end + describe "key_id_to_actor_id/1" do test "it properly deduces the actor id for misskey" do assert Signature.key_id_to_actor_id("https://example.com/users/1234/publickey") == From ea886dc36b3727d7bd22ede1bdb90de3dfb8668e Mon Sep 17 00:00:00 2001 From: Phantasm Date: Tue, 12 May 2026 11:37:01 +0200 Subject: [PATCH 11/38] EnsureHostMatchesPlug: Ensure Host header matches instance URI --- changelog.d/host-header-verification.security | 1 + .../web/plugs/ensure_host_matches_plug.ex | 68 +++++++++++++++++++ lib/pleroma/web/router.ex | 1 + 3 files changed, 70 insertions(+) create mode 100644 changelog.d/host-header-verification.security create mode 100644 lib/pleroma/web/plugs/ensure_host_matches_plug.ex diff --git a/changelog.d/host-header-verification.security b/changelog.d/host-header-verification.security new file mode 100644 index 000000000..11a0784b2 --- /dev/null +++ b/changelog.d/host-header-verification.security @@ -0,0 +1 @@ +Ensure Host header is present and matches instance URI diff --git a/lib/pleroma/web/plugs/ensure_host_matches_plug.ex b/lib/pleroma/web/plugs/ensure_host_matches_plug.ex new file mode 100644 index 000000000..87c4c0531 --- /dev/null +++ b/lib/pleroma/web/plugs/ensure_host_matches_plug.ex @@ -0,0 +1,68 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2026 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.Plugs.EnsureHostMatchesPlug do + @moduledoc "Ensures Host header matches instance" + + alias Pleroma.Web.Endpoint + + import Plug.Conn + + def init(options), do: options + + @spec call(Plug.Conn.t(), Plug.opts()) :: Plug.Conn.t() + def call(%Plug.Conn{assigns: %{valid_signature: true}} = conn, _opts) do + # Host header is scheme-less, URI.parse needs the // + host_header = get_req_header(conn, "host") + host_uri = URI.parse("//#{host_header}") + instance_uri = URI.parse(Endpoint.url()) + instance_scheme_port = URI.default_port(instance_uri.scheme) + + case host_header do + [host] -> + cond do + host == "" -> + resp(conn, 400, "Host header not provided") |> halt() + + true -> + if host_matches?(host_uri, instance_uri, instance_scheme_port), + do: assign(conn, :valid_host_header, true), + else: resp(conn, 400, "Host header does not match this instance") |> halt() + end + + [_head | _rest] -> + conn + |> resp(400, "More than one Host header provided") + |> halt() + + [] -> + conn + |> resp(400, "Host header not provided") + |> halt() + end + end + + # Host header may not be provided, but signature verification failed anyway + def call(conn, _opts), do: conn + + defp case_insensitive_compare(checked, authority) do + String.downcase(checked) == String.downcase(authority) + end + + # Host header did not provide port + # Host header is scheme-less, URI.parse does not provide default port + defp host_matches?(%URI{host: req_host, port: nil}, %URI{host: instance_host}, _), + do: case_insensitive_compare(req_host, instance_host) + + # Host header provided a port, reverse proxy configuration (port cannot match Endpoint port) + # Both port 80 and 443 are valid based on Endpoint configuration + defp host_matches?(%URI{host: req_host, port: port}, %URI{host: instance_host}, port), + do: case_insensitive_compare(req_host, instance_host) + + # Host header provided port, configuration without reverse proxy (port matches Endpoint port) + defp host_matches?(%URI{host: req_host, port: port}, %URI{host: instance_host, port: port}, _), + do: case_insensitive_compare(req_host, instance_host) + + defp host_matches?(_, _, _), do: false +end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index c91ca8c97..813c5c482 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -216,6 +216,7 @@ defmodule Pleroma.Web.Router do pipeline :http_signature do plug(Pleroma.Web.Plugs.HTTPSignaturePlug) plug(Pleroma.Web.Plugs.MappedSignatureToIdentityPlug) + plug(Pleroma.Web.Plugs.EnsureHostMatchesPlug) end pipeline :inbox_guard do From d6d0ce72604ee450560916bb29297188579faa0e Mon Sep 17 00:00:00 2001 From: Phantasm Date: Tue, 12 May 2026 14:00:28 +0200 Subject: [PATCH 12/38] EnsureHostMatchesPlug: Add tests --- .../plugs/ensure_host_matches_plug_test.exs | 123 ++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 test/pleroma/web/plugs/ensure_host_matches_plug_test.exs diff --git a/test/pleroma/web/plugs/ensure_host_matches_plug_test.exs b/test/pleroma/web/plugs/ensure_host_matches_plug_test.exs new file mode 100644 index 000000000..fbebc21d9 --- /dev/null +++ b/test/pleroma/web/plugs/ensure_host_matches_plug_test.exs @@ -0,0 +1,123 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2026 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.Plugs.EnsureHostMatchesPlugTest do + use Pleroma.Web.ConnCase + + alias Pleroma.Web.Endpoint + alias Pleroma.Web.Plugs.EnsureHostMatchesPlug + + import Mock + import Plug.Conn + import Tesla.Mock + + setup do + mock(fn env -> apply(HttpRequestMock, :request, [env]) end) + :ok + end + + defp set_host(conn, host), do: %{conn | req_headers: conn.req_headers ++ [{"host", host}]} + + describe "EnsureHostMatchesPlug" do + setup do + conn = build_conn(:post, "/cofe") |> assign(:valid_signature, true) + [conn: conn] + end + + test "gracefully handles no Host header", %{conn: conn} do + conn = EnsureHostMatchesPlug.call(conn, %{}) + + assert conn.status == 400 + assert conn.halted == true + assert conn.resp_body == "Host header not provided" + end + + test "gracefully handles empty Host header", %{conn: conn} do + conn = + conn + |> set_host("") + |> EnsureHostMatchesPlug.call(%{}) + + assert conn.status == 400 + assert conn.halted == true + assert conn.resp_body == "Host header not provided" + end + + test "it rejects Host header not matching Endpoint URL", %{conn: conn} do + conn = + conn + |> set_host("invalid.example.com") + |> EnsureHostMatchesPlug.call(%{}) + + assert conn.status == 400 + assert conn.halted == true + assert conn.resp_body == "Host header does not match this instance" + end + + test "it rejects Host header not matching Endpoint port", %{conn: conn} do + endpoint = URI.parse(Endpoint.url()) + + conn = + conn + |> set_host("#{endpoint.host}:25") + |> EnsureHostMatchesPlug.call(%{}) + + assert conn.status == 400 + assert conn.halted == true + assert conn.resp_body == "Host header does not match this instance" + end + + test "it rejects multiple Host headers", %{conn: conn} do + conn = + conn + |> set_host("host1.example.com") + |> set_host("host2.example.com") + |> EnsureHostMatchesPlug.call(%{}) + + assert conn.status == 400 + assert conn.halted == true + assert conn.resp_body == "More than one Host header provided" + end + + test "it works for Host header with port as 80", %{conn: conn} do + endpoint = URI.parse(Endpoint.url()) + + conn = + conn + |> set_host("#{endpoint.host}:80") + |> EnsureHostMatchesPlug.call(%{}) + + assert conn.halted == false + assert Map.get(conn.assigns, :valid_host_header, nil) + end + + test "it works for Host header with port as 443", %{conn: conn} do + with_mock Pleroma.Web.Endpoint, url: fn -> "https://localhost:4001" end do + endpoint = URI.parse(Endpoint.url()) + + conn = + conn + |> set_host("#{endpoint.host}:443") + |> EnsureHostMatchesPlug.call(%{}) + + assert conn.halted == false + assert Map.get(conn.assigns, :valid_host_header, nil) + end + end + + test "it works for Host header with port as same as Endpoint (no reverse proxy config)", %{ + conn: conn + } do + endpoint = URI.parse(Endpoint.url()) + + conn = + conn + |> set_host("#{endpoint.host}:#{endpoint.port}") + |> EnsureHostMatchesPlug.call(%{}) + + assert conn.halted == false + assert Map.get(conn.assigns, :valid_host_header, nil) + end + end +end From 90e390e45b1c1af4bbc8e2e68affb41fd621ea3e Mon Sep 17 00:00:00 2001 From: Phantasm Date: Tue, 12 May 2026 16:48:49 +0200 Subject: [PATCH 13/38] fix tests --- test/support/conn_case.ex | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/support/conn_case.ex b/test/support/conn_case.ex index f010fec33..c01516169 100644 --- a/test/support/conn_case.ex +++ b/test/support/conn_case.ex @@ -119,7 +119,10 @@ defmodule Pleroma.Web.ConnCase do DataCase.stub_pipeline() Mox.verify_on_exit!() + endpoint = URI.parse(Pleroma.Web.Endpoint.url()) + conn = Phoenix.ConnTest.build_conn() + conn = %{conn | req_headers: [{"host", "#{endpoint.host}:#{endpoint.port}"}]} - {:ok, conn: Phoenix.ConnTest.build_conn()} + {:ok, conn: conn} end end From 35b5447f3f21a0a30370c86efffee531f56f1ea7 Mon Sep 17 00:00:00 2001 From: Phantasm Date: Tue, 12 May 2026 17:02:28 +0200 Subject: [PATCH 14/38] EnsureHostMatchesPlug: Add more tests --- .../plugs/ensure_host_matches_plug_test.exs | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/test/pleroma/web/plugs/ensure_host_matches_plug_test.exs b/test/pleroma/web/plugs/ensure_host_matches_plug_test.exs index fbebc21d9..67252e7a7 100644 --- a/test/pleroma/web/plugs/ensure_host_matches_plug_test.exs +++ b/test/pleroma/web/plugs/ensure_host_matches_plug_test.exs @@ -55,6 +55,19 @@ defmodule Pleroma.Web.Plugs.EnsureHostMatchesPlugTest do assert conn.resp_body == "Host header does not match this instance" end + test "it rejects Host header not matching Endpoint with port", %{conn: conn} do + endpoint = URI.parse(Endpoint.url()) + + conn = + conn + |> set_host("invalid.example.com:#{endpoint.port}") + |> EnsureHostMatchesPlug.call(%{}) + + assert conn.status == 400 + assert conn.halted == true + assert conn.resp_body == "Host header does not match this instance" + end + test "it rejects Host header not matching Endpoint port", %{conn: conn} do endpoint = URI.parse(Endpoint.url()) @@ -80,6 +93,18 @@ defmodule Pleroma.Web.Plugs.EnsureHostMatchesPlugTest do assert conn.resp_body == "More than one Host header provided" end + test "it works for Host header without port", %{conn: conn} do + endpoint = URI.parse(Endpoint.url()) + + conn = + conn + |> set_host("#{endpoint.host}") + |> EnsureHostMatchesPlug.call(%{}) + + assert conn.halted == false + assert Map.get(conn.assigns, :valid_host_header, nil) + end + test "it works for Host header with port as 80", %{conn: conn} do endpoint = URI.parse(Endpoint.url()) From 0cf865f0254f33fba312a9342c635b7300ea0291 Mon Sep 17 00:00:00 2001 From: Lain Soykaf Date: Tue, 12 May 2026 23:50:30 +0400 Subject: [PATCH 15/38] Reject third-party remote reports --- changelog.d/reject-third-party-reports.fix | 1 + lib/pleroma/web/activity_pub/transmogrifier.ex | 7 +++++++ .../web/activity_pub/transmogrifier_test.exs | 17 +++++++++++++++++ 3 files changed, 25 insertions(+) create mode 100644 changelog.d/reject-third-party-reports.fix diff --git a/changelog.d/reject-third-party-reports.fix b/changelog.d/reject-third-party-reports.fix new file mode 100644 index 000000000..7d4e87b94 --- /dev/null +++ b/changelog.d/reject-third-party-reports.fix @@ -0,0 +1 @@ +Reject incoming reports when both the reporter and reported account are remote diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 4421da26c..261272d08 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -430,6 +430,12 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do end) end + defp reject_third_party_report(%User{local: false}, %User{local: false} = account) do + {:reject, "[Transmogrifier] third-party report: #{account.ap_id}"} + end + + defp reject_third_party_report(_, _), do: :ok + def handle_incoming(data, options \\ []) do data |> fix_recursive(&strip_internal_fields/1) @@ -447,6 +453,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do %User{} = actor <- User.get_cached_by_ap_id(actor), # Reduce the object list to find the reported user. %User{} = account <- get_reported(objects), + :ok <- reject_third_party_report(actor, account), # Remove the reported user from the object list. statuses <- Enum.filter(objects, fn ap_id -> ap_id != account.ap_id end) do %{ diff --git a/test/pleroma/web/activity_pub/transmogrifier_test.exs b/test/pleroma/web/activity_pub/transmogrifier_test.exs index c1e01557d..b20f15cbd 100644 --- a/test/pleroma/web/activity_pub/transmogrifier_test.exs +++ b/test/pleroma/web/activity_pub/transmogrifier_test.exs @@ -86,6 +86,23 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do assert activity.data["cc"] == [user.ap_id] end + test "it rejects Flag activities when both reporter and reported account are remote" do + reporter = insert(:user, local: false, domain: "mastodon.cat") + reported = insert(:user, local: false, domain: "nicecrew.digital") + + message = %{ + "@context" => "https://www.w3.org/ns/activitystreams", + "actor" => reporter.ap_id, + "content" => "blocked AND reported!!!", + "object" => [reported.ap_id, "https://nicecrew.digital/objects/report-status"], + "type" => "Flag" + } + + assert {:reject, reason} = Transmogrifier.handle_incoming(message) + assert reason =~ "third-party report" + refute "Flag" |> Pleroma.Activity.Queries.by_type() |> Pleroma.Repo.one() + end + test "it accepts Move activities" do old_user = insert(:user) new_user = insert(:user) From 6f415cf3fcdad8fc8d440a00c75e8388d99677e5 Mon Sep 17 00:00:00 2001 From: Phantasm Date: Tue, 12 May 2026 23:31:55 +0200 Subject: [PATCH 16/38] EnsureHostMatchesPlug: Remove match against default scheme port Checking against the default port of the Endpoint URL scheme is redundant as normal instances will have the combination https/443 by default created by pleroma.instance gen, Tor-only instances should have combination http/80 and local testing instances httt/XXXX. The default scheme port doesn't add anything usefull in these configs. --- .../web/plugs/ensure_host_matches_plug.ex | 17 ++++------- .../plugs/ensure_host_matches_plug_test.exs | 29 +------------------ 2 files changed, 7 insertions(+), 39 deletions(-) diff --git a/lib/pleroma/web/plugs/ensure_host_matches_plug.ex b/lib/pleroma/web/plugs/ensure_host_matches_plug.ex index 87c4c0531..ab2129f9e 100644 --- a/lib/pleroma/web/plugs/ensure_host_matches_plug.ex +++ b/lib/pleroma/web/plugs/ensure_host_matches_plug.ex @@ -17,7 +17,6 @@ defmodule Pleroma.Web.Plugs.EnsureHostMatchesPlug do host_header = get_req_header(conn, "host") host_uri = URI.parse("//#{host_header}") instance_uri = URI.parse(Endpoint.url()) - instance_scheme_port = URI.default_port(instance_uri.scheme) case host_header do [host] -> @@ -26,7 +25,7 @@ defmodule Pleroma.Web.Plugs.EnsureHostMatchesPlug do resp(conn, 400, "Host header not provided") |> halt() true -> - if host_matches?(host_uri, instance_uri, instance_scheme_port), + if host_matches?(host_uri, instance_uri), do: assign(conn, :valid_host_header, true), else: resp(conn, 400, "Host header does not match this instance") |> halt() end @@ -52,17 +51,13 @@ defmodule Pleroma.Web.Plugs.EnsureHostMatchesPlug do # Host header did not provide port # Host header is scheme-less, URI.parse does not provide default port - defp host_matches?(%URI{host: req_host, port: nil}, %URI{host: instance_host}, _), + defp host_matches?(%URI{host: req_host, port: nil}, %URI{host: instance_host}), do: case_insensitive_compare(req_host, instance_host) - # Host header provided a port, reverse proxy configuration (port cannot match Endpoint port) - # Both port 80 and 443 are valid based on Endpoint configuration - defp host_matches?(%URI{host: req_host, port: port}, %URI{host: instance_host}, port), + # Host header provided a port + # Any port specified in the Endpoint url configuration is valid here + defp host_matches?(%URI{host: req_host, port: port}, %URI{host: instance_host, port: port}), do: case_insensitive_compare(req_host, instance_host) - # Host header provided port, configuration without reverse proxy (port matches Endpoint port) - defp host_matches?(%URI{host: req_host, port: port}, %URI{host: instance_host, port: port}, _), - do: case_insensitive_compare(req_host, instance_host) - - defp host_matches?(_, _, _), do: false + defp host_matches?(_, _), do: false end diff --git a/test/pleroma/web/plugs/ensure_host_matches_plug_test.exs b/test/pleroma/web/plugs/ensure_host_matches_plug_test.exs index 67252e7a7..8ace74dfb 100644 --- a/test/pleroma/web/plugs/ensure_host_matches_plug_test.exs +++ b/test/pleroma/web/plugs/ensure_host_matches_plug_test.exs @@ -8,7 +8,6 @@ defmodule Pleroma.Web.Plugs.EnsureHostMatchesPlugTest do alias Pleroma.Web.Endpoint alias Pleroma.Web.Plugs.EnsureHostMatchesPlug - import Mock import Plug.Conn import Tesla.Mock @@ -105,33 +104,7 @@ defmodule Pleroma.Web.Plugs.EnsureHostMatchesPlugTest do assert Map.get(conn.assigns, :valid_host_header, nil) end - test "it works for Host header with port as 80", %{conn: conn} do - endpoint = URI.parse(Endpoint.url()) - - conn = - conn - |> set_host("#{endpoint.host}:80") - |> EnsureHostMatchesPlug.call(%{}) - - assert conn.halted == false - assert Map.get(conn.assigns, :valid_host_header, nil) - end - - test "it works for Host header with port as 443", %{conn: conn} do - with_mock Pleroma.Web.Endpoint, url: fn -> "https://localhost:4001" end do - endpoint = URI.parse(Endpoint.url()) - - conn = - conn - |> set_host("#{endpoint.host}:443") - |> EnsureHostMatchesPlug.call(%{}) - - assert conn.halted == false - assert Map.get(conn.assigns, :valid_host_header, nil) - end - end - - test "it works for Host header with port as same as Endpoint (no reverse proxy config)", %{ + test "it works for Host header with port same as Endpoint", %{ conn: conn } do endpoint = URI.parse(Endpoint.url()) From 6c2d8209c9b3f86db6b89d48421b7a0d70a33d00 Mon Sep 17 00:00:00 2001 From: Phantasm Date: Wed, 13 May 2026 00:31:50 +0200 Subject: [PATCH 17/38] SignatureRetryWorker: require validated host header --- lib/pleroma/workers/signature_retry_worker.ex | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/lib/pleroma/workers/signature_retry_worker.ex b/lib/pleroma/workers/signature_retry_worker.ex index 2c4c097dd..28958faff 100644 --- a/lib/pleroma/workers/signature_retry_worker.ex +++ b/lib/pleroma/workers/signature_retry_worker.ex @@ -8,6 +8,7 @@ defmodule Pleroma.Workers.SignatureRetryWorker do alias Pleroma.User alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.Federator + alias Pleroma.Web.Plugs.EnsureHostMatchesPlug alias Pleroma.Web.Plugs.MappedSignatureToIdentityPlug require Logger @@ -48,6 +49,7 @@ defmodule Pleroma.Workers.SignatureRetryWorker do {:ok, _public_key} <- Signature.refetch_public_key(conn_data), {:signature, true} <- {:signature, validate_signature(conn_data)}, {:same_actor, true} <- {:same_actor, validate_same_actor(conn_data)}, + {:host_header, true} <- {:host_header, validate_host_header(conn_data)}, {:ok, res} <- Federator.perform(:incoming_ap_doc, params) do unless Instances.reachable?(params["actor"]) do domain = URI.parse(params["actor"]).host @@ -103,6 +105,16 @@ defmodule Pleroma.Workers.SignatureRetryWorker do end end + defp validate_host_header(conn_data) do + case EnsureHostMatchesPlug.call(conn_data, []) do + %Plug.Conn{assigns: %{valid_signature: true, valid_host_header: true}} -> + true + + _ -> + false + end + end + defp validate_same_actor(conn_data) do case MappedSignatureToIdentityPlug.call(conn_data, []) do %Plug.Conn{assigns: %{valid_signature: true}} -> @@ -170,6 +182,10 @@ defmodule Pleroma.Workers.SignatureRetryWorker do {:same_actor, false} -> {:cancel, :actor_signature_mismatch} + # Host header from request not for us + {:host_header, false} -> + {:cancel, :host_header_mismatch} + # Origin / URL validation failed somewhere possibly due to spoofing {:error, :origin_containment_failed} -> {:cancel, :origin_containment_failed} @@ -234,6 +250,7 @@ defmodule Pleroma.Workers.SignatureRetryWorker do defp log_signature_retry_rejection({:cancel, reason}, context) when reason in [ :actor_signature_mismatch, + :host_header_mismatch, :invalid_signature, :invalid_signature_retry_metadata, :missing_signature_retry_metadata, From 95b15190dedab255c102db023af2d90eacf3a259 Mon Sep 17 00:00:00 2001 From: Phantasm Date: Wed, 13 May 2026 00:32:16 +0200 Subject: [PATCH 18/38] ActivityPubController: require validated host header --- lib/pleroma/web/activity_pub/activity_pub_controller.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex index 2bfff6968..415aa4f68 100644 --- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex +++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex @@ -303,7 +303,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do end end - def inbox(%{assigns: %{valid_signature: true}} = conn, %{"nickname" => nickname} = params) do + def inbox(%{assigns: %{valid_signature: true, valid_host_header: true}} = conn, %{"nickname" => nickname} = params) do with {:recipient_exists, %User{} = recipient} <- {:recipient_exists, User.get_cached_by_nickname(nickname)}, {:sender_exists, {:ok, %User{} = actor}} <- @@ -342,7 +342,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do end end - def inbox(%{assigns: %{valid_signature: true}} = conn, params) do + def inbox(%{assigns: %{valid_signature: true, valid_host_header: true}} = conn, params) do Federator.incoming_ap_doc(params) json(conn, "ok") end From c19bdf38143fcbe3a91a3b6053acefb8925773ac Mon Sep 17 00:00:00 2001 From: Phantasm Date: Wed, 13 May 2026 00:33:09 +0200 Subject: [PATCH 19/38] SignatureRetryWorker: add mismatched host test, fix tests --- .../workers/signature_retry_worker_test.exs | 66 ++++++++++++++++++- 1 file changed, 63 insertions(+), 3 deletions(-) diff --git a/test/pleroma/workers/signature_retry_worker_test.exs b/test/pleroma/workers/signature_retry_worker_test.exs index 94dd5f6c1..7f1351f4a 100644 --- a/test/pleroma/workers/signature_retry_worker_test.exs +++ b/test/pleroma/workers/signature_retry_worker_test.exs @@ -16,12 +16,13 @@ defmodule Pleroma.Workers.SignatureRetryWorkerTest do alias Pleroma.Signature alias Pleroma.User alias Pleroma.Web.ActivityPub.UserView + alias Pleroma.Web.Endpoint alias Pleroma.Web.Federator alias Pleroma.Workers.SignatureRetryWorker defp signature_headers_for(%User{} = signer) do [ - {"host", "local.test"}, + {"host", "#{URI.parse(Endpoint.url()).host}"}, {"date", "Thu, 25 Jul 2024 13:33:31 GMT"}, {"digest", "SHA-256=fake-digest"}, {"content-type", "application/activity+json"}, @@ -245,6 +246,65 @@ defmodule Pleroma.Workers.SignatureRetryWorkerTest do refute Activity.get_by_ap_id(create["id"]) end + test "cancels when the Host header does not match Endpoint" do + alice = insert(:user, local: false, ap_id: "https://one.com/users/alice") + + create = %{ + "type" => "Create", + "actor" => alice.ap_id, + "id" => "https://one.com/activities/invalid-signature-create", + "to" => ["https://www.w3.org/ns/activitystreams#Public"], + "cc" => [], + "object" => %{ + "type" => "Note", + "id" => "https://one.com/objects/invalid-signature-note", + "actor" => alice.ap_id, + "attributedTo" => alice.ap_id, + "content" => "forged post", + "published" => "2024-07-25T13:33:31Z", + "to" => ["https://www.w3.org/ns/activitystreams#Public"], + "cc" => [] + } + } + + expect_signature_from(alice) + + headers = + [ + {"host", "invalid.example.com"}, + {"date", "Thu, 25 Jul 2024 13:33:31 GMT"}, + {"digest", "SHA-256=fake-digest"}, + {"content-type", "application/activity+json"}, + { + "signature", + "keyId=\"#{alice.ap_id}#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) host date digest content-type\",signature=\"fake-signature\"" + } + ] + + assert {:ok, oban_job} = Federator.incoming_failed_signature_ap_doc(%{ + method: "POST", + req_headers: headers, + request_path: "/inbox", + params: create, + query_string: "" + }) + + log = + capture_log([level: :warning], fn -> + assert {:cancel, :host_header_mismatch} = SignatureRetryWorker.perform(oban_job) + end) + + assert log =~ "Failed-signature inbox retry rejected" + assert log =~ "reason=:host_header_mismatch" + assert log =~ "payload_actor=\"https://one.com/users/alice\"" + assert log =~ "signature_actor=\"https://one.com/users/alice\"" + assert log =~ "activity_id=\"https://one.com/activities/invalid-signature-create\"" + assert log =~ "type=\"Create\"" + assert log =~ "request_path=\"/inbox\"" + + refute Activity.get_by_ap_id(create["id"]) + end + test "processes the activity after refetching a valid matching signature" do alice = insert(:user, local: false, ap_id: "https://one.com/users/alice") @@ -309,11 +369,11 @@ defmodule Pleroma.Workers.SignatureRetryWorkerTest do "content-type" => "application/activity+json", date: date, digest: digest, - host: "local.test" + host: "#{URI.parse(Endpoint.url()).host}" }) req_headers = [ - ["host", "local.test"], + ["host", "#{URI.parse(Endpoint.url()).host}"], ["date", date], ["digest", digest], ["content-type", "application/activity+json"], From 95eef879d76df69e4d113f79afa9fbd2188aa5b0 Mon Sep 17 00:00:00 2001 From: Phantasm Date: Wed, 13 May 2026 00:40:53 +0200 Subject: [PATCH 20/38] ActivityPubController: add mismatched host test --- .../activity_pub_controller_test.exs | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs index 3988c3912..64f79dab5 100644 --- a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs +++ b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs @@ -950,6 +950,49 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do refute Activity.get_by_ap_id(data["id"]) end + test "does not process post with Host header not for us", %{conn: conn} do + alice = insert(:user, local: false, ap_id: "https://one.com/users/alice") + object_id = "https://one.com/objects/inbox-forged-note" + + data = %{ + "type" => "Create", + "actor" => alice.ap_id, + "id" => "https://one.com/activities/inbox-forged-create", + "context" => "https://one.com/contexts/inbox-forged-create", + "to" => ["https://www.w3.org/ns/activitystreams#Public"], + "cc" => [], + "object" => %{ + "type" => "Note", + "id" => object_id, + "actor" => alice.ap_id, + "attributedTo" => alice.ap_id, + "context" => "https://one.com/contexts/inbox-forged-create", + "content" => "forged post", + "published" => "2024-07-25T13:33:31Z", + "to" => ["https://www.w3.org/ns/activitystreams#Public"], + "cc" => [] + } + } + + expect_signature_retry_from(alice) + + conn = %{conn | req_headers: [{"host", "invalid.example.com"}]} + + conn = + conn + |> assign(:valid_signature, false) + |> put_req_header("content-type", "application/activity+json") + |> put_req_header("signature", "keyId=\"https://one.com/users/alice#main-key\"") + |> post("/inbox", data) + + assert "Host header does not match this instance" == conn.resp_body + assert 400 == conn.status + assert true == conn.halted + + refute Activity.get_by_ap_id(data["id"]) + refute Object.get_by_ap_id(object_id) + end + test "accept follow activity", %{conn: conn} do clear_config([:instance, :federating], true) relay = Relay.get_actor() From 2b3ac2d7fee91cdcbe53ca53760559def250b7d7 Mon Sep 17 00:00:00 2001 From: Phantasm Date: Wed, 13 May 2026 00:44:33 +0200 Subject: [PATCH 21/38] lint --- .../web/activity_pub/activity_pub_controller.ex | 7 ++++++- .../workers/signature_retry_worker_test.exs | 15 ++++++++------- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex index 415aa4f68..3d1875599 100644 --- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex +++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex @@ -303,7 +303,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do end end - def inbox(%{assigns: %{valid_signature: true, valid_host_header: true}} = conn, %{"nickname" => nickname} = params) do + def inbox( + %{ + assigns: %{valid_signature: true, valid_host_header: true} + } = conn, + %{"nickname" => nickname} = params + ) do with {:recipient_exists, %User{} = recipient} <- {:recipient_exists, User.get_cached_by_nickname(nickname)}, {:sender_exists, {:ok, %User{} = actor}} <- diff --git a/test/pleroma/workers/signature_retry_worker_test.exs b/test/pleroma/workers/signature_retry_worker_test.exs index 7f1351f4a..3806ecac9 100644 --- a/test/pleroma/workers/signature_retry_worker_test.exs +++ b/test/pleroma/workers/signature_retry_worker_test.exs @@ -281,13 +281,14 @@ defmodule Pleroma.Workers.SignatureRetryWorkerTest do } ] - assert {:ok, oban_job} = Federator.incoming_failed_signature_ap_doc(%{ - method: "POST", - req_headers: headers, - request_path: "/inbox", - params: create, - query_string: "" - }) + assert {:ok, oban_job} = + Federator.incoming_failed_signature_ap_doc(%{ + method: "POST", + req_headers: headers, + request_path: "/inbox", + params: create, + query_string: "" + }) log = capture_log([level: :warning], fn -> From 4d3aea1fce1a3d9b268aad2de8cae75b625ff5d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?nicole=20miko=C5=82ajczyk?= Date: Wed, 13 May 2026 00:55:47 +0200 Subject: [PATCH 22/38] Handle reports with just actor ap id as the object MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: nicole mikołajczyk --- changelog.d/iceshrimpnet-reports-fix.fix | 1 + .../web/activity_pub/transmogrifier.ex | 1 + .../web/activity_pub/transmogrifier_test.exs | 20 +++++++++++++++++++ 3 files changed, 22 insertions(+) create mode 100644 changelog.d/iceshrimpnet-reports-fix.fix diff --git a/changelog.d/iceshrimpnet-reports-fix.fix b/changelog.d/iceshrimpnet-reports-fix.fix new file mode 100644 index 000000000..7487ae47e --- /dev/null +++ b/changelog.d/iceshrimpnet-reports-fix.fix @@ -0,0 +1 @@ +Handle reports with just actor ap id as the object diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 4421da26c..eba588cf3 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -444,6 +444,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do ) do with context <- data["context"] || Utils.generate_context_id(), content <- data["content"] || "", + objects <- List.wrap(objects), %User{} = actor <- User.get_cached_by_ap_id(actor), # Reduce the object list to find the reported user. %User{} = account <- get_reported(objects), diff --git a/test/pleroma/web/activity_pub/transmogrifier_test.exs b/test/pleroma/web/activity_pub/transmogrifier_test.exs index c1e01557d..2e3da8598 100644 --- a/test/pleroma/web/activity_pub/transmogrifier_test.exs +++ b/test/pleroma/web/activity_pub/transmogrifier_test.exs @@ -86,6 +86,26 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do assert activity.data["cc"] == [user.ap_id] end + test "it accepts Flag activities with just actor id as object" do + user = insert(:user) + other_user = insert(:user) + + message = %{ + "@context" => "https://www.w3.org/ns/activitystreams", + "cc" => [user.ap_id], + "object" => user.ap_id, + "type" => "Flag", + "content" => "blocked AND reported!!!", + "actor" => other_user.ap_id + } + + assert {:ok, activity} = Transmogrifier.handle_incoming(message) + + assert activity.data["content"] == "blocked AND reported!!!" + assert activity.data["actor"] == other_user.ap_id + assert activity.data["cc"] == [user.ap_id] + end + test "it accepts Move activities" do old_user = insert(:user) new_user = insert(:user) From 4810d2536e2fd366364c1ec39d88b21265166f88 Mon Sep 17 00:00:00 2001 From: Phantasm Date: Wed, 13 May 2026 12:10:10 +0200 Subject: [PATCH 23/38] ActivityPubController: Use valid signatures in Host header test --- .../activity_pub/activity_pub_controller_test.exs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs index 64f79dab5..68e39b3a0 100644 --- a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs +++ b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs @@ -974,16 +974,17 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do } } - expect_signature_retry_from(alice) - - conn = %{conn | req_headers: [{"host", "invalid.example.com"}]} - + # Plug will complain when replacing raw host header with put_req_header. + # The Plug way is updating conn.host, but that isn't the raw header + # and that isn't used in the EnsureHostMatchesPlug, because it doesn't include the port. conn = conn - |> assign(:valid_signature, false) + |> assign_valid_signature_for_actor(alice) + |> delete_req_header("host") |> put_req_header("content-type", "application/activity+json") - |> put_req_header("signature", "keyId=\"https://one.com/users/alice#main-key\"") - |> post("/inbox", data) + + conn = %{conn | req_headers: conn.req_headers ++ [{"host", "invalid.example.com"}]} + conn = post(conn, "/inbox", data) assert "Host header does not match this instance" == conn.resp_body assert 400 == conn.status From 9e16332d9d208abc4d3992fec27b317c932f7e07 Mon Sep 17 00:00:00 2001 From: Lain Soykaf Date: Wed, 13 May 2026 18:39:40 +0400 Subject: [PATCH 24/38] Update majic to 1.2.0 --- changelog.d/majic-1.2.0.change | 1 + mix.exs | 4 +++- mix.lock | 2 +- test/pleroma/dependency_version_test.exs | 16 ++++++++++++++++ 4 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 changelog.d/majic-1.2.0.change create mode 100644 test/pleroma/dependency_version_test.exs diff --git a/changelog.d/majic-1.2.0.change b/changelog.d/majic-1.2.0.change new file mode 100644 index 000000000..bc8d5eb3c --- /dev/null +++ b/changelog.d/majic-1.2.0.change @@ -0,0 +1 @@ +Update majic to 1.2.0. diff --git a/mix.exs b/mix.exs index 9c10cacc0..872929c2c 100644 --- a/mix.exs +++ b/mix.exs @@ -191,7 +191,9 @@ defmodule Pleroma.Mixfile do git: "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git", ref: "e7b7cc34cc16b383461b966484c297e4ec9aeef6"}, {:restarter, path: "./restarter"}, - {:majic, "~> 1.1"}, + {:majic, "1.2.0", + git: "https://git.pleroma.social/pleroma-elixir-libraries/majic.git", + ref: "1f2df25cccdd9548fd59dbfa402c996268ae99ec"}, {:open_api_spex, "~> 3.22"}, {:ecto_psql_extras, "~> 0.8"}, {:vix, "~> 0.36"}, diff --git a/mix.lock b/mix.lock index da0c1c60c..780dea4c0 100644 --- a/mix.lock +++ b/mix.lock @@ -73,7 +73,7 @@ "linkify": {:hex, :linkify, "0.5.3", "5f8143d8f61f5ff08d3aeeff47ef6509492b4948d8f08007fbf66e4d2246a7f2", [:mix], [], "hexpm", "3ef35a1377d47c25506e07c1c005ea9d38d700699d92ee92825f024434258177"}, "logger_backends": {:hex, :logger_backends, "1.0.0", "09c4fad6202e08cb0fbd37f328282f16539aca380f512523ce9472b28edc6bdf", [:mix], [], "hexpm", "1faceb3e7ec3ef66a8f5746c5afd020e63996df6fd4eb8cdb789e5665ae6c9ce"}, "mail": {:hex, :mail, "0.3.1", "cb0a14e4ed8904e4e5a08214e686ccf6f9099346885db17d8c309381f865cc5c", [:mix], [], "hexpm", "1db701e89865c1d5fa296b2b57b1cd587587cca8d8a1a22892b35ef5a8e352a6"}, - "majic": {:hex, :majic, "1.1.1", "16092a3a3376cc5e13d207e82ec06e05a5561170465e50cc11cc4df8a5747938", [:make, :mix], [{:elixir_make, "~> 0.8.4", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "7fbb0372f0447b3f777056177d6ab3f009742e68474f850521ff56b84bd85b96"}, + "majic": {:git, "https://git.pleroma.social/pleroma-elixir-libraries/majic.git", "1f2df25cccdd9548fd59dbfa402c996268ae99ec", [ref: "1f2df25cccdd9548fd59dbfa402c996268ae99ec"]}, "makeup": {:hex, :makeup, "1.0.5", "d5a830bc42c9800ce07dd97fa94669dfb93d3bf5fcf6ea7a0c67b2e0e4a7f26c", [:mix], [{:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cfa158c02d3f5c0c665d0af11512fed3fba0144cf1aadee0f2ce17747fba2ca9"}, "makeup_elixir": {:hex, :makeup_elixir, "0.14.1", "4f0e96847c63c17841d42c08107405a005a2680eb9c7ccadfd757bd31dabccfb", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f2438b1a80eaec9ede832b5c41cd4f373b38fd7aa33e3b22d9db79e640cbde11"}, "makeup_erlang": {:hex, :makeup_erlang, "1.0.2", "03e1804074b3aa64d5fad7aa64601ed0fb395337b982d9bcf04029d68d51b6a7", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "af33ff7ef368d5893e4a267933e7744e46ce3cf1f61e2dccf53a111ed3aa3727"}, diff --git a/test/pleroma/dependency_version_test.exs b/test/pleroma/dependency_version_test.exs new file mode 100644 index 000000000..0c9c1e939 --- /dev/null +++ b/test/pleroma/dependency_version_test.exs @@ -0,0 +1,16 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.DependencyVersionTest do + use ExUnit.Case, async: true + + test "uses majic 1.2" do + majic_version = + :majic + |> Application.spec(:vsn) + |> to_string() + + assert Version.match?(majic_version, "~> 1.2") + end +end From d8e3ea69b17189aad1a3d3e60587ccf16bd3154f Mon Sep 17 00:00:00 2001 From: Lain Soykaf Date: Wed, 13 May 2026 19:00:16 +0400 Subject: [PATCH 25/38] Use Hex release for majic 1.2.0 --- mix.exs | 4 +--- mix.lock | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/mix.exs b/mix.exs index 872929c2c..2cf5137a5 100644 --- a/mix.exs +++ b/mix.exs @@ -191,9 +191,7 @@ defmodule Pleroma.Mixfile do git: "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git", ref: "e7b7cc34cc16b383461b966484c297e4ec9aeef6"}, {:restarter, path: "./restarter"}, - {:majic, "1.2.0", - git: "https://git.pleroma.social/pleroma-elixir-libraries/majic.git", - ref: "1f2df25cccdd9548fd59dbfa402c996268ae99ec"}, + {:majic, "~> 1.2"}, {:open_api_spex, "~> 3.22"}, {:ecto_psql_extras, "~> 0.8"}, {:vix, "~> 0.36"}, diff --git a/mix.lock b/mix.lock index 780dea4c0..9214478c7 100644 --- a/mix.lock +++ b/mix.lock @@ -73,7 +73,7 @@ "linkify": {:hex, :linkify, "0.5.3", "5f8143d8f61f5ff08d3aeeff47ef6509492b4948d8f08007fbf66e4d2246a7f2", [:mix], [], "hexpm", "3ef35a1377d47c25506e07c1c005ea9d38d700699d92ee92825f024434258177"}, "logger_backends": {:hex, :logger_backends, "1.0.0", "09c4fad6202e08cb0fbd37f328282f16539aca380f512523ce9472b28edc6bdf", [:mix], [], "hexpm", "1faceb3e7ec3ef66a8f5746c5afd020e63996df6fd4eb8cdb789e5665ae6c9ce"}, "mail": {:hex, :mail, "0.3.1", "cb0a14e4ed8904e4e5a08214e686ccf6f9099346885db17d8c309381f865cc5c", [:mix], [], "hexpm", "1db701e89865c1d5fa296b2b57b1cd587587cca8d8a1a22892b35ef5a8e352a6"}, - "majic": {:git, "https://git.pleroma.social/pleroma-elixir-libraries/majic.git", "1f2df25cccdd9548fd59dbfa402c996268ae99ec", [ref: "1f2df25cccdd9548fd59dbfa402c996268ae99ec"]}, + "majic": {:hex, :majic, "1.2.0", "414b69c1460ece692fa892a0ed6669b8db6a44c42bb3071cb723854f22e7bd78", [:make, :mix], [{:elixir_make, "~> 0.8.4", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "0bd0c65f8e4dcc595757d957577f6151b59246da9614ba87debfdc641ccc0514"}, "makeup": {:hex, :makeup, "1.0.5", "d5a830bc42c9800ce07dd97fa94669dfb93d3bf5fcf6ea7a0c67b2e0e4a7f26c", [:mix], [{:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cfa158c02d3f5c0c665d0af11512fed3fba0144cf1aadee0f2ce17747fba2ca9"}, "makeup_elixir": {:hex, :makeup_elixir, "0.14.1", "4f0e96847c63c17841d42c08107405a005a2680eb9c7ccadfd757bd31dabccfb", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f2438b1a80eaec9ede832b5c41cd4f373b38fd7aa33e3b22d9db79e640cbde11"}, "makeup_erlang": {:hex, :makeup_erlang, "1.0.2", "03e1804074b3aa64d5fad7aa64601ed0fb395337b982d9bcf04029d68d51b6a7", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "af33ff7ef368d5893e4a267933e7744e46ce3cf1f61e2dccf53a111ed3aa3727"}, From d0c2d04356bbed3b2be2b9873a6c207f54021285 Mon Sep 17 00:00:00 2001 From: Lain Soykaf Date: Wed, 13 May 2026 19:10:15 +0400 Subject: [PATCH 26/38] Use Hex release for oban_plugins_lazarus --- changelog.d/oban-plugins-lazarus-hex.change | 1 + mix.exs | 4 +--- mix.lock | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) create mode 100644 changelog.d/oban-plugins-lazarus-hex.change diff --git a/changelog.d/oban-plugins-lazarus-hex.change b/changelog.d/oban-plugins-lazarus-hex.change new file mode 100644 index 000000000..9259df0f5 --- /dev/null +++ b/changelog.d/oban-plugins-lazarus-hex.change @@ -0,0 +1 @@ +Use the Hex package for oban_plugins_lazarus. diff --git a/mix.exs b/mix.exs index 2cf5137a5..946bf0ab0 100644 --- a/mix.exs +++ b/mix.exs @@ -137,9 +137,7 @@ defmodule Pleroma.Mixfile do {:tzdata, "~> 1.0.5"}, {:plug_cowboy, "~> 2.7"}, {:oban, "~> 2.19.4"}, - {:oban_plugins_lazarus, - git: "https://git.pleroma.social/pleroma/elixir-libraries/oban_plugins_lazarus.git", - ref: "e49fc355baaf0e435208bf5f534d31e26e897711"}, + {:oban_plugins_lazarus, "~> 0.1.1"}, {:oban_web, "~> 2.11"}, {:gettext, "~> 0.24"}, {:bcrypt_elixir, "~> 2.3"}, diff --git a/mix.lock b/mix.lock index 9214478c7..f33c22482 100644 --- a/mix.lock +++ b/mix.lock @@ -96,7 +96,7 @@ "oban": {:hex, :oban, "2.19.4", "045adb10db1161dceb75c254782f97cdc6596e7044af456a59decb6d06da73c1", [:mix], [{:ecto_sql, "~> 3.10", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:ecto_sqlite3, "~> 0.9", [hex: :ecto_sqlite3, repo: "hexpm", optional: true]}, {:igniter, "~> 0.5", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5fcc6219e6464525b808d97add17896e724131f498444a292071bf8991c99f97"}, "oban_live_dashboard": {:hex, :oban_live_dashboard, "0.1.1", "8aa4ceaf381c818f7d5c8185cc59942b8ac82ef0cf559881aacf8d3f8ac7bdd3", [:mix], [{:oban, "~> 2.15", [hex: :oban, repo: "hexpm", optional: false]}, {:phoenix_live_dashboard, "~> 0.7", [hex: :phoenix_live_dashboard, repo: "hexpm", optional: false]}], "hexpm", "16dc4ce9c9a95aa2e655e35ed4e675652994a8def61731a18af85e230e1caa63"}, "oban_met": {:hex, :oban_met, "1.0.5", "bb633ab06448dab2ef9194f6688d33b3d07fc3f2ad793a1a08f4dfbb2cc9fe50", [:mix], [{:oban, "~> 2.19", [hex: :oban, repo: "hexpm", optional: false]}], "hexpm", "64664d50805bbfd3903aeada1f3c39634652a87844797ee400b0bcc95a28f5ea"}, - "oban_plugins_lazarus": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/oban_plugins_lazarus.git", "e49fc355baaf0e435208bf5f534d31e26e897711", [ref: "e49fc355baaf0e435208bf5f534d31e26e897711"]}, + "oban_plugins_lazarus": {:hex, :oban_plugins_lazarus, "0.1.1", "a5141d569e5b9f3bec8f4a958231d2c538af097d3c1ad06274f096fc06956821", [:mix], [{:oban, "< 3.0.0", [hex: :oban, repo: "hexpm", optional: false]}], "hexpm", "7e9c51bc44d33f71c0a52cf0cd5c8d4c70380ede065c1042013f5123f2fc9729"}, "oban_web": {:hex, :oban_web, "2.11.6", "53933cb4253c4d9f1098ee311c06f07935259f0e564dcf2d66bae4cc98e317fe", [:mix], [{:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: false]}, {:oban, "~> 2.19", [hex: :oban, repo: "hexpm", optional: false]}, {:oban_met, "~> 1.0", [hex: :oban_met, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.7", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}], "hexpm", "576d94b705688c313694c2c114ca21aa0f8f2ad1b9ca45c052c5ba316d3e8d10"}, "octo_fetch": {:hex, :octo_fetch, "0.4.0", "074b5ecbc08be10b05b27e9db08bc20a3060142769436242702931c418695b19", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "cf8be6f40cd519d7000bb4e84adcf661c32e59369ca2827c4e20042eda7a7fc6"}, "open_api_spex": {:hex, :open_api_spex, "3.22.0", "fbf90dc82681dc042a4ee79853c8e989efbba73d9e87439085daf849bbf8bc20", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 3.0 or ~> 4.0 or ~> 5.0 or ~> 6.0", [hex: :poison, repo: "hexpm", optional: true]}, {:ymlr, "~> 2.0 or ~> 3.0 or ~> 4.0 or ~> 5.0", [hex: :ymlr, repo: "hexpm", optional: true]}], "hexpm", "dd751ddbdd709bb4a5313e9a24530da6e66594773c7242a0c2592cbd9f589063"}, From c92d23323346e0538fc04e79e9a110fe6bb9198e Mon Sep 17 00:00:00 2001 From: Lain Soykaf Date: Wed, 13 May 2026 20:04:12 +0400 Subject: [PATCH 27/38] Use upstream remote_ip package --- changelog.d/remote-ip-hex.change | 1 + config/config.exs | 1 + config/description.exs | 8 ++++- lib/pleroma/web/plugs/remote_ip.ex | 42 ++++++++++++++++++++--- mix.exs | 5 ++- mix.lock | 4 +-- test/pleroma/web/plugs/remote_ip_test.exs | 34 ++++++++++++++++++ 7 files changed, 85 insertions(+), 10 deletions(-) create mode 100644 changelog.d/remote-ip-hex.change diff --git a/changelog.d/remote-ip-hex.change b/changelog.d/remote-ip-hex.change new file mode 100644 index 000000000..44f6bc1bf --- /dev/null +++ b/changelog.d/remote-ip-hex.change @@ -0,0 +1 @@ +Use upstream Hex releases for the `remote_ip` dependency and expose client IP ranges for remote IP resolution. diff --git a/config/config.exs b/config/config.exs index 2d38e3ebe..3f880af53 100644 --- a/config/config.exs +++ b/config/config.exs @@ -738,6 +738,7 @@ config :pleroma, Pleroma.Workers.PurgeExpiredActivity, enabled: true, min_lifeti config :pleroma, Pleroma.Web.Plugs.RemoteIp, enabled: true, headers: ["x-forwarded-for"], + clients: [], proxies: [], reserved: [ "127.0.0.0/8", diff --git a/config/description.exs b/config/description.exs index 6e4348907..7c377588c 100644 --- a/config/description.exs +++ b/config/description.exs @@ -2910,7 +2910,7 @@ config :pleroma, :config_description, [ key: Pleroma.Web.Plugs.RemoteIp, type: :group, description: """ - `Pleroma.Web.Plugs.RemoteIp` is a shim to call [`RemoteIp`](https://git.pleroma.social/pleroma/remote_ip) but with runtime configuration. + `Pleroma.Web.Plugs.RemoteIp` is a shim to call [`RemoteIp`](https://hex.pm/packages/remote_ip) but with runtime configuration. **If your instance is not behind at least one reverse proxy, you should not enable this plug.** """, children: [ @@ -2926,6 +2926,12 @@ config :pleroma, :config_description, [ A list of strings naming the HTTP headers to use when deriving the true client IP. Default: `["x-forwarded-for"]`. """ }, + %{ + key: :clients, + type: {:list, :string}, + description: + "A list of client IPs or subnets in CIDR notation. These will not be treated as proxies or reserved ranges. Defaults to `[]`. IPv4 entries without a bitmask will be assumed to be /32 and IPv6 /128." + }, %{ key: :proxies, type: {:list, :string}, diff --git a/lib/pleroma/web/plugs/remote_ip.ex b/lib/pleroma/web/plugs/remote_ip.ex index 3a4bffb50..4c2ea099d 100644 --- a/lib/pleroma/web/plugs/remote_ip.ex +++ b/lib/pleroma/web/plugs/remote_ip.ex @@ -4,7 +4,7 @@ defmodule Pleroma.Web.Plugs.RemoteIp do @moduledoc """ - This is a shim to call [`RemoteIp`](https://git.pleroma.social/pleroma/remote_ip) but with runtime configuration. + This is a shim to call [`RemoteIp`](https://hex.pm/packages/remote_ip) but with runtime configuration. """ alias Pleroma.Config @@ -17,15 +17,29 @@ defmodule Pleroma.Web.Plugs.RemoteIp do def call(%{remote_ip: original_remote_ip} = conn, _) do if Config.get([__MODULE__, :enabled]) do - %{remote_ip: new_remote_ip} = conn = RemoteIp.call(conn, remote_ip_opts()) + new_remote_ip = remote_ip(conn) || original_remote_ip + + conn = %{conn | remote_ip: new_remote_ip} assign(conn, :remote_ip_found, original_remote_ip != new_remote_ip) else conn end end + defp remote_ip(conn) do + opts = remote_ip_opts() + + # Do not use RemoteIp.from/2 here: upstream remote_ip always applies its + # built-in reserved ranges. Pleroma keeps :reserved configurable, so reuse + # only the header parsing and apply Pleroma's own block classification. + conn.req_headers + |> RemoteIp.Headers.take(opts[:headers]) + |> RemoteIp.Headers.parse() + |> Enum.reverse() + |> Enum.find(&client?(&1, opts)) + end + defp remote_ip_opts do - headers = Config.get([__MODULE__, :headers], []) |> MapSet.new() reserved = Config.get([__MODULE__, :reserved], []) proxies = @@ -33,6 +47,26 @@ defmodule Pleroma.Web.Plugs.RemoteIp do |> Enum.concat(reserved) |> Enum.map(&InetHelper.parse_cidr/1) - {headers, proxies} + clients = + Config.get([__MODULE__, :clients], []) + |> Enum.map(&InetHelper.parse_cidr/1) + + [ + headers: Config.get([__MODULE__, :headers], []), + clients: clients, + proxies: proxies + ] + end + + defp client?(ip, opts) do + client_ip?(ip, opts[:clients]) || !proxy_ip?(ip, opts[:proxies]) + end + + defp client_ip?(ip, clients) do + Enum.any?(clients, &InetCidr.contains?(&1, ip)) + end + + defp proxy_ip?(ip, proxies) do + Enum.any?(proxies, &InetCidr.contains?(&1, ip)) end end diff --git a/mix.exs b/mix.exs index 946bf0ab0..90586b2d3 100644 --- a/mix.exs +++ b/mix.exs @@ -182,9 +182,8 @@ defmodule Pleroma.Mixfile do {:plug_static_index_html, "~> 1.0.0"}, {:flake_id, "~> 0.1.0"}, {:concurrent_limiter, "~> 0.1.1"}, - {:remote_ip, - git: "https://git.pleroma.social/pleroma/remote_ip.git", - ref: "b647d0deecaa3acb140854fe4bda5b7e1dc6d1c8"}, + {:remote_ip, "~> 1.2.0"}, + {:inet_cidr, "~> 1.0"}, {:captcha, git: "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git", ref: "e7b7cc34cc16b383461b966484c297e4ec9aeef6"}, diff --git a/mix.lock b/mix.lock index f33c22482..04de2825e 100644 --- a/mix.lock +++ b/mix.lock @@ -65,7 +65,7 @@ "http_signatures": {:hex, :http_signatures, "0.1.3", "19f26aee35b4684e37efdce3ac4638605e6e41a04368186bd39d2b6138a60ea9", [:mix], [{:plug, "~> 1.18", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "20313a65516db88006f85b090f6f76cc5b04e9609b45943657e6781eb91174f4"}, "httpoison": {:hex, :httpoison, "1.8.2", "9eb9c63ae289296a544842ef816a85d881d4a31f518a0fec089aaa744beae290", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "2bb350d26972e30c96e2ca74a1aaf8293d61d0742ff17f01e0279fef11599921"}, "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, - "inet_cidr": {:hex, :inet_cidr, "1.0.8", "d26bb7bdbdf21ae401ead2092bf2bb4bf57fe44a62f5eaa5025280720ace8a40", [:mix], [], "hexpm", "d5b26da66603bb56c933c65214c72152f0de9a6ea53618b56d63302a68f6a90e"}, + "inet_cidr": {:hex, :inet_cidr, "1.0.9", "e0ef72a2942529da78c8e4147d53f2ef5f6f5293335c3637b0fdf83c012cc816", [:mix], [], "hexpm", "172da15ff7cf635b1feaf14f5818be28c811b37cc5fb7c5f7c01058c1c1066cc"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, "joken": {:hex, :joken, "2.6.2", "5daaf82259ca603af4f0b065475099ada1b2b849ff140ccd37f4b6828ca6892a", [:mix], [{:jose, "~> 1.11.10", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "5134b5b0a6e37494e46dbf9e4dad53808e5e787904b7c73972651b51cce3d72b"}, "jose": {:hex, :jose, "1.11.10", "a903f5227417bd2a08c8a00a0cbcc458118be84480955e8d251297a425723f83", [:mix, :rebar3], [], "hexpm", "0d6cd36ff8ba174db29148fc112b5842186b68a90ce9fc2b3ec3afe76593e614"}, @@ -132,7 +132,7 @@ "quic": {:hex, :quic, "0.10.2", "4b390507a85f65ce47808f3df1a864e0baf9adb7a1b4ea9f4dcd66fe9d0cb166", [:rebar3], [], "hexpm", "7c196a66973c877a59768a5687f0a0610ff11817254d0a4e45cc4e3a16b1d00b"}, "ranch": {:hex, :ranch, "2.2.0", "25528f82bc8d7c6152c57666ca99ec716510fe0925cb188172f41ce93117b1b0", [:make, :rebar3], [], "hexpm", "fa0b99a1780c80218a4197a59ea8d3bdae32fbff7e88527d7d8a4787eff4f8e7"}, "recon": {:hex, :recon, "2.5.6", "9052588e83bfedfd9b72e1034532aee2a5369d9d9343b61aeb7fbce761010741", [:mix, :rebar3], [], "hexpm", "96c6799792d735cc0f0fd0f86267e9d351e63339cbe03df9d162010cefc26bb0"}, - "remote_ip": {:git, "https://git.pleroma.social/pleroma/remote_ip.git", "b647d0deecaa3acb140854fe4bda5b7e1dc6d1c8", [ref: "b647d0deecaa3acb140854fe4bda5b7e1dc6d1c8"]}, + "remote_ip": {:hex, :remote_ip, "1.2.0", "fb078e12a44414f4cef5a75963c33008fe169b806572ccd17257c208a7bc760f", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "2ff91de19c48149ce19ed230a81d377186e4412552a597d6a5137373e5877cb7"}, "rustler": {:hex, :rustler, "0.30.0", "cefc49922132b072853fa9b0ca4dc2ffcb452f68fb73b779042b02d545e097fb", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:toml, "~> 0.6", [hex: :toml, repo: "hexpm", optional: false]}], "hexpm", "9ef1abb6a7dda35c47cfc649e6a5a61663af6cf842a55814a554a84607dee389"}, "sleeplocks": {:hex, :sleeplocks, "1.1.3", "96a86460cc33b435c7310dbd27ec82ca2c1f24ae38e34f8edde97f756503441a", [:rebar3], [], "hexpm", "d3b3958552e6eb16f463921e70ae7c767519ef8f5be46d7696cc1ed649421321"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, diff --git a/test/pleroma/web/plugs/remote_ip_test.exs b/test/pleroma/web/plugs/remote_ip_test.exs index 37b751370..19e786f8a 100644 --- a/test/pleroma/web/plugs/remote_ip_test.exs +++ b/test/pleroma/web/plugs/remote_ip_test.exs @@ -106,4 +106,38 @@ defmodule Pleroma.Web.Plugs.RemoteIpTest do assert conn.remote_ip == {1, 1, 1, 1} end + + test "reserved ranges are configurable" do + clear_config([RemoteIp, :reserved], []) + + conn = + conn(:get, "/") + |> put_req_header("x-forwarded-for", "1.1.1.1, 10.0.0.3") + |> RemoteIp.call(nil) + + assert conn.remote_ip == {10, 0, 0, 3} + end + + test "clients override reserved ranges" do + clear_config([RemoteIp, :clients], ["10.0.0.0/8"]) + + conn = + conn(:get, "/") + |> put_req_header("x-forwarded-for", "1.1.1.1, 10.0.0.3") + |> RemoteIp.call(nil) + + assert conn.remote_ip == {10, 0, 0, 3} + end + + test "clients override proxies" do + clear_config([RemoteIp, :clients], ["10.0.0.3"]) + clear_config([RemoteIp, :proxies], ["10.0.0.0/8"]) + + conn = + conn(:get, "/") + |> put_req_header("x-forwarded-for", "1.1.1.1, 10.0.0.3") + |> RemoteIp.call(nil) + + assert conn.remote_ip == {10, 0, 0, 3} + end end From 7ab9e2c7cecf5f53fb5d46037dd52260fe42d762 Mon Sep 17 00:00:00 2001 From: Lain Soykaf Date: Wed, 13 May 2026 23:05:25 +0400 Subject: [PATCH 28/38] Use pleroma_captcha Hex package --- changelog.d/captcha-package.fix | 1 + mix.exs | 4 +--- mix.lock | 2 +- test/pleroma/captcha_test.exs | 8 ++++++++ 4 files changed, 11 insertions(+), 4 deletions(-) create mode 100644 changelog.d/captcha-package.fix diff --git a/changelog.d/captcha-package.fix b/changelog.d/captcha-package.fix new file mode 100644 index 000000000..98c2f6525 --- /dev/null +++ b/changelog.d/captcha-package.fix @@ -0,0 +1 @@ +Switch native captcha to the published pleroma_captcha Hex package. diff --git a/mix.exs b/mix.exs index 946bf0ab0..17ca278be 100644 --- a/mix.exs +++ b/mix.exs @@ -185,9 +185,7 @@ defmodule Pleroma.Mixfile do {:remote_ip, git: "https://git.pleroma.social/pleroma/remote_ip.git", ref: "b647d0deecaa3acb140854fe4bda5b7e1dc6d1c8"}, - {:captcha, - git: "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git", - ref: "e7b7cc34cc16b383461b966484c297e4ec9aeef6"}, + {:captcha, "~> 1.0.3", hex: :pleroma_captcha}, {:restarter, path: "./restarter"}, {:majic, "~> 1.2"}, {:open_api_spex, "~> 3.22"}, diff --git a/mix.lock b/mix.lock index f33c22482..d20cc408d 100644 --- a/mix.lock +++ b/mix.lock @@ -10,7 +10,7 @@ "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "cachex": {:hex, :cachex, "3.6.0", "14a1bfbeee060dd9bec25a5b6f4e4691e3670ebda28c8ba2884b12fe30b36bf8", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:jumper, "~> 1.0", [hex: :jumper, repo: "hexpm", optional: false]}, {:sleeplocks, "~> 1.1", [hex: :sleeplocks, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm", "ebf24e373883bc8e0c8d894a63bbe102ae13d918f790121f5cfe6e485cc8e2e2"}, "calendar": {:hex, :calendar, "1.0.0", "f52073a708528482ec33d0a171954ca610fe2bd28f1e871f247dc7f1565fa807", [:mix], [{:tzdata, "~> 0.1.201603 or ~> 0.5.20 or ~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "990e9581920c82912a5ee50e62ff5ef96da6b15949a2ee4734f935fdef0f0a6f"}, - "captcha": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git", "e7b7cc34cc16b383461b966484c297e4ec9aeef6", [ref: "e7b7cc34cc16b383461b966484c297e4ec9aeef6"]}, + "captcha": {:hex, :pleroma_captcha, "1.0.3", "6cc1440e91a092653fe909f028cc4c5d83ea858b1439e3b9a85e446382e2b9a3", [:make, :mix], [], "hexpm", "477f62c1a845a9458c546658223295f958f98136acef89c05beb278b1b6f4a14"}, "castore": {:hex, :castore, "1.0.15", "8aa930c890fe18b6fe0a0cff27b27d0d4d231867897bd23ea772dee561f032a3", [:mix], [], "hexpm", "96ce4c69d7d5d7a0761420ef743e2f4096253931a3ba69e5ff8ef1844fe446d3"}, "cc_precompiler": {:hex, :cc_precompiler, "0.1.11", "8c844d0b9fb98a3edea067f94f616b3f6b29b959b6b3bf25fee94ffe34364768", [:mix], [{:elixir_make, "~> 0.7", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "3427232caf0835f94680e5bcf082408a70b48ad68a5f5c0b02a3bea9f3a075b9"}, "certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"}, diff --git a/test/pleroma/captcha_test.exs b/test/pleroma/captcha_test.exs index ec4c713f8..0f0a9bc87 100644 --- a/test/pleroma/captcha_test.exs +++ b/test/pleroma/captcha_test.exs @@ -50,6 +50,14 @@ defmodule Pleroma.CaptchaTest do end describe "Native" do + test "uses the published pleroma_captcha package" do + deps = Mix.Project.config() |> Keyword.fetch!(:deps) + assert {:captcha, "~> 1.0.3", opts} = Enum.find(deps, &(elem(&1, 0) == :captcha)) + assert opts[:hex] == :pleroma_captcha + refute Keyword.has_key?(opts, :compile) + refute Keyword.has_key?(opts, :git) + end + test "new and validate" do new = Native.new() From 9ae1249ccb821607a1babf23cf12eba3d633e6e9 Mon Sep 17 00:00:00 2001 From: Lain Soykaf Date: Wed, 13 May 2026 23:09:29 +0400 Subject: [PATCH 29/38] Remove captcha dependency shape test --- test/pleroma/captcha_test.exs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/test/pleroma/captcha_test.exs b/test/pleroma/captcha_test.exs index 0f0a9bc87..ec4c713f8 100644 --- a/test/pleroma/captcha_test.exs +++ b/test/pleroma/captcha_test.exs @@ -50,14 +50,6 @@ defmodule Pleroma.CaptchaTest do end describe "Native" do - test "uses the published pleroma_captcha package" do - deps = Mix.Project.config() |> Keyword.fetch!(:deps) - assert {:captcha, "~> 1.0.3", opts} = Enum.find(deps, &(elem(&1, 0) == :captcha)) - assert opts[:hex] == :pleroma_captcha - refute Keyword.has_key?(opts, :compile) - refute Keyword.has_key?(opts, :git) - end - test "new and validate" do new = Native.new() From 143f426e840de86e9a90a436f8446cc8534e165b Mon Sep 17 00:00:00 2001 From: Irrlicht Date: Wed, 9 Apr 2025 23:59:32 +0000 Subject: [PATCH 30/38] Translated using Weblate (French) Currently translated at 10.8% (106 of 974 strings) Translation: Pleroma/Pleroma Backend (domain config_descriptions) Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-backend-domain-config_descriptions/fr/ --- .../fr/LC_MESSAGES/config_descriptions.po | 116 ++++++++++-------- 1 file changed, 66 insertions(+), 50 deletions(-) diff --git a/priv/gettext/fr/LC_MESSAGES/config_descriptions.po b/priv/gettext/fr/LC_MESSAGES/config_descriptions.po index c24ab6751..56ea1bdf3 100644 --- a/priv/gettext/fr/LC_MESSAGES/config_descriptions.po +++ b/priv/gettext/fr/LC_MESSAGES/config_descriptions.po @@ -3,8 +3,8 @@ msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-07-22 02:09+0300\n" -"PO-Revision-Date: 2024-10-13 21:03+0000\n" -"Last-Translator: Codimp \n" +"PO-Revision-Date: 2025-04-11 01:04+0000\n" +"Last-Translator: Irrlicht \n" "Language-Team: French \n" "Language: fr\n" @@ -402,7 +402,7 @@ msgstr "" #, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:shout" msgid "Pleroma shout settings" -msgstr "" +msgstr "Paramètre de Pleroma shout" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format @@ -416,7 +416,7 @@ msgstr "" #, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:streamer" msgid "Settings for notifications streamer" -msgstr "" +msgstr "Paramètres des streamers de notifications" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format @@ -429,12 +429,14 @@ msgstr "Paramètres liés au schémas d'URI" msgctxt "config description at :pleroma-:web_cache_ttl" msgid "The expiration time for the web responses cache. Values should be in milliseconds or `nil` to disable expiration." msgstr "" +"Temps d'expiration pour le cache des réponses web. Les valeurs doivent être " +"en millisecondes ou `nil`pour désactiver l'expiration." #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:welcome" msgid "Welcome messages settings" -msgstr "" +msgstr "Paramètres de messages de bienvenue" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format @@ -458,133 +460,147 @@ msgstr "" #, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Captcha" msgid "Captcha-related settings" -msgstr "" +msgstr "Paramètres liés au Captcha" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Captcha.Kocaptcha" msgid "Kocaptcha is a very simple captcha service with a single API endpoint, the source code is here: https://github.com/koto-bank/kocaptcha. The default endpoint (https://captcha.kotobank.ch) is hosted by the developer." msgstr "" +"Kocaptcha est un service de captcha très simple avec une API n'utilisant " +"qu'une seule ressource, le code source est ici: https://github.com/koto-bank/" +"kocaptcha.\n" +"La ressource par défaut (https://captcha.kotobank.ch) est hébergée par son " +"développeur." #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Emails.Mailer" msgid "Mailer-related settings" -msgstr "" +msgstr "Paramètres liés à l'envoyeur d'email" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Emails.NewUsersDigestEmail" msgid "New users admin email digest" -msgstr "" +msgstr "Email à l'administrateur de résumé des nouveaux utilisateurs" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Emails.UserEmail" msgid "Email template settings" -msgstr "" +msgstr "Paramètres de template d'email" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Formatter" msgid "Configuration for Pleroma's link formatter which parses mentions, hashtags, and URLs." msgstr "" +"Configuration du formateur de lien Pleroma qui interprète les mentions, " +"hashtags et URLs." #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.ScheduledActivity" msgid "Scheduled activities settings" -msgstr "" +msgstr "Paramètres des activités planifiées" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Upload" msgid "Upload general settings" -msgstr "" +msgstr "Paramètres généraux d'upload" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Upload.Filter.AnonymizeFilename" msgid "Filter replaces the filename of the upload" -msgstr "" +msgstr "Filtre de remplacement du nom de fichier de l'upload" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Upload.Filter.Mogrify" msgid "Uploads mogrify filter settings" -msgstr "" +msgstr "Paramètres de filtre mogrify pour les uploads" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Uploaders.Local" msgid "Local uploader-related settings" -msgstr "" +msgstr "Paramètres liés aux uploads locaux" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Uploaders.S3" msgid "S3 uploader-related settings" -msgstr "" +msgstr "Paramètres liés à l'uploader S3" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.User.Backup" msgid "Account Backup" -msgstr "" +msgstr "Sauvegarde de Comptes" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Web.MediaProxy.Invalidation.Http" msgid "HTTP invalidate settings" -msgstr "" +msgstr "Paramètres d'invalidation HTTP" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Web.MediaProxy.Invalidation.Script" msgid "Invalidation script settings" -msgstr "" +msgstr "Paramètres du script d'invalidation" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Web.Metadata" msgid "Metadata-related settings" -msgstr "" +msgstr "Paramètres liés aux métadonnées" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Web.Plugs.RemoteIp" msgid "`Pleroma.Web.Plugs.RemoteIp` is a shim to call [`RemoteIp`](https://git.pleroma.social/pleroma/remote_ip) but with runtime configuration.\n**If your instance is not behind at least one reverse proxy, you should not enable this plug.**\n" msgstr "" +"`Pleroma.Web.Plugs.RemoteIp` est un shim pour invoquer " +"[`RemoteIp`](https://git.pleroma.social/pleroma/remote_ip) mais avec une " +"configuration au runtime.\n" +"**Si votre instance n'est pas derrière au moins un proxy inverse, vous ne " +"devriez pas activer ce plug.**\n" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Web.Preload" msgid "Preload-related settings" -msgstr "" +msgstr "Paramètres liés au préchargement" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Workers.PurgeExpiredActivity" msgid "Expired activities settings" -msgstr "" +msgstr "Paramètres des activités expirées" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config description at :prometheus-Pleroma.Web.Endpoint.MetricsExporter" msgid "Prometheus app metrics endpoint configuration" -msgstr "" +msgstr "Configuration des endpoints de métriques Prometheus" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config description at :web_push_encryption-:vapid_details" msgid "Web Push Notifications configuration. You can use the mix task mix web_push.gen.keypair to generate it." msgstr "" +"Configuration des notifications Web Push. Vous pouvez utiliser la tâche mix " +"web_push.gen.keypair pour le générer." #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config label at :ex_aws-:s3" msgid "S3" -msgstr "" +msgstr "S3" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format @@ -596,13 +612,13 @@ msgstr "" #, elixir-autogen, elixir-format msgctxt "config label at :logger-:ex_syslogger" msgid "ExSyslogger" -msgstr "" +msgstr "ExSyslogger" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:activitypub" msgid "ActivityPub" -msgstr "" +msgstr "ActivityPub" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format @@ -620,151 +636,151 @@ msgstr "" #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:connections_pool" msgid "Connections pool" -msgstr "" +msgstr "Pool de connexions" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:email_notifications" msgid "Email notifications" -msgstr "" +msgstr "Notifications email" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:emoji" msgid "Emoji" -msgstr "" +msgstr "Emoji" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:features" msgid "Features" -msgstr "" +msgstr "Fonctionnalités" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:feed" msgid "Feed" -msgstr "" +msgstr "Flux" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:frontend_configurations" msgid "Frontend configurations" -msgstr "" +msgstr "Configurations des frontends" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:frontends" msgid "Frontends" -msgstr "" +msgstr "Frontends" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:gopher" msgid "Gopher" -msgstr "" +msgstr "Gopher" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:hackney_pools" msgid "Hackney pools" -msgstr "" +msgstr "Bacs (pools) d'Hackney" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:http" msgid "HTTP" -msgstr "" +msgstr "HTTP" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:http_security" msgid "HTTP security" -msgstr "" +msgstr "Sécurité HTTP" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:instance" msgid "Instance" -msgstr "" +msgstr "Instance" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:instances_favicons" msgid "Instances favicons" -msgstr "" +msgstr "Favicons des instances" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:ldap" msgid "LDAP" -msgstr "" +msgstr "LDAP" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:majic_pool" msgid "Majic pool" -msgstr "" +msgstr "Bac (pool) de Majic" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:manifest" msgid "Manifest" -msgstr "" +msgstr "Manifeste" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:markup" msgid "Markup Settings" -msgstr "" +msgstr "Paramètres des Balises" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:media_preview_proxy" msgid "Media preview proxy" -msgstr "" +msgstr "Proxy de prévisualisation média" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:media_proxy" msgid "Media proxy" -msgstr "" +msgstr "Proxy média" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:modules" msgid "Modules" -msgstr "" +msgstr "Modules" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf" msgid "MRF" -msgstr "" +msgstr "MRF" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf_activity_expiration" msgid "MRF Activity Expiration Policy" -msgstr "" +msgstr "Politique MRF d'Expiration des Activités" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf_follow_bot" msgid "MRF FollowBot Policy" -msgstr "" +msgstr "Politique MRF FollowBot" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf_hashtag" msgid "MRF Hashtag" -msgstr "" +msgstr "Politique MRF hashtag" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf_hellthread" msgid "MRF Hellthread" -msgstr "" +msgstr "MRF Hellthread" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format From 7fff19cbe4f337deb2e4b58bdb0223d4b553c120 Mon Sep 17 00:00:00 2001 From: Neko Nekowazarashi Date: Wed, 30 Jul 2025 13:30:45 +0000 Subject: [PATCH 31/38] Translated using Weblate (Indonesian) Currently translated at 77.8% (74 of 95 strings) Translation: Pleroma/Pleroma Backend (domain errors) Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-backend-domain-errors/id/ --- priv/gettext/id/LC_MESSAGES/errors.po | 58 +++++++++++++-------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/priv/gettext/id/LC_MESSAGES/errors.po b/priv/gettext/id/LC_MESSAGES/errors.po index bc98273be..5bb9d56c6 100644 --- a/priv/gettext/id/LC_MESSAGES/errors.po +++ b/priv/gettext/id/LC_MESSAGES/errors.po @@ -3,16 +3,16 @@ msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2021-08-15 15:40+0000\n" -"PO-Revision-Date: 2021-09-03 06:45+0000\n" -"Last-Translator: @liimee \n" +"PO-Revision-Date: 2025-07-31 05:58+0000\n" +"Last-Translator: Neko Nekowazarashi \n" "Language-Team: Indonesian \n" +"pleroma-backend-domain-errors/id/>\n" "Language: id\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -"X-Generator: Weblate 4.6.2\n" +"X-Generator: Weblate 4.13.1\n" ## This file is a PO Template file. ## @@ -41,7 +41,7 @@ msgstr "memiliki format yang tidak valid" ## From Ecto.Changeset.validate_subset/3 msgid "has an invalid entry" -msgstr "" +msgstr "ada yang tidak valid" ## From Ecto.Changeset.validate_exclusion/3 msgid "is reserved" @@ -49,7 +49,7 @@ msgstr "" ## From Ecto.Changeset.validate_confirmation/3 msgid "does not match confirmation" -msgstr "" +msgstr "tidak sama dengan konfirmasi" ## From Ecto.Changeset.no_assoc_constraint/3 msgid "is still associated with this entry" @@ -61,19 +61,19 @@ msgstr "" ## From Ecto.Changeset.validate_length/3 msgid "should be %{count} character(s)" msgid_plural "should be %{count} character(s)" -msgstr[0] "harus memiliki %{count} karakter" +msgstr[0] "harus ada %{count} karakter" msgid "should have %{count} item(s)" msgid_plural "should have %{count} item(s)" -msgstr[0] "harus memiliki %{count} item" +msgstr[0] "harus ada %{count} item" msgid "should be at least %{count} character(s)" msgid_plural "should be at least %{count} character(s)" -msgstr[0] "harus memiliki sekurang-kurangnya %{count} karakter" +msgstr[0] "harus ada sekurang-kurangnya %{count} karakter" msgid "should have at least %{count} item(s)" msgid_plural "should have at least %{count} item(s)" -msgstr[0] "harus memiliki sekurang-kurangnya %{count} item" +msgstr[0] "harus ada sekurang-kurangnya %{count} item" msgid "should be at most %{count} character(s)" msgid_plural "should be at most %{count} character(s)" @@ -112,7 +112,7 @@ msgstr "Sudah memilih" #: lib/pleroma/web/oauth/oauth_controller.ex:359 #, elixir-format msgid "Bad request" -msgstr "Permintaan buruk (bad request)" +msgstr "Permintaan buruk" #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:426 #, elixir-format @@ -133,7 +133,7 @@ msgstr "Tidak dapat mencari pengguna" #: lib/pleroma/web/pleroma_api/controllers/account_controller.ex:61 #, elixir-format msgid "Can't get favorites" -msgstr "Tidak dapat mendapatkan favorit" +msgstr "Tidak dapat mengambil favorit" #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:438 #, elixir-format @@ -143,7 +143,7 @@ msgstr "Tidak dapat menyukai objek" #: lib/pleroma/web/common_api/utils.ex:563 #, elixir-format msgid "Cannot post an empty status without attachments" -msgstr "Tidak dapat memposting status kosong tanpa lampiran" +msgstr "Tidak dapat mempos status kosong tanpa lampiran" #: lib/pleroma/web/common_api/utils.ex:511 #, elixir-format @@ -206,7 +206,7 @@ msgstr "CAPTCHA tidak valid" #: lib/pleroma/web/oauth/oauth_controller.ex:568 #, elixir-format msgid "Invalid credentials" -msgstr "Kredensian tidak valid" +msgstr "Kredensial tidak valid" #: lib/pleroma/plugs/ensure_authenticated_plug.ex:38 #, elixir-format @@ -279,12 +279,12 @@ msgstr "" #: lib/pleroma/web/ostatus/ostatus_controller.ex:149 #, elixir-format msgid "Something went wrong" -msgstr "Sesuatu yang salah terjadi" +msgstr "Ada sesuatu yang salah" #: lib/pleroma/web/common_api/activity_draft.ex:107 #, elixir-format msgid "The message visibility must be direct" -msgstr "Visibilitas pesan harus langsung" +msgstr "Ketampakan pesan harus langsung" #: lib/pleroma/web/common_api/utils.ex:573 #, elixir-format @@ -294,7 +294,7 @@ msgstr "Status lebih dari batas karakter" #: lib/pleroma/plugs/ensure_public_or_authenticated_plug.ex:31 #, elixir-format msgid "This resource requires authentication." -msgstr "" +msgstr "Autentikasi diperlukan untuk hal ini." #: lib/pleroma/plugs/rate_limiter/rate_limiter.ex:206 #, elixir-format @@ -314,7 +314,7 @@ msgstr "" #: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:485 #, elixir-format msgid "You can't revoke your own admin status." -msgstr "" +msgstr "Anda tidak dapat mencabut status admin Anda." #: lib/pleroma/web/oauth/oauth_controller.ex:221 #: lib/pleroma/web/oauth/oauth_controller.ex:308 @@ -382,7 +382,7 @@ msgstr "Gagal" #: lib/pleroma/web/oauth/oauth_controller.ex:410 #, elixir-format msgid "Failed to authenticate: %{message}." -msgstr "Gagal mengotentikasi: %{message}." +msgstr "Gagal mengautentikasi: %{message}." #: lib/pleroma/web/oauth/oauth_controller.ex:441 #, elixir-format @@ -392,7 +392,7 @@ msgstr "Gagal menyiapkan akun pengguna." #: lib/pleroma/plugs/oauth_scopes_plug.ex:38 #, elixir-format msgid "Insufficient permissions: %{permissions}." -msgstr "" +msgstr "Izin tidak cukup: %{permissions}." #: lib/pleroma/plugs/uploaded_media.ex:104 #, elixir-format @@ -418,12 +418,12 @@ msgstr "" #: lib/pleroma/web/oauth/oauth_controller.ex:172 #, elixir-format msgid "This action is outside the authorized scopes" -msgstr "" +msgstr "Tindakan ini diluar jangkauan yang terotorisasi" #: lib/pleroma/web/oauth/fallback_controller.ex:14 #, elixir-format msgid "Unknown error, please check the details and try again." -msgstr "Kesalahan tidak dikenal, harap periksa keterangannya dan coba lagi." +msgstr "Kesalahan tidak dikenal, harap periksa detailnya dan coba lagi." #: lib/pleroma/web/oauth/oauth_controller.ex:119 #: lib/pleroma/web/oauth/oauth_controller.ex:158 @@ -444,7 +444,7 @@ msgstr "" #: lib/pleroma/web/uploader_controller.ex:23 #, elixir-format msgid "bad request" -msgstr "permintaan buruk (bad request)" +msgstr "permintaan buruk" #: lib/pleroma/web/twitter_api/twitter_api.ex:103 #, elixir-format @@ -469,7 +469,7 @@ msgstr "CAPTCHA Tidak Valid (Parameter kurang: %{name})" #: lib/pleroma/web/mastodon_api/controllers/list_controller.ex:92 #, elixir-format msgid "List not found" -msgstr "" +msgstr "Daftar tidak ditemukan" #: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:123 #, elixir-format @@ -480,7 +480,7 @@ msgstr "Parameter kurang: %{name}" #: lib/pleroma/web/oauth/oauth_controller.ex:321 #, elixir-format msgid "Password reset is required" -msgstr "" +msgstr "Diperlukan atur ulang kata sandi" #: lib/pleroma/tests/auth_test_controller.ex:9 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:6 lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:6 @@ -522,7 +522,7 @@ msgstr "" #: lib/pleroma/plugs/ensure_authenticated_plug.ex:28 #, elixir-format msgid "Two-factor authentication enabled, you must use a access token." -msgstr "Otentikasi dua-faktor diaktifkan, Anda harus menggunakan token akses." +msgstr "Autentikasi dua langkah diaktifkan, Anda harus menggunakan token akses." #: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:210 #, elixir-format @@ -552,7 +552,7 @@ msgstr "" #: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:61 #, elixir-format msgid "Web push subscription is disabled on this Pleroma instance" -msgstr "" +msgstr "Langganan push web dinonaktifkan untuk peladen Pleroma ini" #: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:451 #, elixir-format @@ -562,7 +562,7 @@ msgstr "Anda tidak bisa mencabut status admin/moderator Anda sendiri." #: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:126 #, elixir-format msgid "authorization required for timeline view" -msgstr "" +msgstr "Otorisasi diperlukan untuk tampilan linimasa" #: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:24 #, elixir-format @@ -572,7 +572,7 @@ msgstr "Akses ditolak" #: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:282 #, elixir-format msgid "This API requires an authenticated user" -msgstr "" +msgstr "API ini memerlukan pengguna terautentikasi" #: lib/pleroma/plugs/user_is_admin_plug.ex:21 #, elixir-format From 526365364bf98da6172f60732396e5a87178b1f7 Mon Sep 17 00:00:00 2001 From: Neko Nekowazarashi Date: Mon, 11 Aug 2025 15:40:00 +0000 Subject: [PATCH 32/38] Added translation using Weblate (Indonesian) --- priv/gettext/id/LC_MESSAGES/posix_errors.po | 163 ++++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100644 priv/gettext/id/LC_MESSAGES/posix_errors.po diff --git a/priv/gettext/id/LC_MESSAGES/posix_errors.po b/priv/gettext/id/LC_MESSAGES/posix_errors.po new file mode 100644 index 000000000..f5ea93285 --- /dev/null +++ b/priv/gettext/id/LC_MESSAGES/posix_errors.po @@ -0,0 +1,163 @@ +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-08-11 18:40+0300\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: id\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Translate Toolkit 3.7.2\n" + +## This file is a PO Template file. +## +## `msgid`s here are often extracted from source code. +## Add new translations manually only if they're dynamic +## translations that can't be statically extracted. +## +## Run `mix gettext.extract` to bring this file up to +## date. Leave `msgstr`s empty as changing them here as no +## effect: edit them in PO (`.po`) files instead. +msgid "eperm" +msgstr "" + +msgid "eacces" +msgstr "" + +msgid "eagain" +msgstr "" + +msgid "ebadf" +msgstr "" + +msgid "ebadmsg" +msgstr "" + +msgid "ebusy" +msgstr "" + +msgid "edeadlk" +msgstr "" + +msgid "edeadlock" +msgstr "" + +msgid "edquot" +msgstr "" + +msgid "eexist" +msgstr "" + +msgid "efault" +msgstr "" + +msgid "efbig" +msgstr "" + +msgid "eftype" +msgstr "" + +msgid "eintr" +msgstr "" + +msgid "einval" +msgstr "" + +msgid "eio" +msgstr "" + +msgid "eisdir" +msgstr "" + +msgid "eloop" +msgstr "" + +msgid "emfile" +msgstr "" + +msgid "emlink" +msgstr "" + +msgid "emultihop" +msgstr "" + +msgid "enametoolong" +msgstr "" + +msgid "enfile" +msgstr "" + +msgid "enobufs" +msgstr "" + +msgid "enodev" +msgstr "" + +msgid "enolck" +msgstr "" + +msgid "enolink" +msgstr "" + +msgid "enoent" +msgstr "" + +msgid "enomem" +msgstr "" + +msgid "enospc" +msgstr "" + +msgid "enosr" +msgstr "" + +msgid "enostr" +msgstr "" + +msgid "enosys" +msgstr "" + +msgid "enotblk" +msgstr "" + +msgid "enotdir" +msgstr "" + +msgid "enotsup" +msgstr "" + +msgid "enxio" +msgstr "" + +msgid "eopnotsupp" +msgstr "" + +msgid "eoverflow" +msgstr "" + +msgid "epipe" +msgstr "" + +msgid "erange" +msgstr "" + +msgid "erofs" +msgstr "" + +msgid "espipe" +msgstr "" + +msgid "esrch" +msgstr "" + +msgid "estale" +msgstr "" + +msgid "etxtbsy" +msgstr "" + +msgid "exdev" +msgstr "" From 4fef91030308b0d73d085cb10b2e49790f322ca1 Mon Sep 17 00:00:00 2001 From: Neko Nekowazarashi Date: Mon, 11 Aug 2025 15:40:23 +0000 Subject: [PATCH 33/38] Added translation using Weblate (Indonesian) --- priv/gettext/id/LC_MESSAGES/static_pages.po | 574 ++++++++++++++++++++ 1 file changed, 574 insertions(+) create mode 100644 priv/gettext/id/LC_MESSAGES/static_pages.po diff --git a/priv/gettext/id/LC_MESSAGES/static_pages.po b/priv/gettext/id/LC_MESSAGES/static_pages.po new file mode 100644 index 000000000..e2565888a --- /dev/null +++ b/priv/gettext/id/LC_MESSAGES/static_pages.po @@ -0,0 +1,574 @@ +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-08-11 18:40+0300\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: id\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Translate Toolkit 3.7.2\n" + +## This file is a PO Template file. +## +## "msgid"s here are often extracted from source code. +## Add new translations manually only if they're dynamic +## translations that can't be statically extracted. +## +## Run "mix gettext.extract" to bring this file up to +## date. Leave "msgstr"s empty as changing them here as no +## effect: edit them in PO (.po) files instead. + +#: lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex:9 +#, elixir-autogen, elixir-format +msgctxt "remote follow authorization button" +msgid "Authorize" +msgstr "" + +#: lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex:2 +#, elixir-autogen, elixir-format +msgctxt "remote follow error" +msgid "Error fetching user" +msgstr "" + +#: lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex:4 +#, elixir-autogen, elixir-format +msgctxt "remote follow header" +msgid "Remote follow" +msgstr "" + +#: lib/pleroma/web/templates/twitter_api/remote_follow/follow_mfa.html.eex:8 +#, elixir-autogen, elixir-format +msgctxt "placeholder text for auth code entry" +msgid "Authentication code" +msgstr "" + +#: lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex:10 +#, elixir-autogen, elixir-format +msgctxt "placeholder text for password entry" +msgid "Password" +msgstr "" + +#: lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex:8 +#, elixir-autogen, elixir-format +msgctxt "placeholder text for username entry" +msgid "Username" +msgstr "" + +#: lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex:13 +#, elixir-autogen, elixir-format +msgctxt "remote follow authorization button for login" +msgid "Authorize" +msgstr "" + +#: lib/pleroma/web/templates/twitter_api/remote_follow/follow_mfa.html.eex:12 +#, elixir-autogen, elixir-format +msgctxt "remote follow authorization button for mfa" +msgid "Authorize" +msgstr "" + +#: lib/pleroma/web/templates/twitter_api/remote_follow/followed.html.eex:2 +#, elixir-autogen, elixir-format +msgctxt "remote follow error" +msgid "Error following account" +msgstr "" + +#: lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex:4 +#, elixir-autogen, elixir-format +msgctxt "remote follow header, need login" +msgid "Log in to follow" +msgstr "" + +#: lib/pleroma/web/templates/twitter_api/remote_follow/follow_mfa.html.eex:4 +#, elixir-autogen, elixir-format +msgctxt "remote follow mfa header" +msgid "Two-factor authentication" +msgstr "" + +#: lib/pleroma/web/templates/twitter_api/remote_follow/followed.html.eex:4 +#, elixir-autogen, elixir-format +msgctxt "remote follow success" +msgid "Account followed!" +msgstr "" + +#: lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex:7 +#: lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex:7 +#, elixir-autogen, elixir-format +msgctxt "placeholder text for account id" +msgid "Your account ID, e.g. lain@quitter.se" +msgstr "" + +#: lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex:8 +#, elixir-autogen, elixir-format +msgctxt "remote follow authorization button for following with a remote account" +msgid "Follow" +msgstr "" + +#: lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex:2 +#, elixir-autogen, elixir-format +msgctxt "remote follow error" +msgid "Error: %{error}" +msgstr "" + +#: lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex:4 +#, elixir-autogen, elixir-format +msgctxt "remote follow header" +msgid "Remotely follow %{nickname}" +msgstr "" + +#: lib/pleroma/web/templates/twitter_api/password/reset.html.eex:12 +#, elixir-autogen, elixir-format +msgctxt "password reset button" +msgid "Reset" +msgstr "" + +#: lib/pleroma/web/templates/twitter_api/password/reset_failed.html.eex:4 +#, elixir-autogen, elixir-format +msgctxt "password reset failed homepage link" +msgid "Homepage" +msgstr "" + +#: lib/pleroma/web/templates/twitter_api/password/reset_failed.html.eex:1 +#, elixir-autogen, elixir-format +msgctxt "password reset failed message" +msgid "Password reset failed" +msgstr "" + +#: lib/pleroma/web/templates/twitter_api/password/reset.html.eex:8 +#, elixir-autogen, elixir-format +msgctxt "password reset form confirm password prompt" +msgid "Confirmation" +msgstr "" + +#: lib/pleroma/web/templates/twitter_api/password/reset.html.eex:4 +#, elixir-autogen, elixir-format +msgctxt "password reset form password prompt" +msgid "Password" +msgstr "" + +#: lib/pleroma/web/templates/twitter_api/password/invalid_token.html.eex:1 +#, elixir-autogen, elixir-format +msgctxt "password reset invalid token message" +msgid "Invalid Token" +msgstr "" + +#: lib/pleroma/web/templates/twitter_api/password/reset_success.html.eex:2 +#, elixir-autogen, elixir-format +msgctxt "password reset successful homepage link" +msgid "Homepage" +msgstr "" + +#: lib/pleroma/web/templates/twitter_api/password/reset_success.html.eex:1 +#, elixir-autogen, elixir-format +msgctxt "password reset successful message" +msgid "Password changed!" +msgstr "" + +#: lib/pleroma/web/templates/feed/feed/tag.atom.eex:12 +#: lib/pleroma/web/templates/feed/feed/tag.rss.eex:8 +#, elixir-autogen, elixir-format +msgctxt "tag feed description" +msgid "These are public toots tagged with #%{tag}. You can interact with them if you have an account anywhere in the fediverse." +msgstr "" + +#: lib/pleroma/web/templates/o_auth/o_auth/oob_token_exists.html.eex:1 +#, elixir-autogen, elixir-format +msgctxt "oauth authorization exists page title" +msgid "Authorization exists" +msgstr "" + +#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:32 +#, elixir-autogen, elixir-format +msgctxt "oauth authorize approve button" +msgid "Approve" +msgstr "" + +#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:30 +#, elixir-autogen, elixir-format +msgctxt "oauth authorize cancel button" +msgid "Cancel" +msgstr "" + +#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:23 +#, elixir-autogen, elixir-format +msgctxt "oauth authorize message" +msgid "Application %{client_name} is requesting access to your account." +msgstr "" + +#: lib/pleroma/web/templates/o_auth/o_auth/oob_authorization_created.html.eex:1 +#, elixir-autogen, elixir-format +msgctxt "oauth authorized page title" +msgid "Successfully authorized" +msgstr "" + +#: lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex:1 +#, elixir-autogen, elixir-format +msgctxt "oauth external provider page title" +msgid "Sign in with external provider" +msgstr "" + +#: lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex:13 +#, elixir-autogen, elixir-format +msgctxt "oauth external provider sign in button" +msgid "Sign in with %{strategy}" +msgstr "" + +#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:54 +#, elixir-autogen, elixir-format +msgctxt "oauth login button" +msgid "Log In" +msgstr "" + +#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:51 +#, elixir-autogen, elixir-format +msgctxt "oauth login password prompt" +msgid "Password" +msgstr "" + +#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:47 +#, elixir-autogen, elixir-format +msgctxt "oauth login username prompt" +msgid "Username" +msgstr "" + +#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:39 +#, elixir-autogen, elixir-format +msgctxt "oauth register nickname prompt" +msgid "Pleroma Handle" +msgstr "" + +#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:37 +#, elixir-autogen, elixir-format +msgctxt "oauth register nickname unchangeable warning" +msgid "Choose carefully! You won't be able to change this later. You will be able to change your display name, though." +msgstr "" + +#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:18 +#, elixir-autogen, elixir-format +msgctxt "oauth register page email prompt" +msgid "Email" +msgstr "" + +#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:10 +#, elixir-autogen, elixir-format +msgctxt "oauth register page fill form prompt" +msgid "If you'd like to register a new account, please provide the details below." +msgstr "" + +#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:35 +#, elixir-autogen, elixir-format +msgctxt "oauth register page login button" +msgid "Proceed as existing user" +msgstr "" + +#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:31 +#, elixir-autogen, elixir-format +msgctxt "oauth register page login password prompt" +msgid "Password" +msgstr "" + +#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:24 +#, elixir-autogen, elixir-format +msgctxt "oauth register page login prompt" +msgid "Alternatively, sign in to connect to existing account." +msgstr "" + +#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:27 +#, elixir-autogen, elixir-format +msgctxt "oauth register page login username prompt" +msgid "Name or email" +msgstr "" + +#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:14 +#, elixir-autogen, elixir-format +msgctxt "oauth register page nickname prompt" +msgid "Nickname" +msgstr "" + +#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:22 +#, elixir-autogen, elixir-format +msgctxt "oauth register page register button" +msgid "Proceed as new user" +msgstr "" + +#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:8 +#, elixir-autogen, elixir-format +msgctxt "oauth register page title" +msgid "Registration Details" +msgstr "" + +#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:36 +#, elixir-autogen, elixir-format +msgctxt "oauth register page title" +msgid "This is the first time you visit! Please enter your Pleroma handle." +msgstr "" + +#: lib/pleroma/web/templates/o_auth/o_auth/_scopes.html.eex:2 +#, elixir-autogen, elixir-format +msgctxt "oauth scopes message" +msgid "The following permissions will be granted" +msgstr "" + +#: lib/pleroma/web/templates/o_auth/o_auth/oob_authorization_created.html.eex:2 +#: lib/pleroma/web/templates/o_auth/o_auth/oob_token_exists.html.eex:2 +#, elixir-autogen, elixir-format +msgctxt "oauth token code message" +msgid "Token code is
%{token}" +msgstr "" + +#: lib/pleroma/web/templates/o_auth/mfa/totp.html.eex:12 +#, elixir-autogen, elixir-format +msgctxt "mfa auth code prompt" +msgid "Authentication code" +msgstr "" + +#: lib/pleroma/web/templates/o_auth/mfa/totp.html.eex:8 +#, elixir-autogen, elixir-format +msgctxt "mfa auth page title" +msgid "Two-factor authentication" +msgstr "" + +#: lib/pleroma/web/templates/o_auth/mfa/totp.html.eex:23 +#, elixir-autogen, elixir-format +msgctxt "mfa auth page use recovery code link" +msgid "Enter a two-factor recovery code" +msgstr "" + +#: lib/pleroma/web/templates/o_auth/mfa/totp.html.eex:20 +#, elixir-autogen, elixir-format +msgctxt "mfa auth verify code button" +msgid "Verify" +msgstr "" + +#: lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex:8 +#, elixir-autogen, elixir-format +msgctxt "mfa recover page title" +msgid "Two-factor recovery" +msgstr "" + +#: lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex:12 +#, elixir-autogen, elixir-format +msgctxt "mfa recover recovery code prompt" +msgid "Recovery code" +msgstr "" + +#: lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex:23 +#, elixir-autogen, elixir-format +msgctxt "mfa recover use 2fa code link" +msgid "Enter a two-factor code" +msgstr "" + +#: lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex:20 +#, elixir-autogen, elixir-format +msgctxt "mfa recover verify recovery code button" +msgid "Verify" +msgstr "" + +#: lib/pleroma/web/templates/static_fe/static_fe/profile.html.eex:8 +#, elixir-autogen, elixir-format +msgctxt "static fe profile page remote follow button" +msgid "Remote follow" +msgstr "" + +#: lib/pleroma/web/templates/email/digest.html.eex:163 +#, elixir-autogen, elixir-format +msgctxt "digest email header line" +msgid "Hey %{nickname}, here is what you've missed!" +msgstr "" + +#: lib/pleroma/web/templates/email/digest.html.eex:544 +#, elixir-autogen, elixir-format +msgctxt "digest email receiver address" +msgid "The email address you are subscribed as is %{email}. " +msgstr "" + +#: lib/pleroma/web/templates/email/digest.html.eex:538 +#, elixir-autogen, elixir-format +msgctxt "digest email sending reason" +msgid "You have received this email because you have signed up to receive digest emails from %{instance} Pleroma instance." +msgstr "" + +#: lib/pleroma/web/templates/email/digest.html.eex:547 +#, elixir-autogen, elixir-format +msgctxt "digest email unsubscribe action" +msgid "To unsubscribe, please go %{here}." +msgstr "" + +#: lib/pleroma/web/templates/email/digest.html.eex:547 +#, elixir-autogen, elixir-format +msgctxt "digest email unsubscribe action link text" +msgid "here" +msgstr "" + +#: lib/pleroma/web/templates/mailer/subscription/unsubscribe_failure.html.eex:1 +#, elixir-autogen, elixir-format +msgctxt "mailer unsubscribe failed message" +msgid "UNSUBSCRIBE FAILURE" +msgstr "" + +#: lib/pleroma/web/templates/mailer/subscription/unsubscribe_success.html.eex:1 +#, elixir-autogen, elixir-format +msgctxt "mailer unsubscribe successful message" +msgid "UNSUBSCRIBE SUCCESSFUL" +msgstr "" + +#: lib/pleroma/web/templates/email/digest.html.eex:385 +#, elixir-format +msgctxt "new followers count header" +msgid "%{count} New Follower" +msgid_plural "%{count} New Followers" +msgstr[0] "" +msgstr[1] "" + +#: lib/pleroma/emails/user_email.ex:356 +#, elixir-autogen, elixir-format +msgctxt "account archive email body - self-requested" +msgid "

You requested a full backup of your Pleroma account. It's ready for download:

\n

%{download_url}

\n" +msgstr "" + +#: lib/pleroma/emails/user_email.ex:384 +#, elixir-autogen, elixir-format +msgctxt "account archive email subject" +msgid "Your account archive is ready" +msgstr "" + +#: lib/pleroma/emails/user_email.ex:188 +#, elixir-autogen, elixir-format +msgctxt "approval pending email body" +msgid "

Awaiting Approval

\n

Your account at %{instance_name} is being reviewed by staff. You will receive another email once your account is approved.

\n" +msgstr "" + +#: lib/pleroma/emails/user_email.ex:202 +#, elixir-autogen, elixir-format +msgctxt "approval pending email subject" +msgid "Your account is awaiting approval" +msgstr "" + +#: lib/pleroma/emails/user_email.ex:158 +#, elixir-autogen, elixir-format +msgctxt "confirmation email body" +msgid "

Thank you for registering on %{instance_name}

\n

Email confirmation is required to activate the account.

\n

Please click the following link to activate your account.

\n" +msgstr "" + +#: lib/pleroma/emails/user_email.ex:174 +#, elixir-autogen, elixir-format +msgctxt "confirmation email subject" +msgid "%{instance_name} account confirmation" +msgstr "" + +#: lib/pleroma/emails/user_email.ex:310 +#, elixir-autogen, elixir-format +msgctxt "digest email subject" +msgid "Your digest from %{instance_name}" +msgstr "" + +#: lib/pleroma/emails/user_email.ex:81 +#, elixir-autogen, elixir-format +msgctxt "password reset email body" +msgid "

Reset your password at %{instance_name}

\n

Someone has requested password change for your account at %{instance_name}.

\n

If it was you, visit the following link to proceed: reset password.

\n

If it was someone else, nothing to worry about: your data is secure and your password has not been changed.

\n" +msgstr "" + +#: lib/pleroma/emails/user_email.ex:98 +#, elixir-autogen, elixir-format +msgctxt "password reset email subject" +msgid "Password reset" +msgstr "" + +#: lib/pleroma/emails/user_email.ex:215 +#, elixir-autogen, elixir-format +msgctxt "successful registration email body" +msgid "

Hello @%{nickname},

\n

Your account at %{instance_name} has been registered successfully.

\n

No further action is required to activate your account.

\n" +msgstr "" + +#: lib/pleroma/emails/user_email.ex:231 +#, elixir-autogen, elixir-format +msgctxt "successful registration email subject" +msgid "Account registered on %{instance_name}" +msgstr "" + +#: lib/pleroma/emails/user_email.ex:119 +#, elixir-autogen, elixir-format +msgctxt "user invitation email body" +msgid "

You are invited to %{instance_name}

\n

%{inviter_name} invites you to join %{instance_name}, an instance of Pleroma federated social networking platform.

\n

Click the following link to register: accept invitation.

\n" +msgstr "" + +#: lib/pleroma/emails/user_email.ex:136 +#, elixir-autogen, elixir-format +msgctxt "user invitation email subject" +msgid "Invitation to %{instance_name}" +msgstr "" + +#: lib/pleroma/emails/user_email.ex:53 +#, elixir-autogen, elixir-format +msgctxt "welcome email html body" +msgid "Welcome to %{instance_name}!" +msgstr "" + +#: lib/pleroma/emails/user_email.ex:41 +#, elixir-autogen, elixir-format +msgctxt "welcome email subject" +msgid "Welcome to %{instance_name}!" +msgstr "" + +#: lib/pleroma/emails/user_email.ex:65 +#, elixir-autogen, elixir-format +msgctxt "welcome email text body" +msgid "Welcome to %{instance_name}!" +msgstr "" + +#: lib/pleroma/emails/user_email.ex:368 +#, elixir-autogen, elixir-format +msgctxt "account archive email body - admin requested" +msgid "

Admin @%{admin_nickname} requested a full backup of your Pleroma account. It's ready for download:

\n

%{download_url}

\n" +msgstr "" + +#: lib/pleroma/web/twitter_api/controllers/util_controller.ex:123 +#, elixir-autogen, elixir-format +msgctxt "remote follow error message - unknown error" +msgid "Something went wrong." +msgstr "" + +#: lib/pleroma/web/twitter_api/controllers/util_controller.ex:67 +#, elixir-autogen, elixir-format +msgctxt "remote follow error message - user not found" +msgid "Could not find user" +msgstr "" + +#: lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex:8 +#, elixir-autogen, elixir-format +msgctxt "status interact authorization button" +msgid "Interact" +msgstr "" + +#: lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex:2 +#, elixir-autogen, elixir-format +msgctxt "status interact error" +msgid "Error: %{error}" +msgstr "" + +#: lib/pleroma/web/twitter_api/controllers/util_controller.ex:95 +#, elixir-autogen, elixir-format +msgctxt "status interact error message - status not found" +msgid "Could not find status" +msgstr "" + +#: lib/pleroma/web/twitter_api/controllers/util_controller.ex:144 +#, elixir-autogen, elixir-format +msgctxt "status interact error message - unknown error" +msgid "Something went wrong." +msgstr "" + +#: lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex:4 +#, elixir-autogen, elixir-format +msgctxt "status interact header" +msgid "Interacting with %{nickname}'s %{status_link}" +msgstr "" + +#: lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex:4 +#, elixir-autogen, elixir-format +msgctxt "status interact header - status link text" +msgid "status" +msgstr "" From 086c15b5cd4d431bb54974d470de5b81321ebe38 Mon Sep 17 00:00:00 2001 From: Codimp Date: Fri, 26 Dec 2025 18:18:33 +0000 Subject: [PATCH 34/38] Translated using Weblate (French) Currently translated at 100.0% (80 of 80 strings) Translation: Pleroma/Pleroma Backend (domain errors) Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-backend-domain-errors/fr/ --- priv/gettext/fr/LC_MESSAGES/errors.po | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/priv/gettext/fr/LC_MESSAGES/errors.po b/priv/gettext/fr/LC_MESSAGES/errors.po index 406f98de9..20636cee3 100644 --- a/priv/gettext/fr/LC_MESSAGES/errors.po +++ b/priv/gettext/fr/LC_MESSAGES/errors.po @@ -8,16 +8,15 @@ ## to merge POT files into PO files. msgid "" msgstr "" -"PO-Revision-Date: 2020-05-12 15:52+0000\n" -"Last-Translator: Haelwenn (lanodan) Monnier " -"\n" +"PO-Revision-Date: 2025-12-27 07:08+0000\n" +"Last-Translator: Codimp \n" "Language-Team: French \n" +"pleroma-backend-domain-errors/fr/>\n" "Language: fr\n" "Content-Type: text/plain; charset=UTF-8\n" -"Plural-Forms: nplurals=2; plural=n > 1;\n" -"X-Generator: Weblate 4.0.4\n" "Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n > 1;\n" +"X-Generator: Weblate 4.13.1\n" msgid "can't be blank" msgstr "ne peut être vide" @@ -357,7 +356,7 @@ msgstr "Ne peut poster dans la boite d'émission de %{nickname} en tant que %{as #: lib/pleroma/web/common_api/common_api.ex:335 #, elixir-format msgid "conversation is already muted" -msgstr "la conversation est déjà baillonée" +msgstr "la conversation est déjà silenciée" #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:192 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:317 @@ -421,7 +420,7 @@ msgstr "Erreur interne" #: lib/pleroma/web/oauth/fallback_controller.ex:29 #, elixir-format msgid "Invalid Username/Password" -msgstr "Nom d'utilisateur/mot de passe invalide" +msgstr "Nom d'utilisateur·ice/mot de passe invalide" #: lib/pleroma/captcha/captcha.ex:107 #, elixir-format From d7a0d97c3687d3a8bac2597a223bb686693aaa8a Mon Sep 17 00:00:00 2001 From: Codimp Date: Fri, 26 Dec 2025 18:20:14 +0000 Subject: [PATCH 35/38] Translated using Weblate (French) Currently translated at 10.8% (106 of 974 strings) Translation: Pleroma/Pleroma Backend (domain config_descriptions) Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-backend-domain-config_descriptions/fr/ --- priv/gettext/fr/LC_MESSAGES/config_descriptions.po | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/priv/gettext/fr/LC_MESSAGES/config_descriptions.po b/priv/gettext/fr/LC_MESSAGES/config_descriptions.po index 56ea1bdf3..2e12baa43 100644 --- a/priv/gettext/fr/LC_MESSAGES/config_descriptions.po +++ b/priv/gettext/fr/LC_MESSAGES/config_descriptions.po @@ -3,8 +3,8 @@ msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-07-22 02:09+0300\n" -"PO-Revision-Date: 2025-04-11 01:04+0000\n" -"Last-Translator: Irrlicht \n" +"PO-Revision-Date: 2025-12-27 07:08+0000\n" +"Last-Translator: Codimp \n" "Language-Team: French \n" "Language: fr\n" @@ -280,7 +280,7 @@ msgstr "" "Rejeter, Enlever de TWKN ou marquer comme contenu sensible les messages avec " "des mots-croisillons (sans mettre le # du début)\n" "\n" -"Note: cette politique MRF est toujours activée. Si vous voulez la " +"Note : cette politique MRF est toujours activée. Si vous voulez la " "désactiver, vous devez configurer des listes vides.\n" #: lib/pleroma/docs/translator.ex:5 @@ -468,10 +468,9 @@ msgctxt "config description at :pleroma-Pleroma.Captcha.Kocaptcha" msgid "Kocaptcha is a very simple captcha service with a single API endpoint, the source code is here: https://github.com/koto-bank/kocaptcha. The default endpoint (https://captcha.kotobank.ch) is hosted by the developer." msgstr "" "Kocaptcha est un service de captcha très simple avec une API n'utilisant " -"qu'une seule ressource, le code source est ici: https://github.com/koto-bank/" -"kocaptcha.\n" -"La ressource par défaut (https://captcha.kotobank.ch) est hébergée par son " -"développeur." +"qu'une seule ressource, le code source est ici : https://github.com/" +"koto-bank/kocaptcha. La ressource par défaut (https://captcha.kotobank.ch) " +"est hébergée par son développeur." #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format From 1a13ec539baca66d23db899d92b42c181aaf3149 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Mon, 18 May 2026 21:05:46 +0300 Subject: [PATCH 36/38] reorganize mfm attributes in order they're listed in misskey repo, add missing ones --- priv/scrubbers/default.ex | 49 ++++++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/priv/scrubbers/default.ex b/priv/scrubbers/default.ex index 342ef9944..d5c7340d6 100644 --- a/priv/scrubbers/default.ex +++ b/priv/scrubbers/default.ex @@ -83,48 +83,55 @@ defmodule Pleroma.HTML.Scrubber.Default do "quote-inline", "invisible", "ellipsis", - "mfm-center", - "mfm-flip", - "mfm-font", - "mfm-blur", - "mfm-rotate", - "mfm-x2", - "mfm-x3", - "mfm-x4", - "mfm-position", - "mfm-scale", - "mfm-fg", - "mfm-bg", + "mfm-tada", "mfm-jelly", "mfm-twitch", "mfm-shake", "mfm-spin", "mfm-jump", "mfm-bounce", + "mfm-flip", + "mfm-x2", + "mfm-x3", + "mfm-x4", + "mfm-scale", + "mfm-position", + "mfm-fg", + "mfm-bg", + "mfm-border", + "mfm-font", + "mfm-blur", "mfm-rainbow", - "mfm-tada", - "mfm-sparkle" + "mfm-sparkle", + "mfm-rotate", + "mfm-ruby", + "mfm-unixtime", ]) Meta.allow_tag_with_this_attribute_values(:p, "class", ["quote-inline"]) Meta.allow_tag_with_these_attributes(:span, [ "lang", - "data-mfm-h", - "data-mfm-v", + "data-mfm-speed", + "data-mfm-delay", + "data-mfm-left", + "data-mfm-alternate", "data-mfm-x", "data-mfm-y", - "data-mfm-alternate", - "data-mfm-speed", - "data-mfm-deg", - "data-mfm-left", + "data-mfm-h", + "data-mfm-v", + "data-mfm-color", + "data-mfm-width", + "data-mfm-style", + "data-mfm-radius", + "data-mfm-noclip", "data-mfm-serif", "data-mfm-monospace", "data-mfm-cursive", "data-mfm-fantasy", "data-mfm-emoji", "data-mfm-math", - "data-mfm-color" + "data-mfm-deg", ]) Meta.allow_tag_with_this_attribute_values(:code, "class", ["inline"]) From c428cf43e8683f5428341845629013504d0057bf Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Mon, 18 May 2026 21:07:12 +0300 Subject: [PATCH 37/38] "changelog" --- changelog.d/mfm-extend.skip | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 changelog.d/mfm-extend.skip diff --git a/changelog.d/mfm-extend.skip b/changelog.d/mfm-extend.skip new file mode 100644 index 000000000..e69de29bb From 46a28086900600d187348b796c486d11575f22da Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Mon, 18 May 2026 21:08:24 +0300 Subject: [PATCH 38/38] extraneous commas --- priv/scrubbers/default.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/priv/scrubbers/default.ex b/priv/scrubbers/default.ex index d5c7340d6..8963d99e8 100644 --- a/priv/scrubbers/default.ex +++ b/priv/scrubbers/default.ex @@ -105,7 +105,7 @@ defmodule Pleroma.HTML.Scrubber.Default do "mfm-sparkle", "mfm-rotate", "mfm-ruby", - "mfm-unixtime", + "mfm-unixtime" ]) Meta.allow_tag_with_this_attribute_values(:p, "class", ["quote-inline"]) @@ -131,7 +131,7 @@ defmodule Pleroma.HTML.Scrubber.Default do "data-mfm-fantasy", "data-mfm-emoji", "data-mfm-math", - "data-mfm-deg", + "data-mfm-deg" ]) Meta.allow_tag_with_this_attribute_values(:code, "class", ["inline"])