...
 
Commits (3)
# This file is responsible for configuring your application
# and its dependencies with the aid of the Mix.Config module.
use Mix.Config
# This configuration is loaded before any dependency and is restricted
# to this project. If another project depends on this project, this
# file won't be loaded nor affect the parent project. For this reason,
# if you want to provide default values for your application for
# 3rd-party users, it should be done in your "mix.exs" file.
# You can configure your application as:
#
# config :fast_sanitize, key: :value
#
# and access this configuration in your application as:
#
# Application.get_env(:fast_sanitize, :key)
#
# You can also configure a 3rd-party app:
#
# config :logger, level: :info
#
# It is also possible to import configuration files, relative to this
# directory. For example, you can emulate configuration per environment
# by uncommenting the line below and defining dev.exs, test.exs and such.
# Configuration from the imported file will override the ones defined
# here (which is why it is important to import them last).
#
# import_config "#{Mix.env()}.exs"
if Mix.env() == :test do
config :logger, level: :warn
end
......@@ -21,7 +21,7 @@ defmodule FastSanitize do
## Example
iex> FastSanitize.basic_html("<h1>hello world</h1><script>alert('xss')</script>")
{:ok, "<h1>hello world</h1>"}
{:ok, "<h1>hello world</h1>alert(&#39;xss&#39;)"}
"""
def basic_html(doc), do: Sanitizer.scrub(doc, FastSanitize.Sanitizer.BasicHTML)
end
defmodule FastSanitize.Fragment do
require Logger
import Plug.HTML, only: [html_escape: 1]
def to_tree(bin) do
with {:html, _, [{:head, _, _}, {:body, _, fragment}]} <-
......@@ -10,23 +10,22 @@ defmodule FastSanitize.Fragment do
end
end
defp build_start_tag(tag, attrs) when length(attrs) == 0, do: "<#{tag}>"
defp build_start_tag(tag, attrs) do
attr_chunks =
Enum.map(attrs, fn {k, v} ->
"#{k}=\"#{v}\""
end)
|> Enum.join(" ")
"<#{tag} #{attr_chunks}>"
defp build_attr_chunks(attrs) do
Enum.map(attrs, fn {k, v} ->
"#{html_escape(k)}=\"#{html_escape(v)}\""
end)
|> Enum.join(" ")
end
defp build_start_tag(tag, attrs, nil), do: "<#{tag} #{build_attr_chunks(attrs)}/>"
defp build_start_tag(tag, attrs, _children) when length(attrs) == 0, do: "<#{tag}>"
defp build_start_tag(tag, attrs, _children), do: "<#{tag} #{build_attr_chunks(attrs)}>"
# empty tuple - fragment was clobbered, return nothing
defp fragment_to_html({}), do: ""
# text node
defp fragment_to_html(text) when is_binary(text), do: text
defp fragment_to_html(text) when is_binary(text), do: html_escape(text)
# comment node
defp fragment_to_html({:comment, _, text}), do: "<!-- #{text} -->"
......@@ -38,11 +37,11 @@ defmodule FastSanitize.Fragment do
end
# a node which can never accept children will have nil instead of a subtree
defp fragment_to_html({tag, attrs, nil}), do: build_start_tag(tag, attrs)
defp fragment_to_html({tag, attrs, nil}), do: build_start_tag(tag, attrs, nil)
# every other case, assume a subtree
defp fragment_to_html({tag, attrs, subtree}) do
with start_tag <- build_start_tag(tag, attrs),
with start_tag <- build_start_tag(tag, attrs, subtree),
end_tag <- "</#{tag}>",
{:ok, subtree} <- subtree_to_html(subtree) do
[start_tag, subtree, end_tag]
......
defmodule FastSanitize.Sanitizer do
require Logger
alias FastSanitize.Fragment
@moduledoc """
......@@ -21,18 +23,41 @@ defmodule FastSanitize.Sanitizer do
@callback scrub(binary()) :: binary()
# fallbacks
def scrub("", _), do: ""
def scrub(nil, _), do: ""
def scrub(doc, scrubber) do
with {:ok, subtree} <- Fragment.to_tree(doc) do
Enum.map(subtree, fn fragment ->
scrubber.scrub(fragment)
end)
def scrub("", _), do: {:ok, ""}
def scrub(nil, _), do: {:ok, ""}
def scrub(doc, scrubber) when is_binary(doc) do
with wrapped_doc <- "<body>" <> doc <> "</body>",
{:ok, subtree} <- Fragment.to_tree(wrapped_doc) do
scrub(subtree, scrubber)
|> Fragment.to_html()
else
e ->
{:error, e}
end
end
def scrub(subtree, scrubber) when is_list(subtree) do
Logger.debug("Pre-process: #{inspect(subtree)}")
Enum.map(subtree, fn fragment ->
case scrubber.scrub(fragment) do
{tag, attrs, nil} ->
Logger.debug("Post-process closure: #{inspect({tag, attrs, nil})}")
{tag, attrs, nil}
{tag, attrs, children} ->
Logger.debug("Post-process tag: #{inspect({tag, attrs, children})}")
{tag, attrs, scrub(children, scrubber)}
subtree when is_list(subtree) ->
Logger.debug("Post-process subtree: #{inspect(subtree)}")
scrub(subtree, scrubber)
other ->
Logger.debug("Post-process other: #{inspect(other)}")
other
end
end)
end
end
......@@ -47,7 +47,5 @@ defmodule FastSanitize.Sanitizer.BasicHTML do
Meta.allow_tag_with_these_attributes(:u, [])
Meta.allow_tag_with_these_attributes(:ul, [])
Meta.strip_children_of(:script)
Meta.strip_everything_not_covered()
end
......@@ -110,7 +110,7 @@ defmodule FastSanitize.Sanitizer.Meta do
"""
defmacro strip_comments do
quote do
def scrub({:comment, _, _}), do: ""
def scrub({:comment, _, _}), do: nil
end
end
......
......@@ -21,6 +21,7 @@ defmodule FastSanitize.MixProject do
# Run "mix help deps" to learn about dependencies.
defp deps do
[
{:plug, "~> 1.8"},
{:myhtmlex, "~> 0.2"},
{:credo, "~> 1.0.0", only: [:dev, :test], runtime: false},
{:ex_doc, "~> 0.19", only: :dev, runtime: false},
......
%{
"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm"},
"credo": {:hex, :credo, "1.0.5", "fdea745579f8845315fe6a3b43e2f9f8866839cfbc8562bb72778e9fdaa94214", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm"},
"dialyxir": {:hex, :dialyxir, "1.0.0-rc.6", "78e97d9c0ff1b5521dd68041193891aebebce52fc3b93463c0a6806874557d7d", [:mix], [{:erlex, "~> 0.2.1", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm"},
"earmark": {:hex, :earmark, "1.3.2", "b840562ea3d67795ffbb5bd88940b1bed0ed9fa32834915125ea7d02e35888a5", [:mix], [], "hexpm"},
"erlex": {:hex, :erlex, "0.2.1", "cee02918660807cbba9a7229cae9b42d1c6143b768c781fa6cee1eaf03ad860b", [:mix], [], "hexpm"},
"ex_doc": {:hex, :ex_doc, "0.20.2", "1bd0dfb0304bade58beb77f20f21ee3558cc3c753743ae0ddbb0fd7ba2912331", [:mix], [{:earmark, "~> 1.3", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.10", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"},
"jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"},
"makeup": {:hex, :makeup, "0.8.0", "9cf32aea71c7fe0a4b2e9246c2c4978f9070257e5c9ce6d4a28ec450a839b55f", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"},
"makeup_elixir": {:hex, :makeup_elixir, "0.13.0", "be7a477997dcac2e48a9d695ec730b2d22418292675c75aa2d34ba0909dcdeda", [:mix], [{:makeup, "~> 0.8", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"},
"mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm"},
"myhtmlex": {:hex, :myhtmlex, "0.2.1", "d6f3eb1826f7cdaa0225a996569da0930d1a334405510845c905ae59295ab226", [:make, :mix], [{:nodex, "~> 0.1.1", [hex: :nodex, repo: "hexpm", optional: false]}], "hexpm"},
"nimble_parsec": {:hex, :nimble_parsec, "0.5.0", "90e2eca3d0266e5c53f8fbe0079694740b9c91b6747f2b7e3c5d21966bba8300", [:mix], [], "hexpm"},
"nodex": {:hex, :nodex, "0.1.1", "ed2f7bbe19ea62a43ad4b7ad332eb3f9ca12c64a35a5802a0eb545b93ebe32af", [:mix], [], "hexpm"},
"plug": {:hex, :plug, "1.8.0", "9d2685cb007fe5e28ed9ac27af2815bc262b7817a00929ac10f56f169f43b977", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm"},
"plug_crypto": {:hex, :plug_crypto, "1.0.0", "18e49317d3fa343f24620ed22795ec29d4a5e602d52d1513ccea0b07d8ea7d4d", [:mix], [], "hexpm"},
}
This diff is collapsed.
......@@ -17,7 +17,7 @@ defmodule FastSanitize.Fragment.Test do
test "it works for simple fragment trees with atypical tags" do
tree = [{:br, [], nil}, {:hr, [], nil}]
{:ok, "<br><hr>"} = FastSanitize.Fragment.to_html(tree)
{:ok, "<br /><hr />"} = FastSanitize.Fragment.to_html(tree)
end
test "it works for simple fragment trees with non-terminating tags" do
......@@ -30,7 +30,7 @@ defmodule FastSanitize.Fragment.Test do
], nil}
]
{:ok, "<link rel=\"stylesheet\" type=\"text/css\" href=\"http://example.com/example.css\">"} =
{:ok, "<link rel=\"stylesheet\" type=\"text/css\" href=\"http://example.com/example.css\"/>"} =
FastSanitize.Fragment.to_html(tree)
end
end
......