Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F85650225
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Size
6 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/changelog.d/mfm-code-blocks.fix b/changelog.d/mfm-code-blocks.fix
new file mode 100644
index 000000000..d8eca3581
--- /dev/null
+++ b/changelog.d/mfm-code-blocks.fix
@@ -0,0 +1 @@
+Fix MFM being rendered inside <code> and <pre> elements; MFM syntax in quoted code is now shown verbatim, matching Misskey
diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex
index 26034d685..c160abf64 100644
--- a/lib/pleroma/web/common_api/utils.ex
+++ b/lib/pleroma/web/common_api/utils.ex
@@ -338,16 +338,73 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|> Formatter.html_escape("text/html")
end
+ # <pre> and <code> elements are shielded from MFM parsing, mirroring how
+ # Misskey renders MFM: markup like `$[x2 text]` inside quoted code is shown
+ # verbatim instead of being turned into an MFM span.
+ @mfm_code_element ~r/<pre(?:\s[^>]*)?>.*?<\/pre>|<code(?:\s[^>]*)?>.*?<\/code>/is
+
defp safe_mfm_to_html(html) do
- html
+ {open_marker, close_marker} = mfm_code_markers(html)
+ {masked, segments} = mask_mfm_code_elements(html, open_marker, close_marker)
+
+ masked
|> MfmParser.Parser.parse()
|> MfmParser.Encoder.to_html()
+ |> unmask_mfm_code_elements(segments, open_marker, close_marker)
rescue
_ -> html
catch
_, _ -> html
end
+ # Code elements are replaced by placeholders built from two private-use
+ # codepoints that do not occur in the input, so a placeholder can never
+ # collide with user-provided text.
+ defp mfm_code_markers(html) do
+ used =
+ html
++ |> String.codepoints()
+ |> MapSet.new()
+
+ 0xE000..0xF8FF
+ |> Enum.filter(fn codepoint -> not MapSet.member?(used, <<codepoint::utf8>>) end)
+ |> Enum.take(2)
+ |> case do
+ [open, close] -> {<<open::utf8>>, <<close::utf8>>}
+ _ -> {nil, nil}
+ end
+ end
+
+ defp mask_mfm_code_elements(html, nil, nil), do: {html, []}
+
+ defp mask_mfm_code_elements(html, open_marker, close_marker) do
+ {masked, segments} =
+ @mfm_code_element
+ |> Regex.split(html, include_captures: true)
+ |> Enum.with_index()
+ |> Enum.map_reduce([], fn
+ {part, index}, acc when rem(index, 2) == 1 ->
+ {open_marker <> Integer.to_string(length(acc)) <> close_marker, [part | acc]}
+
+ {part, _index}, acc ->
+ {part, acc}
+ end)
+
+ {IO.iodata_to_binary(masked), Enum.reverse(segments)}
+ end
+
+ defp unmask_mfm_code_elements(html, [], _open_marker, _close_marker), do: html
+
+ defp unmask_mfm_code_elements(html, segments, open_marker, close_marker) do
+ Enum.reduce(Enum.with_index(segments), html, fn {segment, index}, masked ->
+ String.replace(
+ masked,
+ open_marker <> Integer.to_string(index) <> close_marker,
+ segment
+ )
+ end)
+ end
+
def format_naive_asctime(date) do
date |> DateTime.from_naive!("Etc/UTC") |> format_asctime
end
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..5bb9f8181 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
@@ -178,6 +178,33 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidatorTest
refute content =~ "<script"
end
+ test "a Misskey MFM note does not render MFM inside code elements" do
+ user = insert(:user, ap_id: "https://misskey.example/users/code")
+
+ note = %{
+ "id" => "https://misskey.example/notes/6",
+ "type" => "Note",
+ "actor" => user.ap_id,
+ "attributedTo" => user.ap_id,
+ "to" => ["https://www.w3.org/ns/activitystreams#Public"],
+ "cc" => [],
+ "content" => "original content",
+ "context" => Utils.generate_context_id(),
+ "source" => %{
+ "content" => "$[x2 big] `$[x2 small]`\n\n```\n$[spin.speed=1s block]\n```",
+ "mediaType" => "text/x.misskeymarkdown"
+ }
+ }
+
+ %{valid?: true, changes: %{content: content}} =
+ ArticleNotePageValidator.cast_and_validate(note)
+
+ assert content =~ ~s(<span class="mfm-x2">big</span>)
+ assert content =~ "<code class=\"inline\">$[x2 small]</code>"
+ assert content =~ "<pre><code>$[spin.speed=1s block]</code></pre>"
+ refute content =~ "mfm-spin"
+ end
+
test "a Misskey MFM note resolves only cached AP mention tags" do
remote_user = insert(:user, ap_id: "https://misskey.example/users/carol")
local_user = insert(:user, nickname: "local_user")
diff --git a/test/pleroma/web/common_api/utils_test.exs b/test/pleroma/web/common_api/utils_test.exs
index d0cbc3111..589e8f4d5 100644
--- a/test/pleroma/web/common_api/utils_test.exs
+++ b/test/pleroma/web/common_api/utils_test.exs
@@ -161,6 +161,26 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do
assert output ==
~s(<p><strong>hello world</strong></p><p><em>another <span class="h-card"><a class="u-url mention" data-user="#{user.id}" href="http://foo.com/user__test" rel="ugc">@<span>user__test</span></a></span> and <span class="h-card"><a class="u-url mention" data-user="#{user.id}" href="http://foo.com/user__test" rel="ugc">@<span>user__test</span></a></span> <a href="http://google.com" rel="ugc">google.com</a> paragraph</em></p>)
end
+
+ test "does not render MFM inside code elements" do
+ text = "$[x2 big] `$[x2 small]`\n\n```\n$[spin.speed=1s block]\n```"
+
+ {output, [], []} = Utils.format_input(text, "text/x.misskeymarkdown")
+
+ assert output =~ ~s(<span class="mfm-x2">big</span>)
+ assert output =~ "<code class=\"inline\">$[x2 small]</code>"
+ assert output =~ "<pre><code>$[spin.speed=1s block]</code></pre>"
+ refute output =~ "mfm-spin"
+ end
+
+ test "MFM inside code elements does not break MFM outside of them" do
+ text = "```\n$[x2 unclosed\n```\n\n$[flip flipped]"
+
+ {output, [], []} = Utils.format_input(text, "text/x.misskeymarkdown")
+
+ assert output =~ ~s(<span class="mfm-flip">flipped</span>)
+ assert output =~ "$[x2 unclosed"
+ end
end
describe "format_input/3 with markdown" do
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sat, Aug 29, 10:59 PM (1 d, 15 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1737563
Default Alt Text
(6 KB)
Attached To
Mode
rPUBE pleroma-upstream
Attached
Detach File
Event Timeline
Log In to Comment