diff --git a/lib/html_sanitize_ex/scrubber/html5.ex b/lib/html_sanitize_ex/scrubber/html5.ex index fb1e3a0..a4c0363 100644 --- a/lib/html_sanitize_ex/scrubber/html5.ex +++ b/lib/html_sanitize_ex/scrubber/html5.ex @@ -2123,6 +2123,7 @@ defmodule HtmlSanitizeEx.Scrubber.HTML5 do Meta.allow_tags_with_style_attributes([ "a", "blockquote", + "body", "br", "code", "del", @@ -2181,6 +2182,7 @@ defmodule HtmlSanitizeEx.Scrubber.HTML5 do "sub", "summary", "sup", + "svg", "table", "tbody", "td", @@ -2199,6 +2201,51 @@ defmodule HtmlSanitizeEx.Scrubber.HTML5 do "wbr" ]) + # ---------- SVG shapes & groups ----------------------------------- + @shape_attrs ~w(fill stroke stroke-width stroke-linecap stroke-linejoin class transform) + @geo_attrs ~w(d x y cx cy r x1 y1 x2 y2 points) + + @svg_ns "http://www.w3.org/2000/svg" + @xlink_ns "http://www.w3.org/1999/xlink" + @svg_root_attrs ~w(viewbox width height fill stroke stroke-width class role aria-label) + + defp keep(attrs, whitelist) do + Enum.filter(attrs, fn {n, _} -> n in whitelist end) + |> Enum.filter(fn {n, v} -> + (n !== "fill" and n !== "stroke") or safe_colour?(v) + end) + end + + # Reject gradients / external refs in colour attributes + defp safe_colour?(v) do + not String.starts_with?(v, "url(") and + not String.contains?(v, "javascript:") and + byte_size(v) < 64 + end + + def scrub({"svg", attrs, kids}) do + {"svg", + Enum.filter(attrs, fn + {"xmlns", @svg_ns} -> true + {"xmlns:xlink", @xlink_ns} -> true + {"fill", v} -> safe_colour?(v) + {"stroke", v} -> safe_colour?(v) + {n, _} when n in @svg_root_attrs -> true + _ -> false + end), Enum.map(kids, &scrub/1)} + end + + def scrub({"g", attrs, kids}), + do: {"g", keep(attrs, @shape_attrs), Enum.map(kids, &scrub/1)} + + def scrub({"path", attrs, _}), + do: {"path", keep(attrs, @shape_attrs ++ ["d"]), []} + + for tag <- ~w(rect circle line polyline polygon) do + def scrub({unquote(tag), attrs, _kids}), + do: {unquote(tag), keep(attrs, @shape_attrs ++ @geo_attrs), []} + end + # style tags def scrub({"style", attributes, [text]}) do diff --git a/test/html5_test.exs b/test/html5_test.exs index 9bd9662..397bdc5 100644 --- a/test/html5_test.exs +++ b/test/html5_test.exs @@ -125,6 +125,45 @@ defmodule HtmlSanitizeExScrubberHTML5Test do assert input == full_html_sanitize(input) end + test "does not strip valid attributes from svg and shapes" do + icon = + ~s( + + ) + + assert icon == full_html_sanitize(icon) + end + + test "strip unsafe colours for fill and stroke in svg" do + evil_hero_icon = + ~s| + + | + + good_hero_icon = + ~s| + + | + + assert good_hero_icon == full_html_sanitize(evil_hero_icon) + end + + test "strip unsafe colours for fill and stroke in rect, circle, line, polyline, polygon, path, g" do + for tag <- ~w(rect circle line polyline polygon path g) do + evil_hero_icon = + ~s| + <#{tag} fill="url(javascript:alert(1))" stroke="url(javascript:alert(1))" stroke-linecap="round" stroke-linejoin="round"> + | + + good_hero_icon = + ~s| + <#{tag} stroke-linecap="round" stroke-linejoin="round"> + | + + assert good_hero_icon == full_html_sanitize(evil_hero_icon) + end + end + test "make sure a very long URI is truncated before capturing URI scheme" do input = ""