From e63b7d67158a59547c1c7290c7628b6ad55b942d Mon Sep 17 00:00:00 2001 From: Lain Soykaf Date: Wed, 22 Jul 2026 14:17:17 +0400 Subject: [PATCH] fix(formatter): preserve remote MFM mentions --- lib/pleroma/formatter.ex | 20 +++++++++++ lib/pleroma/web/common_api/utils.ex | 2 +- .../article_note_page_validator_test.exs | 33 +++++++++++++++++++ test/pleroma/web/common_api/utils_test.exs | 8 +++++ 4 files changed, 62 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/formatter.ex b/lib/pleroma/formatter.ex index 13dbb9e0d..61d9bee45 100644 --- a/lib/pleroma/formatter.ex +++ b/lib/pleroma/formatter.ex @@ -129,6 +129,7 @@ defmodule Pleroma.Formatter do def markdown_to_html(text, opts) do breaks = Map.get(opts, :breaks, false) + strip_email_autolinks = Map.get(opts, :strip_email_autolinks, false) text = normalize_markdown_quirks(text) MDEx.new( @@ -138,6 +139,7 @@ defmodule Pleroma.Formatter do render: [hardbreaks: breaks, unsafe: true] ) |> MDEx.Document.run() + |> maybe_strip_email_autolinks(strip_email_autolinks) |> MDEx.Document.update_nodes(MDEx.HtmlBlock, &strip_dangerous_raw_html/1) |> MDEx.Document.update_nodes(MDEx.HtmlInline, &strip_dangerous_raw_html/1) |> MDEx.to_html!() @@ -176,6 +178,24 @@ defmodule Pleroma.Formatter do String.ends_with?(markdown, ">") end + defp maybe_strip_email_autolinks(document, true) do + MDEx.Document.update_nodes(document, MDEx.Link, &strip_email_autolink/1) + end + + defp maybe_strip_email_autolinks(document, _), do: document + + defp strip_email_autolink( + %MDEx.Link{ + nodes: [%MDEx.Text{literal: literal, sourcepos: text_sourcepos} = text], + url: "mailto:" <> email, + sourcepos: sourcepos + } = link + ) do + if email == literal and sourcepos == text_sourcepos, do: text, else: link + end + + defp strip_email_autolink(link), do: link + defp strip_dangerous_raw_html(%{literal: literal} = node) do literal = literal diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex index 26034d685..255803c14 100644 --- a/lib/pleroma/web/common_api/utils.ex +++ b/lib/pleroma/web/common_api/utils.ex @@ -324,7 +324,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do def format_input(text, "text/x.misskeymarkdown", options) do text - |> Formatter.markdown_to_html(%{breaks: true}) + |> Formatter.markdown_to_html(%{breaks: true, strip_email_autolinks: true}) |> safe_mfm_to_html() |> Formatter.linkify(options) |> Formatter.html_escape("text/x.misskeymarkdown") diff --git a/test/pleroma/web/activity_pub/object_validators/article_note_page_validator_test.exs b/test/pleroma/web/activity_pub/object_validators/article_note_page_validator_test.exs index bf9c70fb6..96efbf2cc 100644 --- a/test/pleroma/web/activity_pub/object_validators/article_note_page_validator_test.exs +++ b/test/pleroma/web/activity_pub/object_validators/article_note_page_validator_test.exs @@ -216,6 +216,39 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidatorTest assert content =~ "@uncached" end + test "a Misskey MFM remote mention is not rendered as an email link" do + remote_user = insert(:user, ap_id: "https://mk.absturztau.be/users/alice") + mentioned_user = insert(:user, ap_id: "https://movsw.0x0.st/users/bob") + + note = %{ + "id" => "https://mk.absturztau.be/notes/1", + "type" => "Note", + "actor" => remote_user.ap_id, + "attributedTo" => remote_user.ap_id, + "to" => ["https://www.w3.org/ns/activitystreams#Public"], + "cc" => [mentioned_user.ap_id], + "content" => ~s(

@bob@movsw.0x0.st hello

), + "context" => Utils.generate_context_id(), + "tag" => [ + %{ + "type" => "Mention", + "name" => "@bob@movsw.0x0.st", + "href" => mentioned_user.ap_id + } + ], + "source" => %{ + "content" => "@bob@movsw.0x0.st hello", + "mediaType" => "text/x.misskeymarkdown" + } + } + + %{valid?: true, changes: %{content: content}} = + ArticleNotePageValidator.cast_and_validate(note) + + assert content =~ ~s(href="#{mentioned_user.ap_id}") + refute content =~ "mailto:" + end + test "a Misskey MFM note drops oversized source content instead of parsing it" do user = insert(:user, ap_id: "https://misskey.example/users/oversized") diff --git a/test/pleroma/web/common_api/utils_test.exs b/test/pleroma/web/common_api/utils_test.exs index 29e7fd9d7..ff5150c40 100644 --- a/test/pleroma/web/common_api/utils_test.exs +++ b/test/pleroma/web/common_api/utils_test.exs @@ -125,6 +125,14 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do assert output == expected end + test "MFM keeps bare URLs containing Markdown delimiters intact" do + url = "https://example.org/a*b*c" + + {output, [], []} = Utils.format_input(url, "text/x.misskeymarkdown") + + assert output == ~s(

#{url}

) + end + test "works for bare text/bbcode" do text = "[b]hello world[/b]" expected = "hello world"