defmodule StripJs do @moduledoc ~S""" StripJs is an Elixir module for stripping executable JavaScript from blocks of HTML. It removes `` and `` tags are removed entirely. * `` is converted to ``. * Event handler attributes such as `onclick="..."` are converted to e.g., `data-onclick="..."`. ## Installation Add `strip_js` to your application's dependencies in `mix.exs`: def deps do [{:strip_js, "~> 0.1.0"}] end ## Usage `strip_js/1` returns a copy of its input, with all JS removed. iex> html = "" iex> StripJs.strip_js(html) "" `strip_js_with_status/1` performs the same function as `strip_js/1`, also returning a boolean indicating whether any JS was removed from the input. iex> html = "" iex> StripJs.strip_js_with_status(html) {"", true} StripJs relies on the [Floki](https://github.com/philss/floki) HTML parser library. StripJs provides a `strip_js_from_tree/1` function to strip JS from Floki HTML parse trees. """ @doc ~S""" Returns a copy of the given HTML string with all JS removed. Even if the input HTML contained no JS, it may not match the output byte-for-byte. """ @spec strip_js(String.t) :: String.t def strip_js(html) when is_binary(html) do html |> Floki.parse |> strip_js_from_tree |> Floki.raw_html end @doc ~S""" Returns a tuple containing a copy of the given HTML string with all JS removed, as well as a boolean that is `true` when there was JS present in the original HTML and `false` otherwise. Even if the input HTML contained no JS, it may not match the output byte-for-byte. """ @spec strip_js_with_status(String.t) :: {String.t, boolean} def strip_js_with_status(html) when is_binary(html) do tree = html |> Floki.parse stripped_tree = tree |> strip_js_from_tree {Floki.raw_html(stripped_tree), (tree != stripped_tree)} end @doc ~S""" Returns a copy of the given Floki HTML tree with all JS removed. """ @spec strip_js_from_tree(Floki.html_tree) :: Floki.html_tree def strip_js_from_tree(tree) def strip_js_from_tree({tag, attrs, children}) do case String.downcase(tag) do "script" -> "" # remove scripts entirely _ -> {tag, clean_attrs(attrs), Enum.map(children, &strip_js_from_tree/1)} end end def strip_js_from_tree(string) when is_binary(string), do: string ## Removes attributes that carry JS; namely `href="javascript:..."` and ## `onevent="..."` handlers (onclick, onchange, etc). @spec clean_attrs([{String.t, String.t}]) :: [{String.t, String.t}] defp clean_attrs(attrs) do rev_attrs = Enum.reduce attrs, [], fn ({attr, value}, acc) -> case String.downcase(attr) do ## ... becomes ## ... "href" -> case String.downcase(value) do "javascript:" <> rest -> [{attr, "#"}, {"data-href-javascript", rest} | acc] _ -> [{attr, value} | acc] end ## ... becomes ## ... "on" <> event -> [{"data-on#{event}", value} | acc] _ -> [{attr, value} | acc] end end rev_attrs |> Enum.reverse end end