diff --git a/lib/mdex.ex b/lib/mdex.ex index 25ef81c..1a49968 100644 --- a/lib/mdex.ex +++ b/lib/mdex.ex @@ -603,11 +603,38 @@ defmodule MDEx do defp maybe_trim({:ok, result}), do: {:ok, String.trim(result)} defp maybe_trim(error), do: error - # TODO: spec/docs - def safe_html(unsafe_html, opts) do - sanitize = Keyword.get(opts, :sanitize, true) - escape_tags = Keyword.get(opts, :escape_tags, true) - escape_curly_braces_in_code = Keyword.get(opts, :escape_curly_braces_in_code, true) - Native.safe_html(unsafe_html, sanitize, escape_tags, escape_curly_braces_in_code) + @doc """ + Utility function to sanitize and escape HTML. + + ## Example + + iex> MDEx.safe_html("") + "" + + iex> MDEx.safe_html("Hello") + "<span>Hello</span>" + + iex> MDEx.safe_html("
{:ok, 'MDEx'}
")
+ "<h1>{'Example:'}</h1><code>{:ok, 'MDEx'}</code>"
+
+ ## Options
+
+ - `:sanitize` - clean HTML using these rules https://docs.rs/ammonia/latest/ammonia/fn.clean.html. Defaults to `true`.
+ - `:escape` - which entities should be escaped. Defaults to `[:content, :curly_braces_in_code]`.
+ `:content` - escape common chars like `<`, `>`, `&`, and others in the HTML content;
+ `:curly_braces_in_code` - escape `{` and `}` only inside `` tags, particularly useful for compiling HTML in LiveView;
+ """
+ def safe_html(unsafe_html, opts \\ []) when is_binary(unsafe_html) and is_list(opts) do
+ sanitize = opt(opts, [:sanitize], true)
+ escape_content = opt(opts, [:escape, :content], true)
+ escape_curly_braces_in_code = opt(opts, [:escape, :curly_braces_in_code], true)
+ Native.safe_html(unsafe_html, sanitize, escape_content, escape_curly_braces_in_code)
+ end
+
+ defp opt(opts, keys, default) do
+ case get_in(opts, keys) do
+ nil -> default
+ val -> val
+ end
end
end
diff --git a/native/comrak_nif/src/lib.rs b/native/comrak_nif/src/lib.rs
index c25608e..911d660 100644
--- a/native/comrak_nif/src/lib.rs
+++ b/native/comrak_nif/src/lib.rs
@@ -382,18 +382,16 @@ fn do_safe_html(
false => unsafe_html,
};
- let html = match escape_tags {
- true => v_htmlescape::escape(&html).to_string(),
- false => html,
- };
-
let html = match escape_curly_braces_in_code {
true => rewrite_str(
&html,
RewriteStrSettings {
- element_content_handlers: vec![text!("code", |t| {
- t.replace(
- &t.as_str().replace('{', "{").replace('}', "}"),
+ element_content_handlers: vec![text!("code", |chunk| {
+ chunk.replace(
+ &chunk
+ .as_str()
+ .replace('{', "{")
+ .replace('}', "}"),
ContentType::Html,
);
@@ -406,5 +404,12 @@ fn do_safe_html(
false => html,
};
- html
+ let html = match escape_tags {
+ true => v_htmlescape::escape(&html).to_string(),
+ false => html,
+ };
+
+ // TODO: not so clean solution to undo double escaping, could be better
+ html.replace("{", "{")
+ .replace("}", "}")
}
diff --git a/test/mdex_test.exs b/test/mdex_test.exs
index 1acacb8..d3ab85e 100644
--- a/test/mdex_test.exs
+++ b/test/mdex_test.exs
@@ -231,19 +231,29 @@ defmodule MDExTest do
test "sanitize" do
assert MDEx.safe_html("tag",
sanitize: true,
- escape_tags: false,
- escape_curly_braces_in_code: false
+ escape: [content: false, curly_braces_in_code: false]
) == "tag"
end
test "escape tags" do
- assert MDEx.safe_html("tag", sanitize: false, escape_tags: true, escape_curly_braces_in_code: false) ==
- "<span>tag</span>"
+ assert MDEx.safe_html("content",
+ sanitize: false,
+ escape: [content: true, curly_braces_in_code: false]
+ ) == "<span>content</span>"
end
test "escape curly braces in code tags" do
- assert MDEx.safe_html("{test}
{:foo}
", sanitize: false, escape_tags: false, escape_curly_braces_in_code: true) ==
- "{test}
{:foo}
"
+ assert MDEx.safe_html("{test}
{:foo}
",
+ sanitize: false,
+ escape: [content: false, curly_braces_in_code: true]
+ ) == "{test}
{:foo}
"
+ end
+
+ test "enable all by default" do
+ assert MDEx.safe_html(
+ "{:example} {:ok, 'foo'}
"
+ ) ==
+ "<span>{:example} <code>{:ok, 'foo'}</code></span>"
end
end