Skip to content

Commit faaf618

Browse files
committed
Add json_escape
1 parent 677e572 commit faaf618

2 files changed

Lines changed: 70 additions & 0 deletions

File tree

lib/phoenix_html.ex

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,64 @@ defmodule Phoenix.HTML do
371371

372372
defp javascript_escape(<<>>, acc), do: acc
373373

374+
@doc ~S"""
375+
Escapes encoded JSON for safe insertion into an HTML script tag.
376+
377+
The given JSON must already be encoded. The characters `<`, `>`, and `&`
378+
are encoded as JSON Unicode escape sequences and the result is marked as
379+
HTML safe.
380+
381+
iex> json = ~s({"name":"</script>"})
382+
iex> json |> json_escape() |> safe_to_string()
383+
"{\"name\":\"\\u003C/script\\u003E\"}"
384+
385+
"""
386+
@spec json_escape(binary) :: safe
387+
def json_escape(data) when is_binary(data) do
388+
{:safe, json_escape(data, 0, data, [])}
389+
end
390+
391+
escapes = [
392+
{?<, "\\u003C"},
393+
{?>, "\\u003E"},
394+
{?&, "\\u0026"}
395+
]
396+
397+
for {match, insert} <- escapes do
398+
defp json_escape(<<unquote(match), rest::bits>>, skip, original, acc) do
399+
json_escape(rest, skip + 1, original, [acc | unquote(insert)])
400+
end
401+
end
402+
403+
defp json_escape(<<_char, rest::bits>>, skip, original, acc) do
404+
json_escape(rest, skip, original, acc, 1)
405+
end
406+
407+
defp json_escape(<<>>, 0, original, _acc) do
408+
original
409+
end
410+
411+
defp json_escape(<<>>, _skip, _original, acc) do
412+
acc
413+
end
414+
415+
for {match, insert} <- escapes do
416+
defp json_escape(<<unquote(match), rest::bits>>, skip, original, acc, len) do
417+
part = binary_part(original, skip, len)
418+
json_escape(rest, skip + len + 1, original, [acc, part | unquote(insert)], 0)
419+
end
420+
end
421+
422+
defp json_escape(<<_char, rest::bits>>, skip, original, acc, len) do
423+
json_escape(rest, skip, original, acc, len + 1)
424+
end
425+
426+
defp json_escape(<<>>, 0, original, _acc, _len), do: original
427+
428+
defp json_escape(<<>>, skip, original, acc, len) do
429+
[acc | binary_part(original, skip, len)]
430+
end
431+
374432
@doc """
375433
Escapes a string for use as a CSS identifier.
376434

test/phoenix_html_test.exs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,18 @@ defmodule Phoenix.HTMLTest do
2121
assert javascript_escape({:safe, ["'Single quote'"]}) == {:safe, "\\'Single quote\\'"}
2222
end
2323

24+
test "json_escape/1" do
25+
assert json_escape("") == {:safe, ""}
26+
27+
assert ~s({"data":"</script><!--&>"})
28+
|> json_escape()
29+
|> safe_to_string() == ~S({"data":"\u003C/script\u003E\u003C!--\u0026\u003E"})
30+
31+
assert ~S({"data":"\"quotes\" and \\slashes"})
32+
|> json_escape()
33+
|> safe_to_string() == ~S({"data":"\"quotes\" and \\slashes"})
34+
end
35+
2436
describe "html_escape" do
2537
test "escapes entities" do
2638
assert html_escape("foo") == {:safe, "foo"}

0 commit comments

Comments
 (0)