Skip to content

Commit

Permalink
Add more filter escape tests (fixes #150)
Browse files Browse the repository at this point in the history
  • Loading branch information
kaos committed Mar 12, 2014
1 parent 00599f1 commit bbe5be8
Show file tree
Hide file tree
Showing 6 changed files with 100 additions and 54 deletions.
1 change: 1 addition & 0 deletions NEWS.md
Expand Up @@ -11,6 +11,7 @@ Standards](http://www.gnu.org/prep/standards/html_node/NEWS-File.html#NEWS-File)
* The backwards incompatible change in 0.9.1 for directory-compiled * The backwards incompatible change in 0.9.1 for directory-compiled
templates has been reverted. A new `render` function has been added templates has been reverted. A new `render` function has been added
instead (#148). instead (#148).
* Fixed broken escape feature (#150).




## 0.9.1 (2014-03-02) ## 0.9.1 (2014-03-02)
Expand Down
2 changes: 1 addition & 1 deletion include/erlydtl_ext.hrl
Expand Up @@ -10,7 +10,7 @@
block_dict = dict:new(), block_dict = dict:new(),
trans_fun = none, trans_fun = none,
trans_locales = [], trans_locales = [],
auto_escape = off, auto_escape = [off],
doc_root = "", doc_root = "",
parse_trail = [], parse_trail = [],
vars = [], vars = [],
Expand Down
79 changes: 34 additions & 45 deletions src/erlydtl_beam_compiler.erl
Expand Up @@ -50,7 +50,7 @@
-export([ -export([
is_up_to_date/2, is_up_to_date/2,


format/2, format/1,
value_ast/4, value_ast/4,
interpret_args/2 interpret_args/2
]). ]).
Expand All @@ -64,7 +64,7 @@
add_warnings/2, merge_info/2, call_extension/3, add_warnings/2, merge_info/2, call_extension/3,
init_treewalker/1, resolve_variable/2, resolve_variable/3, init_treewalker/1, resolve_variable/2, resolve_variable/3,
reset_parse_trail/2, load_library/3, load_library/4, reset_parse_trail/2, load_library/3, load_library/4,
shorten_filename/2]). shorten_filename/2, push_auto_escape/2, pop_auto_escape/1]).


-include_lib("merl/include/merl.hrl"). -include_lib("merl/include/merl.hrl").
-include("erlydtl_ext.hrl"). -include("erlydtl_ext.hrl").
Expand Down Expand Up @@ -590,8 +590,9 @@ body_ast(DjangoParseTree, BodyScope, TreeWalker) ->
{ScopeId, TreeWalkerScope} = begin_scope(BodyScope, TreeWalker), {ScopeId, TreeWalkerScope} = begin_scope(BodyScope, TreeWalker),
{AstInfoList, TreeWalker1} = {AstInfoList, TreeWalker1} =
lists:mapfoldl( lists:mapfoldl(
fun ({'autoescape', {identifier, _, OnOrOff}, Contents}, #treewalker{ context=Context }=TW) -> fun ({'autoescape', {identifier, _, OnOrOff}, Contents}, TW) ->
body_ast(Contents, TW#treewalker{ context=Context#dtl_context{auto_escape = OnOrOff} }); {Info, BodyTW} = body_ast(Contents, push_auto_escape(OnOrOff, TW)),
{Info, pop_auto_escape(BodyTW)};
({'block', {identifier, Pos, Name}, Contents}, #treewalker{ context=Context }=TW) -> ({'block', {identifier, Pos, Name}, Contents}, #treewalker{ context=Context }=TW) ->
{Block, BlockScope} = {Block, BlockScope} =
case dict:find(Name, Context#dtl_context.block_dict) of case dict:find(Name, Context#dtl_context.block_dict) of
Expand Down Expand Up @@ -712,8 +713,7 @@ body_ast(DjangoParseTree, BodyScope, TreeWalker) ->
({'extends', _}, TW) -> ({'extends', _}, TW) ->
empty_ast(?ERR(unexpected_extends_tag, TW)); empty_ast(?ERR(unexpected_extends_tag, TW));
(ValueToken, TW) -> (ValueToken, TW) ->
{{ValueAst,ValueInfo},ValueTW} = value_ast(ValueToken, true, true, TW), format(value_ast(ValueToken, true, true, TW))
{{format(ValueAst, ValueTW),ValueInfo},ValueTW}
end, end,
TreeWalkerScope, TreeWalkerScope,
DjangoParseTree), DjangoParseTree),
Expand Down Expand Up @@ -987,12 +987,8 @@ ssi_ast(FileName, #treewalker{
{{FileAst, Info}, TreeWalker1} = value_ast(FileName, true, true, TreeWalker), {{FileAst, Info}, TreeWalker1} = value_ast(FileName, true, true, TreeWalker),
{{?Q("erlydtl_runtime:read_file(_@Mod@, _@Fun@, _@Dir@, _@FileAst)"), Info}, TreeWalker1}. {{?Q("erlydtl_runtime:read_file(_@Mod@, _@Fun@, _@Dir@, _@FileAst)"), Info}, TreeWalker1}.


filter_tag_ast(FilterList, Contents, #treewalker{ context=Context }=TreeWalker) -> filter_tag_ast(FilterList, Contents, TreeWalker) ->
{{InnerAst, Info}, #treewalker{ context=Context1 }=TreeWalker1} = body_ast( {{InnerAst, Info}, TreeWalker1} = body_ast(Contents, push_auto_escape(did, TreeWalker)),
Contents,
TreeWalker#treewalker{
context=Context#dtl_context{ auto_escape = did }
}),
{{FilteredAst, FilteredInfo}, TreeWalker2} = {{FilteredAst, FilteredInfo}, TreeWalker2} =
lists:foldl( lists:foldl(
fun ({{identifier, _, Name}, []}, {{AstAcc, InfoAcc}, TreeWalkerAcc}) fun ({{identifier, _, Name}, []}, {{AstAcc, InfoAcc}, TreeWalkerAcc})
Expand All @@ -1003,10 +999,7 @@ filter_tag_ast(FilterList, Contents, #treewalker{ context=Context }=TreeWalker)
{{Ast, merge_info(InfoAcc, AstInfo)}, TW} {{Ast, merge_info(InfoAcc, AstInfo)}, TW}
end, end,
{{?Q("erlang:iolist_to_binary(_@InnerAst)"), Info}, {{?Q("erlang:iolist_to_binary(_@InnerAst)"), Info},
TreeWalker1#treewalker{ pop_auto_escape(TreeWalker1)},
context=Context1#dtl_context{
auto_escape = Context#dtl_context.auto_escape
}}},
FilterList), FilterList),


EscapedAst = case search_for_escape_filter( EscapedAst = case search_for_escape_filter(
Expand All @@ -1017,9 +1010,9 @@ filter_tag_ast(FilterList, Contents, #treewalker{ context=Context }=TreeWalker)
end, end,
{{EscapedAst, FilteredInfo}, TreeWalker2}. {{EscapedAst, FilteredInfo}, TreeWalker2}.


search_for_escape_filter(FilterList, #dtl_context{auto_escape = on}) -> search_for_escape_filter(FilterList, #dtl_context{auto_escape = [on|_]}) ->
search_for_safe_filter(FilterList); search_for_safe_filter(FilterList);
search_for_escape_filter(_, #dtl_context{auto_escape = did}) -> off; search_for_escape_filter(_, #dtl_context{auto_escape = [did|_]}) -> off;
search_for_escape_filter([{{identifier, _, 'escape'}, []}|Rest], _Context) -> search_for_escape_filter([{{identifier, _, 'escape'}, []}|Rest], _Context) ->
search_for_safe_filter(Rest); search_for_safe_filter(Rest);
search_for_escape_filter([_|Rest], Context) -> search_for_escape_filter([_|Rest], Context) ->
Expand All @@ -1031,29 +1024,24 @@ search_for_safe_filter([{{identifier, _, Name}, []}|_])
search_for_safe_filter([_|Rest]) -> search_for_safe_filter(Rest); search_for_safe_filter([_|Rest]) -> search_for_safe_filter(Rest);
search_for_safe_filter([]) -> on. search_for_safe_filter([]) -> on.


filter_ast(Variable, Filter, #treewalker{ context=Context }=TreeWalker) -> filter_ast(Variable, Filter, TreeWalker) ->
%% the escape filter is special; it is always applied last, so we have to go digging for it %% the escape filter is special; it is always applied last, so we have to go digging for it


%% AutoEscape = 'did' means we (will have) decided whether to escape the current variable, %% AutoEscape = 'did' means we (will have) decided whether to escape the current variable,
%% so don't do any more escaping %% so don't do any more escaping


{{UnescapedAst, Info}, #treewalker{ {{UnescapedAst, Info}, TreeWalker1} =
context=Context1 filter_ast_noescape(
}=TreeWalker1} = filter_ast_noescape( Variable, Filter,
Variable, Filter, push_auto_escape(did, TreeWalker)),
TreeWalker#treewalker{
context=Context#dtl_context{ {EscapedAst, TreeWalker2} =
auto_escape = did case search_for_escape_filter(Variable, Filter, TreeWalker#treewalker.context) of
} }), on -> {?Q("erlydtl_filters:force_escape(_@UnescapedAst)"),

TreeWalker1#treewalker{ safe = true }};
EscapedAst = case search_for_escape_filter(Variable, Filter, Context) of _ -> {UnescapedAst, TreeWalker1}
on -> ?Q("erlydtl_filters:force_escape(_@UnescapedAst)"); end,
_ -> UnescapedAst {{EscapedAst, Info}, pop_auto_escape(TreeWalker2)}.
end,
{{EscapedAst, Info}, TreeWalker1#treewalker{
context=Context1#dtl_context{
auto_escape = Context#dtl_context.auto_escape
}}}.


filter_ast_noescape(Variable, {{identifier, _, Name}, []}, TreeWalker) filter_ast_noescape(Variable, {{identifier, _, Name}, []}, TreeWalker)
when Name =:= 'escape'; Name =:= 'safe'; Name =:= 'safeseq' -> when Name =:= 'escape'; Name =:= 'safe'; Name =:= 'safeseq' ->
Expand Down Expand Up @@ -1091,9 +1079,9 @@ filter_ast2(Name, Args, #dtl_context{ filters = Filters }) ->
{unknown_filter, Name, length(Args)} {unknown_filter, Name, length(Args)}
end. end.


search_for_escape_filter(Variable, Filter, #dtl_context{auto_escape = on}) -> search_for_escape_filter(Variable, Filter, #dtl_context{auto_escape = [on|_]}) ->
search_for_safe_filter(Variable, Filter); search_for_safe_filter(Variable, Filter);
search_for_escape_filter(_, _, #dtl_context{auto_escape = did}) -> search_for_escape_filter(_, _, #dtl_context{auto_escape = [did|_]}) ->
off; off;
search_for_escape_filter(Variable, {{identifier, _, 'escape'}, []} = Filter, _Context) -> search_for_escape_filter(Variable, {{identifier, _, 'escape'}, []} = Filter, _Context) ->
search_for_safe_filter(Variable, Filter); search_for_safe_filter(Variable, Filter);
Expand Down Expand Up @@ -1156,17 +1144,18 @@ resolve_variable_ast1({variable, {identifier, Pos, VarName}}, {Runtime, Finder},
end, end,
{Ast, TreeWalker}. {Ast, TreeWalker}.


format(Ast, TreeWalker) -> format({{Ast, Info}, TreeWalker}) ->
auto_escape(format_number_ast(Ast), TreeWalker). auto_escape({{format_number_ast(Ast), Info}, TreeWalker}).


format_number_ast(Ast) -> format_number_ast(Ast) ->
?Q("erlydtl_filters:format_number(_@Ast)"). ?Q("erlydtl_filters:format_number(_@Ast)").




auto_escape(Value, #treewalker{ safe = true }) -> Value; auto_escape({AstInfo, #treewalker{ safe = true }=TW}) ->
auto_escape(Value, #treewalker{ context=#dtl_context{ auto_escape = on }}) -> {AstInfo, TW#treewalker{ safe = false }};
?Q("erlydtl_filters:force_escape(_@Value)"); auto_escape({{Value, Info}, #treewalker{ context=#dtl_context{auto_escape=[on|_]} }=TW}) ->
auto_escape(Value, _) -> Value. {{?Q("erlydtl_filters:force_escape(_@Value)"), Info}, TW};
auto_escape(Value) -> Value.


firstof_ast(Vars, TreeWalker) -> firstof_ast(Vars, TreeWalker) ->
body_ast( body_ast(
Expand Down Expand Up @@ -1352,7 +1341,7 @@ cycle_ast(Names, #treewalker{ context=Context }=TreeWalker) ->
{{V, #ast_info{ var_names=[VarName] }}, _} = resolve_variable_ast(Var, true, TreeWalker), {{V, #ast_info{ var_names=[VarName] }}, _} = resolve_variable_ast(Var, true, TreeWalker),
{V, [VarName|VarNamesAcc]}; {V, [VarName|VarNamesAcc]};
({number_literal, _, Num}, VarNamesAcc) -> ({number_literal, _, Num}, VarNamesAcc) ->
{format(erl_syntax:integer(Num), TreeWalker), VarNamesAcc}; {format_number_ast(erl_syntax:integer(Num)), VarNamesAcc};
(_, VarNamesAcc) -> (_, VarNamesAcc) ->
{[], VarNamesAcc} {[], VarNamesAcc}
end, [], Names), end, [], Names),
Expand Down
15 changes: 10 additions & 5 deletions src/erlydtl_compiler.erl
Expand Up @@ -65,11 +65,17 @@ default_options() -> [verbose, report].


compile_template(Template, Module, Options) -> compile_template(Template, Module, Options) ->
Context = process_opts(undefined, Module, Options), Context = process_opts(undefined, Module, Options),
compile(Context#dtl_context{ bin = iolist_to_binary(Template) }). Bin = iolist_to_binary(Template),
?LOG_INFO("Compile template: ~32s~s~n",
[Bin, if size(Bin) > 32 -> "...";
true -> ""
end],
Context),
compile(Context#dtl_context{ bin = Bin }).


compile_file(File, Module, Options) -> compile_file(File, Module, Options) ->
Context = process_opts(File, Module, Options), Context = process_opts(File, Module, Options),
?LOG_INFO("Compile template: ~s~n", [File], Context), ?LOG_INFO("Compile file: ~s~n", [File], Context),
compile(Context). compile(Context).


compile_dir(Dir, Module, Options) -> compile_dir(Dir, Module, Options) ->
Expand All @@ -90,7 +96,6 @@ format_error(Error) ->
erlydtl_compiler_utils:format_error(Error). erlydtl_compiler_utils:format_error(Error).





%%==================================================================== %%====================================================================
%% Internal functions %% Internal functions
%%==================================================================== %%====================================================================
Expand Down Expand Up @@ -224,8 +229,8 @@ init_context(ParseTrail, DefDir, Module, Options) ->
#dtl_context{ #dtl_context{
all_options = Options, all_options = Options,
auto_escape = case proplists:get_value(auto_escape, Options, true) of auto_escape = case proplists:get_value(auto_escape, Options, true) of
true -> on; true -> [on];
_ -> off _ -> [off]
end, end,
parse_trail = ParseTrail, parse_trail = ParseTrail,
module = Module, module = Module,
Expand Down
16 changes: 15 additions & 1 deletion src/erlydtl_compiler_utils.erl
Expand Up @@ -68,7 +68,9 @@
restore_scope/2, restore_scope/2,
shorten_filename/1, shorten_filename/2, shorten_filename/1, shorten_filename/2,
to_string/2, to_string/2,
unescape_string_literal/1 unescape_string_literal/1,
push_auto_escape/2,
pop_auto_escape/1
]). ]).


-include("erlydtl_ext.hrl"). -include("erlydtl_ext.hrl").
Expand Down Expand Up @@ -296,6 +298,18 @@ shorten_filename(Name, Cwd) ->
end end
end. end.


push_auto_escape(State, #treewalker{ context=Context }=TreeWalker) ->
TreeWalker#treewalker{ context=push_auto_escape(State, Context) };
push_auto_escape(State, #dtl_context{ auto_escape=AutoEscape }=Context) ->
Context#dtl_context{ auto_escape=[State|AutoEscape] }.

pop_auto_escape(#treewalker{ context=Context }=TreeWalker) ->
TreeWalker#treewalker{ context=pop_auto_escape(Context) };
pop_auto_escape(#dtl_context{ auto_escape=[_|AutoEscape] }=Context)
when length(AutoEscape) > 0 ->
Context#dtl_context{ auto_escape=AutoEscape };
pop_auto_escape(Context) -> Context.

format_error({load_library, Name, Mod, Reason}) -> format_error({load_library, Name, Mod, Reason}) ->
io_lib:format("Failed to load library '~p' (~p): ~p", [Name, Mod, Reason]); io_lib:format("Failed to load library '~p' (~p): ~p", [Name, Mod, Reason]);
format_error({load_from, Name, Mod, Tag}) -> format_error({load_from, Name, Mod, Tag}) ->
Expand Down
41 changes: 39 additions & 2 deletions test/erlydtl_test_defs.erl
Expand Up @@ -44,7 +44,14 @@ all_test_defs() ->
[{var1, "<b>bold</b>"}], <<"&lt;b&gt;bold&lt;/b&gt;">>}, [{var1, "<b>bold</b>"}], <<"&lt;b&gt;bold&lt;/b&gt;">>},
{"Nested autoescape", {"Nested autoescape",
<<"{% autoescape on %}{{ var1 }}{% autoescape off %}{{ var1 }}{% endautoescape %}{% endautoescape %}">>, <<"{% autoescape on %}{{ var1 }}{% autoescape off %}{{ var1 }}{% endautoescape %}{% endautoescape %}">>,
[{var1, "<b>"}], <<"&lt;b&gt;<b>">>} [{var1, "<b>"}], <<"&lt;b&gt;<b>">>},
{"default auto escape",
<<"{{ var1 }}">>, [{var1, "&"}], [], [auto_escape],
<<"&amp;">>},
{"intermixed autoescape",
<<"{% autoescape on %}1:{{ var1 }}{% endautoescape %} 2:{{ var1 }}{% autoescape on %} 3:{{ var1 }}{% endautoescape %}">>,
[{var1, "&"}],
<<"1:&amp; 2:& 3:&amp;">>}
]}, ]},
{"string literal", {"string literal",
[{"Render literal", [{"Render literal",
Expand Down Expand Up @@ -648,6 +655,9 @@ all_test_defs() ->
{"|safe", {"|safe",
<<"{% autoescape on %}{{ var1|safe|escape }}{% endautoescape %}">>, [{var1, "&"}], <<"{% autoescape on %}{{ var1|safe|escape }}{% endautoescape %}">>, [{var1, "&"}],
<<"&">>}, <<"&">>},
{"|safe is local",
<<"{{ var1 }}{{ var1|safe }}{{ var1 }}">>, [{var1, "&"}], [], [auto_escape],
<<"&amp;&&amp;">>},
%%python/django slice is zero based, erlang lists are 1 based %%python/django slice is zero based, erlang lists are 1 based
%%first number included, second number not %%first number included, second number not
%%negative numbers are allowed %%negative numbers are allowed
Expand Down Expand Up @@ -1030,7 +1040,34 @@ all_test_defs() ->
source = <<"{{ var|yesno:\"missing_false_choice\" }}">>, source = <<"{{ var|yesno:\"missing_false_choice\" }}">>,
render_vars = [{var, true}], render_vars = [{var, true}],
output = {error, {yesno, choices}} output = {error, {yesno, choices}}
} },
{"escape only once (#150) - no auto escape",
%% note that auto_escape is off by default in the test suite
%% due to how the tests have been written (and it's too much
%% work for me to rewrite them)
<<"{{ foo }}{{ foo|add:'bar' }}">>,
[{foo, "foo&"}],
<<"foo&foo&bar">>},
{"escape only once (#150) - auto escape block",
<<"{% autoescape on %}{{ foo }}{{ foo|add:'bar' }}{% endautoescape %}">>,
[{foo, "foo&"}],
<<"foo&amp;foo&amp;bar">>},
{"escape only once (#150) - auto escape",
<<"{{ foo }}{{ foo|add:'bar' }}">>,
[{foo, "foo&"}], [], [auto_escape],
<<"foo&amp;foo&amp;bar">>},
{"escape only once (#150) - auto escape, safe",
<<"{{ foo|safe }}{{ foo|add:'bar'|safe }}&{{ foo|safe|add:'bar' }}">>,
[{foo, "foo&"}], [], [auto_escape],
<<"foo&foo&bar&foo&bar">>},
{"escape only once (#150) - escape filter",
<<"{{ foo|escape }}{{ foo|add:'bar'|escape }}&{{ foo|escape|add:'bar' }}">>,
[{foo, "foo&"}],
<<"foo&amp;foo&amp;bar&foo&amp;bar">>},
{"escape only once (#150) - auto escape + escape filter",
<<"{{ foo|escape }}{{ foo|add:'bar'|escape }}&{{ foo|escape|add:'bar' }}">>,
[{foo, "foo&"}], [], [auto_escape],
<<"foo&amp;foo&amp;bar&foo&amp;bar">>}
]}, ]},
{"filters_if", {"filters_if",
[{"Filter if 1.1", [{"Filter if 1.1",
Expand Down

0 comments on commit bbe5be8

Please sign in to comment.