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 [https://github.com/philss/floki](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.
"""
@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.
"""
@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(string) when is_binary(string), do: string
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
## 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