defmodule EctoSparkles.SanitiseStrings do
@moduledoc """
Provides functions for sanitising input on `Ecto.Changeset` string fields.
"""
@doc """
Sanitises all changes in the given changeset that apply to field which are of the `:string` `Ecto` type.
By default it uses the `HtmlSanitizeEx.strip_tags/1` function on any change that satisfies all of the following conditions:
1. The field associated with the change is of the type `:string`.
2. The field associated with the change is not in the blacklisted_fields list of `opts` as defined using the `:except` key in `opts`.
Note that this function will change the value in the `:changes` map of an
`%Ecto.Changeset{}` struct if the given changes are sanitized.
## Examples
iex> attrs = %{string_field: "<script>Bad</script>"}
iex> result_changeset =
...> attrs
...> |> FakeEctoSchema.changeset()
...> |> EctoSparkles.SanitiseStrings.strip_all_tags()
iex> result_changeset.changes
%{string_field: "Bad"}
Fields can be exempted from sanitization via the `:except` option.
iex> attrs = %{string_field: "<script>Bad</script>"}
iex> result_changeset =
...> attrs
...> |> FakeEctoSchema.changeset()
...> |> EctoSparkles.SanitiseStrings.strip_all_tags(except: [:string_field])
iex> result_changeset.changes
%{string_field: "<script>Bad</script>"}
### You can also specify a specific scrubber (by passing a function as reference):
ies> attrs
...> |> FakeEctoSchema.changeset()
...> |> EctoSparkles.SanitiseStrings.sanitise_strings(scrubber: HtmlSanitizeEx.Scrubber.html5/1)
"""
def strip_all_tags(%Ecto.Changeset{} = changeset, opts \\ []) do
sanitise_strings(
changeset,
opts ++ [scrubber: &HtmlSanitizeEx.strip_tags/1]
)
end
def clean_html(%Ecto.Changeset{} = changeset, opts \\ []) do
sanitise_strings(
changeset,
opts ++ [scrubber: &HtmlSanitizeEx.markdown_html/1]
)
end
def sanitise_strings(%Ecto.Changeset{} = changeset, opts \\ []) do
blacklisted_fields = Keyword.get(opts, :except, [])
scrubber = Keyword.get(opts, :scrubber, &HtmlSanitizeEx.strip_tags/1)
sanitized_changes =
Enum.into(changeset.changes, %{}, fn change ->
scrub_change(change, blacklisted_fields, changeset.types, scrubber)
end)
%{changeset | changes: sanitized_changes}
end
defp scrub_change({field, value}, blacklisted_fields, types, scrubber)
when is_binary(value) do
if field in blacklisted_fields do
{field, value}
else
if Map.get(types, field) == :string do
{field, scrubber.(value)}
else
{field, value}
end
end
end
defp scrub_change(change, _, _, _), do: change
end