Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,9 @@ import Config
config :logger, :console, colors: [enabled: false]
config :phoenix_template, :json_library, Jason
config :phoenix_template, :trim_on_html_eex_engine, true

config :phoenix_template, :format_encoders,
markup: Phoenix.TemplateTest.MarkupEncoder,
custom: Phoenix.TemplateTest.CustomEncoder

config :phoenix_template, :html_formats, [:markup]
26 changes: 26 additions & 0 deletions lib/phoenix/template.ex
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,32 @@ defmodule Phoenix.Template do
config :phoenix_template, :format_encoders,
html: Phoenix.HTML.Engine

### HTML formats

`.html.eex` templates are compiled and encoded with `Phoenix.HTML.Engine`,
which provides HTML safety. Other formats are compiled with `EEx.SmartEngine`,
which interpolates values as is.

A format that renders HTML-like markup but needs its own encoder, for
example one that post-processes the rendered markup, can be compiled
with `Phoenix.HTML.Engine` too by listing it in the `:html_formats` option:

config :phoenix_template, :format_encoders, mjml: MyApp.MJML
config :phoenix_template, :html_formats, [:html, :mjml]

Its templates are then compiled exactly like `.html.eex` ones. Interpolated
values are escaped unless they are marked as safe, such as with
`Phoenix.HTML.raw/1`, the `:trim_on_html_eex_engine` option applies, and
the format encoder receives the `{:safe, iodata}` tuple that they return:

defmodule MyApp.MJML do
def encode_to_iodata!({:safe, mjml}), do: MyApp.MJML.to_html(mjml)
end

In case the `{:safe, ...}` tuple is not received, you should raise, as it means
the user likely has not configured the `html_formats` accordingly.

This requires `phoenix_html` as a dependency.
"""

@type path :: binary
Expand Down
61 changes: 35 additions & 26 deletions lib/phoenix/template/eex_engine.ex
Original file line number Diff line number Diff line change
Expand Up @@ -20,37 +20,46 @@ defmodule Phoenix.Template.EExEngine do
"template paths in Phoenix require the format extension, got: #{path}"
end

case Phoenix.Template.format_encoder(format) do
Phoenix.HTML.Engine ->
unless Code.ensure_loaded?(Phoenix.HTML.Engine) do
raise "could not load Phoenix.HTML.Engine to use with .html.eex templates. " <>
"You can configure your own format encoder for HTML but we recommend " <>
"adding phoenix_html as a dependency as it provides XSS protection."
end

trim =
case Application.get_env(:phoenix_template, :trim_on_html_eex_engine) do
nil ->
case Application.get_env(:phoenix_view, :trim_on_html_eex_engine) do
nil ->
Application.get_env(:phoenix, :trim_on_html_eex_engine, true)
if html_format?(format) do
if not Code.ensure_loaded?(Phoenix.HTML.Engine) do
raise "could not load Phoenix.HTML.Engine to use with .#{format}.eex templates. " <>
"You can configure your own format encoder for HTML but we recommend " <>
"adding phoenix_html as a dependency as it provides XSS protection."
end

boolean ->
IO.warn(
"config :phoenix_view, :trim_on_html_eex_engine is deprecated, please use config :phoenix_template, :trim_on_html_eex_engine instead"
)
trim =
case Application.get_env(:phoenix_template, :trim_on_html_eex_engine) do
nil ->
case Application.get_env(:phoenix_view, :trim_on_html_eex_engine) do
nil ->
Application.get_env(:phoenix, :trim_on_html_eex_engine, true)

boolean
end
boolean ->
IO.warn(
"config :phoenix_view, :trim_on_html_eex_engine is deprecated, please use config :phoenix_template, :trim_on_html_eex_engine instead"
)

boolean ->
boolean
end
boolean
end

[engine: Phoenix.HTML.Engine, trim: trim]
boolean ->
boolean
end

_ ->
[engine: EEx.SmartEngine]
[engine: Phoenix.HTML.Engine, trim: trim]
else
[engine: EEx.SmartEngine]
end
end

defp html_format?(format) do
Phoenix.Template.format_encoder(format) == Phoenix.HTML.Engine or
format in html_formats()
end

defp html_formats do
:phoenix_template
|> Application.get_env(:html_formats, [])
|> Enum.map(&to_string/1)
end
end
1 change: 1 addition & 0 deletions test/fixtures/templates/show.custom.eex
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<div>Custom! <%= @message %></div>
1 change: 1 addition & 0 deletions test/fixtures/templates/show.markup.eex
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<div>Markup! <%= @message %></div>
3 changes: 3 additions & 0 deletions test/fixtures/templates/trim.markup.eex
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<%= 12 %>
<%= 34 %>
<%= 56 %>
34 changes: 34 additions & 0 deletions test/phoenix/template_test.exs
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
defmodule Phoenix.TemplateTest.MarkupEncoder do
def encode_to_iodata!({:safe, body}), do: ["markup:", body]
end

defmodule Phoenix.TemplateTest.CustomEncoder do
def encode_to_iodata!(body), do: ["custom:", body]
end

defmodule Phoenix.TemplateTest do
use ExUnit.Case, async: true

Expand Down Expand Up @@ -74,10 +82,36 @@ defmodule Phoenix.TemplateTest do
refute AllTemplates.__mix_recompile__?()
end

test "compiles templates of html formats with Phoenix.HTML.Engine" do
assert AllTemplates.show_markup_eex(%{message: "<hello>"})
|> Phoenix.HTML.safe_to_string() ==
"<div>Markup! &lt;hello&gt;</div>\n"

assert AllTemplates.show_markup_eex(%{message: {:safe, "<hello>"}})
|> Phoenix.HTML.safe_to_string() ==
"<div>Markup! <hello></div>\n"

assigns = %{message: "<hello>"}

assert Template.render_to_string(AllTemplates, "show_markup_eex", "markup", assigns) ==
"markup:<div>Markup! &lt;hello&gt;</div>\n"
end

test "compiles templates of other formats with EEx.SmartEngine" do
assert AllTemplates.show_custom_eex(%{message: "<hello>"}) ==
"<div>Custom! <hello></div>\n"

assigns = %{message: "<hello>"}

assert Template.render_to_string(AllTemplates, "show_custom_eex", "custom", assigns) ==
"custom:<div>Custom! <hello></div>\n"
end

if Version.match?(System.version(), ">= 1.12.0") do
test "trims only compiled HTML files" do
assert AllTemplates.no_trim_text_eex(%{}) == "12\n 34\n56\n"
assert AllTemplates.trim_html_eex(%{}) |> Phoenix.HTML.safe_to_string() == "12\n34\n56"
assert AllTemplates.trim_markup_eex(%{}) |> Phoenix.HTML.safe_to_string() == "12\n34\n56"
end
end

Expand Down
Loading