fix(deps): replace mdex with earmark parser
This commit is contained in:
parent
77a7ba3a0e
commit
bf477c1a53
7 changed files with 209 additions and 69 deletions
|
|
@ -27,7 +27,6 @@ variables:
|
|||
- ELIXIR_TAG=1.17.3-otp-26-alpine@sha256:1671da02865cadad4cdb655c6670a50f0bfc9ead0a3896bd52c673e685f8ad1c
|
||||
- ALPINE_IMG=arm32v7/alpine
|
||||
- ALPINE_VER=3.24.1
|
||||
- MDEX_NATIVE_BUILD=true
|
||||
extra_opts: --custom-platform=linux/arm/v7
|
||||
armv7_push_settings: &armv7_push_settings
|
||||
<<: *armv7_build_settings
|
||||
|
|
|
|||
|
|
@ -8,16 +8,12 @@ ARG ALPINE_IMG=alpine
|
|||
|
||||
FROM ${ELIXIR_IMG}:${ELIXIR_TAG} AS build
|
||||
|
||||
ARG MDEX_NATIVE_BUILD=false
|
||||
|
||||
COPY . .
|
||||
|
||||
ENV MIX_ENV=prod
|
||||
ENV MDEX_NATIVE_BUILD=${MDEX_NATIVE_BUILD}
|
||||
ENV VIX_COMPILATION_MODE=PLATFORM_PROVIDED_LIBVIPS
|
||||
|
||||
RUN apk add git gcc g++ musl-dev make cmake file-dev vips-dev &&\
|
||||
case "${MDEX_NATIVE_BUILD}" in true|1) apk add cargo ;; esac &&\
|
||||
echo "import Config" > config/prod.secret.exs &&\
|
||||
mix local.hex --force &&\
|
||||
mix local.rebar --force &&\
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Replace retired Earmark markdown rendering with MDEx to address stored XSS in link attributes.
|
||||
Replace retired Earmark rendering with the maintained EarmarkParser and an attribute-escaping renderer.
|
||||
|
|
|
|||
|
|
@ -3,12 +3,15 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Formatter do
|
||||
require Logger
|
||||
|
||||
alias Pleroma.HTML
|
||||
alias Pleroma.User
|
||||
|
||||
@safe_mention_regex ~r/^(\s*(?<mentions>(@.+?\s+){1,})+)(?<rest>.*)/s
|
||||
@link_regex ~r"((?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~%:/?#[\]@!\$&'\(\)\*\+,;=.]+)|[0-9a-z+\-\.]+:[0-9a-z$-_.+!*'(),]+"ui
|
||||
@markdown_characters_regex ~r/(`|\*|_|{|}|[|]|\(|\)|#|\+|-|\.|!)/
|
||||
@markdown_void_elements ~w[area base br col command embed hr img input keygen link meta param source track wbr]
|
||||
|
||||
defp linkify_opts do
|
||||
Pleroma.Config.get(Pleroma.Formatter) ++
|
||||
|
|
@ -129,81 +132,147 @@ 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(
|
||||
markdown: text,
|
||||
extension: [autolink: true, strikethrough: true],
|
||||
parse: [smart: false],
|
||||
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!()
|
||||
|> compact_markdown_html(text)
|
||||
{_status, ast, _messages} = EarmarkParser.as_ast(text, breaks: breaks, gfm: true)
|
||||
|
||||
ast
|
||||
|> maybe_strip_email_autolinks(text, Map.get(opts, :strip_email_autolinks, false))
|
||||
|> render_markdown_nodes(false)
|
||||
|> IO.iodata_to_binary()
|
||||
rescue
|
||||
exception ->
|
||||
Logger.warning("Markdown rendering failed in #{inspect(exception.__struct__)}")
|
||||
markdown_fallback(text, Map.get(opts, :breaks, false))
|
||||
end
|
||||
|
||||
defp compact_markdown_html(html, markdown) do
|
||||
html
|
||||
|> String.trim_trailing("\n")
|
||||
|> String.replace(~r/>\n\s*</, "><")
|
||||
|> String.replace(~r/\n<\/code><\/pre>/, "</code></pre>")
|
||||
|> String.replace(~r/(?<!<pre>)<code>/, ~s(<code class="inline">))
|
||||
|> String.replace("<hr />", "<hr/>")
|
||||
|> unwrap_raw_html_paragraph(markdown)
|
||||
defp markdown_fallback(text, breaks) do
|
||||
text = text |> escape_html() |> IO.iodata_to_binary() |> maybe_render_markdown_breaks(breaks)
|
||||
"<p>" <> text <> "</p>"
|
||||
end
|
||||
|
||||
defp normalize_markdown_quirks(text) do
|
||||
String.replace(text, ~r/~~([^~\n]+)~~~/, "~~\\1~~\\\\~")
|
||||
defp maybe_render_markdown_breaks(text, true), do: String.replace(text, ~r/\r?\n/, "<br>")
|
||||
defp maybe_render_markdown_breaks(text, false), do: text
|
||||
|
||||
defp maybe_strip_email_autolinks(ast, _text, false), do: ast
|
||||
|
||||
defp maybe_strip_email_autolinks(ast, text, true) do
|
||||
emails =
|
||||
~r/<([^ >]+@[^ >]+)>/
|
||||
|> Regex.scan(text, capture: :all_but_first)
|
||||
|> Enum.map(fn [email] -> String.replace_prefix(email, "mailto:", "") end)
|
||||
|> MapSet.new()
|
||||
|
||||
strip_email_autolinks(ast, emails)
|
||||
end
|
||||
|
||||
defp unwrap_raw_html_paragraph(html, markdown) do
|
||||
if inline_raw_html_only?(markdown) do
|
||||
String.replace(html, ~r/\A<p>(.*)<\/p>\z/s, "\\1")
|
||||
defp strip_email_autolinks(nodes, emails) when is_list(nodes) do
|
||||
Enum.map(nodes, &strip_email_autolinks(&1, emails))
|
||||
end
|
||||
|
||||
defp strip_email_autolinks({"a", attrs, [email], _meta} = node, emails) do
|
||||
if List.keyfind(attrs, "href", 0) == {"href", "mailto:" <> email} and
|
||||
MapSet.member?(emails, email) do
|
||||
email
|
||||
else
|
||||
html
|
||||
node
|
||||
end
|
||||
end
|
||||
|
||||
defp inline_raw_html_only?(markdown) do
|
||||
markdown = String.trim(markdown)
|
||||
|
||||
String.match?(
|
||||
markdown,
|
||||
~r/\A(?:<!--|<\/?(?:a|abbr|acronym|b|bdi|bdo|big|br|cite|code|data|del|dfn|em|i|img|ins|kbd|mark|q|rb|rp|rt|rtc|ruby|s|samp|small|span|strong|sub|sup|time|tt|u|var|wbr)(?:\s|>|\/>))/i
|
||||
) &&
|
||||
String.ends_with?(markdown, ">")
|
||||
defp strip_email_autolinks({tag, attrs, children, meta}, emails) do
|
||||
{tag, attrs, strip_email_autolinks(children, emails), meta}
|
||||
end
|
||||
|
||||
defp maybe_strip_email_autolinks(document, true) do
|
||||
MDEx.Document.update_nodes(document, MDEx.Link, &strip_email_autolink/1)
|
||||
defp strip_email_autolinks(text, _emails) when is_binary(text), do: text
|
||||
|
||||
defp render_markdown_nodes(nodes, verbatim) when is_list(nodes) do
|
||||
Enum.map(nodes, &render_markdown_node(&1, verbatim))
|
||||
end
|
||||
|
||||
defp maybe_strip_email_autolinks(document, _), do: document
|
||||
defp render_markdown_node(text, true) when is_binary(text),
|
||||
do: strip_dangerous_markdown_html(text)
|
||||
|
||||
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
|
||||
defp render_markdown_node(text, false) when is_binary(text) do
|
||||
text
|
||||
|> strip_dangerous_markdown_html()
|
||||
|> escape_markdown_html()
|
||||
end
|
||||
|
||||
defp strip_email_autolink(link), do: link
|
||||
defp render_markdown_node({:comment, _attrs, _children, _meta}, _verbatim), do: []
|
||||
|
||||
defp strip_dangerous_raw_html(%{literal: literal} = node) do
|
||||
literal =
|
||||
literal
|
||||
defp render_markdown_node({tag, attrs, children, meta}, verbatim) do
|
||||
normalized_tag = String.downcase(tag)
|
||||
|
||||
if normalized_tag in ["script", "style"] do
|
||||
[]
|
||||
else
|
||||
render_markdown_element(tag, normalized_tag, attrs, children, meta, verbatim)
|
||||
end
|
||||
end
|
||||
|
||||
defp render_markdown_element("code", _normalized_tag, attrs, children, _meta, _verbatim) do
|
||||
["<code", render_markdown_attrs(attrs), ">", render_markdown_code(children), "</code>"]
|
||||
end
|
||||
|
||||
defp render_markdown_element(tag, "hr", attrs, _children, _meta, _verbatim) do
|
||||
attrs = Enum.reject(attrs, &match?({"class", class} when class in ~w[thin medium thick], &1))
|
||||
["<", tag, render_markdown_attrs(attrs), "/>"]
|
||||
end
|
||||
|
||||
defp render_markdown_element(tag, normalized_tag, attrs, _children, _meta, _verbatim)
|
||||
when normalized_tag in @markdown_void_elements do
|
||||
["<", tag, render_markdown_attrs(attrs), ">"]
|
||||
end
|
||||
|
||||
defp render_markdown_element(tag, _normalized_tag, attrs, children, meta, verbatim) do
|
||||
children_verbatim = verbatim or Map.get(meta, :verbatim, false)
|
||||
|
||||
[
|
||||
"<",
|
||||
tag,
|
||||
render_markdown_attrs(attrs),
|
||||
">",
|
||||
render_markdown_nodes(children, children_verbatim),
|
||||
"</",
|
||||
tag,
|
||||
">"
|
||||
]
|
||||
end
|
||||
|
||||
defp render_markdown_attrs(attrs) do
|
||||
Enum.map(attrs, fn {name, value} ->
|
||||
value = value |> to_string() |> HtmlEntities.decode() |> sanitize_markdown_url(name)
|
||||
[" ", to_string(name), "=\"", escape_html(value), "\""]
|
||||
end)
|
||||
end
|
||||
|
||||
defp render_markdown_code(children) do
|
||||
Enum.map(children, &escape_html/1)
|
||||
end
|
||||
|
||||
defp sanitize_markdown_url(value, name) do
|
||||
if String.downcase(to_string(name)) in ["href", "src"] do
|
||||
value
|
||||
|> String.split(~r/["<>]/, parts: 2)
|
||||
|> hd()
|
||||
else
|
||||
value
|
||||
end
|
||||
end
|
||||
|
||||
defp strip_dangerous_markdown_html(text) do
|
||||
text
|
||||
|> String.replace(~r/<(script|style)\b[^>]*>.*?<\/\1>/is, "")
|
||||
|> String.replace(~r/<(script|style)\b[^>]*>.*\z/is, "")
|
||||
|> String.replace(~r/<\/(script|style)\s*>/i, "")
|
||||
end
|
||||
|
||||
%{node | literal: literal}
|
||||
defp escape_markdown_html(text) do
|
||||
text |> HtmlEntities.decode() |> escape_html()
|
||||
end
|
||||
|
||||
defp escape_html(text) do
|
||||
{:safe, escaped} = Phoenix.HTML.html_escape(text)
|
||||
escaped
|
||||
end
|
||||
|
||||
def html_escape({text, mentions, hashtags}, type) do
|
||||
|
|
|
|||
3
mix.exs
3
mix.exs
|
|
@ -155,8 +155,7 @@ defmodule Pleroma.Mixfile do
|
|||
{:ex_aws, "~> 2.1.9"},
|
||||
{:ex_aws_s3, "~> 2.5"},
|
||||
{:sweet_xml, "~> 0.7.5"},
|
||||
{:mdex, "~> 0.13.2"},
|
||||
{:rustler, "~> 0.32", optional: true},
|
||||
{:earmark_parser, "~> 1.4.46"},
|
||||
{:bbcode_pleroma, "~> 0.2.0"},
|
||||
{:nimble_parsec, "~> 1.4", override: true},
|
||||
{:pleroma_mfm_parser, "~> 0.2.1"},
|
||||
|
|
|
|||
7
mix.lock
7
mix.lock
|
|
@ -30,7 +30,7 @@
|
|||
"decimal": {:hex, :decimal, "3.1.1", "430d87b04011ce6cbd4fd205be758311a81f87d552d40904abd00f015935b1d0", [:mix], [], "hexpm", "c5f25f2ced74a0587d03e6023f595db8e924c9d3922c8c8ffd9edfc4498cf1f6"},
|
||||
"deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"},
|
||||
"dialyxir": {:hex, :dialyxir, "1.4.6", "7cca478334bf8307e968664343cbdb432ee95b4b68a9cba95bdabb0ad5bdfd9a", [:mix], [{:erlex, ">= 0.2.7", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "8cf5615c5cd4c2da6c501faae642839c8405b49f8aa057ad4ae401cb808ef64d"},
|
||||
"earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"},
|
||||
"earmark_parser": {:hex, :earmark_parser, "1.4.46", "67607a0532e810c6f630a515c548d0b24949643f168cc556303bee4cf96105c7", [:mix], [], "hexpm", "9c44636e8a1c68c62f526b2dcd85d941dbbcee7ab82cf64ba06ce28bef8e89f5"},
|
||||
"eblurhash": {:git, "https://github.com/zotonic/eblurhash.git", "bc37ceb426ef021ee9927fb249bb93f7059194ab", [ref: "bc37ceb426ef021ee9927fb249bb93f7059194ab"]},
|
||||
"ecto": {:hex, :ecto, "3.14.0", "2fa64521eebfcb2670d907a86e4ad947290e9933706bb315e6fb5c21b172cb26", [:mix], [{:decimal, "~> 3.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "130d69ffb4285f9ce4792b65dfbb994fd13ea4cbc3cbea2524b199aa3de84af3"},
|
||||
"ecto_enum": {:hex, :ecto_enum, "1.4.0", "d14b00e04b974afc69c251632d1e49594d899067ee2b376277efd8233027aec8", [:mix], [{:ecto, ">= 3.0.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "> 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:mariaex, ">= 0.0.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "8fb55c087181c2b15eee406519dc22578fa60dd82c088be376d0010172764ee4"},
|
||||
|
|
@ -77,8 +77,6 @@
|
|||
"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"},
|
||||
"mdex": {:hex, :mdex, "0.13.2", "0b013e8a01792eb974dd31061c6ec3856092b3a52f3f5d526a0d8acbd9df0873", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:lumis, "~> 0.1", [hex: :lumis, repo: "hexpm", optional: true]}, {:mdex_native, ">= 0.2.3", [hex: :mdex_native, repo: "hexpm", optional: false]}, {:nimble_options, "~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: true]}], "hexpm", "ecbcbde681971396185dfcd83f3d35b8e3bc9f7134f4bf93b4096657ac1ac3a3"},
|
||||
"mdex_native": {:hex, :mdex_native, "0.2.3", "423f650798dada466d9b04700d8ec45e2819fa32af53b70373f0aded29e6c9e6", [:mix], [{:rustler, "~> 0.32", [hex: :rustler, repo: "hexpm", optional: true]}, {:rustler_precompiled, "~> 0.7", [hex: :rustler_precompiled, repo: "hexpm", optional: false]}], "hexpm", "5b150c2121890f75fd1df84cb24e06f58973be4b36ba2ab2a38300cd44dcc00f"},
|
||||
"meck": {:hex, :meck, "0.9.2", "85ccbab053f1db86c7ca240e9fc718170ee5bda03810a6292b5306bf31bae5f5", [:rebar3], [], "hexpm", "81344f561357dc40a8344afa53767c32669153355b626ea9fcbc8da6b3045826"},
|
||||
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
|
||||
"mime": {:hex, :mime, "1.6.0", "dabde576a497cef4bbdd60aceee8160e02a6c89250d6c0b29e56c0dfb00db3d2", [:mix], [], "hexpm", "31a1a8613f8321143dde1dafc36006a17d28d02bdfecb9e95a880fa7aabd19a7"},
|
||||
|
|
@ -136,8 +134,6 @@
|
|||
"ranch": {:hex, :ranch, "2.2.0", "25528f82bc8d7c6152c57666ca99ec716510fe0925cb188172f41ce93117b1b0", [:make, :rebar3], [], "hexpm", "fa0b99a1780c80218a4197a59ea8d3bdae32fbff7e88527d7d8a4787eff4f8e7"},
|
||||
"recon": {:hex, :recon, "2.5.6", "9052588e83bfedfd9b72e1034532aee2a5369d9d9343b61aeb7fbce761010741", [:mix, :rebar3], [], "hexpm", "96c6799792d735cc0f0fd0f86267e9d351e63339cbe03df9d162010cefc26bb0"},
|
||||
"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.38.0", "7a8906998ff0d28e3021c0a73264abcda719bda344b2e58307c6805b0f87c9b4", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "704c03c1bf66be12b031c5a389347b91c81c5cb819a24b068b0de36fe4a5652a"},
|
||||
"rustler_precompiled": {:hex, :rustler_precompiled, "0.9.0", "3a052eda09f3d2436364645cc1f13279cf95db310eb0c17b0d8f25484b233aa0", [:mix], [{:rustler, "~> 0.23", [hex: :rustler, repo: "hexpm", optional: true]}], "hexpm", "471d97315bd3bf7b64623418b3693eedd8e47de3d1cb79a0ac8f9da7d770d94c"},
|
||||
"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"},
|
||||
"statistex": {:hex, :statistex, "1.1.0", "7fec1eb2f580a0d2c1a05ed27396a084ab064a40cfc84246dbfb0c72a5c761e5", [:mix], [], "hexpm", "f5950ea26ad43246ba2cce54324ac394a4e7408fdcf98b8e230f503a0cba9cf5"},
|
||||
|
|
@ -152,7 +148,6 @@
|
|||
"tesla": {:hex, :tesla, "1.20.0", "922b504e186da2ee82990d44e7422435f41b67403908a00340c3a0b23a17aa90", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.13", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, ">= 1.0.0", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.21 or >= 4.0.2 and < 5.0.0-0", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.2", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:mox, "~> 1.0", [hex: :mox, repo: "hexpm", optional: true]}, {:msgpax, "~> 2.3", [hex: :msgpax, repo: "hexpm", optional: true]}, {:opentelemetry_semantic_conventions, "~> 1.27", [hex: :opentelemetry_semantic_conventions, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "3ecb41cb458772332752c3acdfe983e23abb991f5a43cfd69a64e9ea3f4b0061"},
|
||||
"thousand_island": {:hex, :thousand_island, "1.4.3", "2158209580f633be38d43ec4e3ce0a01079592b9657afff9080d5d8ca149a3af", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "6e4ce09b0fd761a58594d02814d40f77daff460c48a7354a15ab353bb998ea0b"},
|
||||
"timex": {:hex, :timex, "3.7.7", "3ed093cae596a410759104d878ad7b38e78b7c2151c6190340835515d4a46b8a", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "0ec4b09f25fe311321f9fc04144a7e3affe48eb29481d7a5583849b6c4dfa0a7"},
|
||||
"toml": {:hex, :toml, "0.7.0", "fbcd773caa937d0c7a02c301a1feea25612720ac3fa1ccb8bfd9d30d822911de", [:mix], [], "hexpm", "0690246a2478c1defd100b0c9b89b4ea280a22be9a7b313a8a058a2408a2fa70"},
|
||||
"trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "bd4fde4c15f3e993a999e019d64347489b91b7a9096af68b2bdadd192afa693f"},
|
||||
"tzdata": {:hex, :tzdata, "1.0.5", "69f1ee029a49afa04ad77801febaf69385f3d3e3d1e4b56b9469025677b89a28", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "55519aa2a99e5d2095c1e61cc74c9be69688f8ab75c27da724eb8279ff402a5a"},
|
||||
"ueberauth": {:hex, :ueberauth, "0.10.8", "ba78fbcbb27d811a6cd06ad851793aaf7d27c3b30c9e95349c2c362b344cd8f0", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "f2d3172e52821375bccb8460e5fa5cb91cfd60b19b636b6e57e9759b6f8c10c1"},
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ defmodule Pleroma.FormatterTest do
|
|||
alias Pleroma.User
|
||||
use Pleroma.DataCase
|
||||
|
||||
import Mock
|
||||
import Pleroma.Factory
|
||||
|
||||
setup_all do
|
||||
|
|
@ -372,4 +373,85 @@ defmodule Pleroma.FormatterTest do
|
|||
|
||||
assert Formatter.html_escape(text, "text/plain") == expected
|
||||
end
|
||||
|
||||
describe "markdown_to_html/2" do
|
||||
test "escapes link attribute values" do
|
||||
markdown = ~s|[click](http://example.com/?a=x" onerror="alert(1))|
|
||||
|
||||
html = Formatter.markdown_to_html(markdown)
|
||||
|
||||
assert html =~ ~s|href="http://example.com/?a=x"|
|
||||
refute html =~ "onerror"
|
||||
end
|
||||
|
||||
test "preserves entities without rendering dangerous raw HTML" do
|
||||
markdown = "safe & sound<script>alert(1)</script>"
|
||||
|
||||
assert Formatter.markdown_to_html(markdown) == "<p>safe & sound</p>"
|
||||
|
||||
assert Formatter.markdown_to_html("<div><script>alert(1)</script>safe</div>") ==
|
||||
"<div>safe</div>"
|
||||
|
||||
assert Formatter.markdown_to_html("<ScRiPt>alert(1)</ScRiPt>") == ""
|
||||
assert Formatter.markdown_to_html("<BR>") == "<BR>"
|
||||
end
|
||||
|
||||
test "escapes dangerous HTML inside code" do
|
||||
markdown = "`<script>&</script>`"
|
||||
|
||||
assert Formatter.markdown_to_html(markdown) ==
|
||||
~s(<p><code class="inline"><script>&amp;</script></code></p>)
|
||||
end
|
||||
|
||||
test "rejects entity-encoded URL delimiters" do
|
||||
markdown = "[click](http://example.com/?a=x"onerror=alert(1))"
|
||||
|
||||
html = Formatter.markdown_to_html(markdown)
|
||||
|
||||
assert html =~ ~s|href="http://example.com/?a=x"|
|
||||
refute html =~ "onerror"
|
||||
end
|
||||
|
||||
test "strips email autolinks without changing explicit mailto links" do
|
||||
opts = %{strip_email_autolinks: true}
|
||||
|
||||
assert Formatter.markdown_to_html("<foo@example.com>", opts) ==
|
||||
"<p>foo@example.com</p>"
|
||||
|
||||
assert Formatter.markdown_to_html("[email](mailto:foo@example.com)", opts) ==
|
||||
~s(<p><a href="mailto:foo@example.com">email</a></p>)
|
||||
end
|
||||
|
||||
test "handles malformed Markdown that crashed older EarmarkParser releases" do
|
||||
assert Formatter.markdown_to_html("\\[{:}") == "<p>[{:}</p>"
|
||||
end
|
||||
|
||||
test "handles a malformed Markdown regression corpus" do
|
||||
markdown_samples = [
|
||||
"- hello\n{:.claass .klass}",
|
||||
"[link](\"",
|
||||
"<script",
|
||||
"</style>",
|
||||
"~~~",
|
||||
"<![CDATA[",
|
||||
<<0>>
|
||||
]
|
||||
|
||||
for markdown <- markdown_samples do
|
||||
html = Formatter.markdown_to_html(markdown)
|
||||
|
||||
assert is_binary(html)
|
||||
refute html =~ <<0>>
|
||||
end
|
||||
end
|
||||
|
||||
test "falls back to escaped text when parsing fails" do
|
||||
markdown = "<script>alert(1)</script>\nnext"
|
||||
|
||||
with_mock EarmarkParser, as_ast: fn _text, _opts -> raise "parser failed" end do
|
||||
assert Formatter.markdown_to_html(markdown, %{breaks: true}) ==
|
||||
"<p><script>alert(1)</script><br>next</p>"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue