Browse Source

Implement "safe" and "safeseq" filters.

Evan Miller 14 years ago
parent
commit
c123411949
3 changed files with 51 additions and 42 deletions
  1. 34 28
      src/erlydtl_compiler.erl
  2. 12 14
      src/erlydtl_filters.erl
  3. 5 0
      tests/src/erlydtl_unittests.erl

+ 34 - 28
src/erlydtl_compiler.erl

@@ -63,7 +63,8 @@
     pre_render_asts = []}).
     pre_render_asts = []}).
     
     
 -record(treewalker, {
 -record(treewalker, {
-    counter = 0
+    counter = 0,
+    safe = false
 }).    
 }).    
 
 
 compile(Binary, Module) when is_binary(Binary) ->
 compile(Binary, Module) when is_binary(Binary) ->
@@ -520,7 +521,7 @@ body_ast(DjangoParseTree, Context, TreeWalker) ->
                 cycle_compat_ast(Names, Context, TreeWalkerAcc);
                 cycle_compat_ast(Names, Context, TreeWalkerAcc);
             (ValueToken, TreeWalkerAcc) -> 
             (ValueToken, TreeWalkerAcc) -> 
                 {{ValueAst,ValueInfo},ValueTreeWalker} = value_ast(ValueToken, true, Context, TreeWalkerAcc),
                 {{ValueAst,ValueInfo},ValueTreeWalker} = value_ast(ValueToken, true, Context, TreeWalkerAcc),
-                {{format(ValueAst, Context),ValueInfo},ValueTreeWalker}
+                {{format(ValueAst, Context, ValueTreeWalker),ValueInfo},ValueTreeWalker}
         end, TreeWalker, DjangoParseTree),   
         end, TreeWalker, DjangoParseTree),   
     {AstList, {Info, TreeWalker3}} = lists:mapfoldl(
     {AstList, {Info, TreeWalker3}} = lists:mapfoldl(
         fun({Ast, Info}, {InfoAcc, TreeWalkerAcc}) -> 
         fun({Ast, Info}, {InfoAcc, TreeWalkerAcc}) -> 
@@ -567,8 +568,7 @@ value_ast(ValueToken, AsString, Context, TreeWalker) ->
                                          [Value1Ast, Value2Ast]),
                                          [Value1Ast, Value2Ast]),
             {{Ast, merge_info(InfoValue1,InfoValue2)}, TreeWalker2};
             {{Ast, merge_info(InfoValue1,InfoValue2)}, TreeWalker2};
         {'string_literal', _Pos, String} ->
         {'string_literal', _Pos, String} ->
-            {{auto_escape(erl_syntax:string(unescape_string_literal(String)), Context), 
-                    #ast_info{}}, TreeWalker};
+            {{erl_syntax:string(unescape_string_literal(String)), #ast_info{}}, TreeWalker};
         {'number_literal', _Pos, Number} ->
         {'number_literal', _Pos, Number} ->
             case AsString of
             case AsString of
                 true  -> string_ast(Number, TreeWalker);
                 true  -> string_ast(Number, TreeWalker);
@@ -675,7 +675,11 @@ filter_ast(Variable, Filter, Context, TreeWalker) ->
     end.
     end.
 
 
 filter_ast_noescape(Variable, [{identifier, _, 'escape'}], Context, TreeWalker) ->
 filter_ast_noescape(Variable, [{identifier, _, 'escape'}], Context, TreeWalker) ->
-    value_ast(Variable, true, Context, TreeWalker);
+    value_ast(Variable, true, Context, TreeWalker#treewalker{safe = true});
+filter_ast_noescape(Variable, [{identifier, _, 'safe'}], Context, TreeWalker) ->
+    value_ast(Variable, true, Context, TreeWalker#treewalker{safe = true});
+filter_ast_noescape(Variable, [{identifier, _, 'safeseq'}], Context, TreeWalker) ->
+    value_ast(Variable, true, Context, TreeWalker#treewalker{safe = true});
 filter_ast_noescape(Variable, Filter, Context, TreeWalker) ->
 filter_ast_noescape(Variable, Filter, Context, TreeWalker) ->
     {{VariableAst, Info1}, TreeWalker2} = value_ast(Variable, true, Context, TreeWalker),
     {{VariableAst, Info1}, TreeWalker2} = value_ast(Variable, true, Context, TreeWalker),
     {VarValue, Info2} = filter_ast1(Filter, VariableAst, Context),
     {VarValue, Info2} = filter_ast1(Filter, VariableAst, Context),
@@ -695,20 +699,25 @@ filter_ast2(Name, VariableAst, AdditionalArgs, VarNames) ->
     {erl_syntax:application(erl_syntax:atom(erlydtl_filters), erl_syntax:atom(Name), 
     {erl_syntax:application(erl_syntax:atom(erlydtl_filters), erl_syntax:atom(Name), 
             [VariableAst | AdditionalArgs]), #ast_info{var_names = VarNames}}.
             [VariableAst | AdditionalArgs]), #ast_info{var_names = VarNames}}.
  
  
-search_for_escape_filter(_, _, #dtl_context{auto_escape = on}) ->
-    on;
+search_for_escape_filter(Variable, Filter, #dtl_context{auto_escape = on}) ->
+    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, Filter, _) ->
-    search_for_escape_filter(Variable, Filter).
-
-search_for_escape_filter(_, [{identifier, _, 'escape'}]) ->
-    on;
-search_for_escape_filter({apply_filter, Variable, Filter}, _) ->
-    search_for_escape_filter(Variable, Filter);
-search_for_escape_filter(_Variable, _Filter) ->
+search_for_escape_filter(Variable, [{identifier, _, 'escape'}] = Filter, _Context) ->
+    search_for_safe_filter(Variable, Filter);
+search_for_escape_filter({apply_filter, Variable, Filter}, _, Context) ->
+    search_for_escape_filter(Variable, Filter, Context);
+search_for_escape_filter(_Variable, _Filter, _Context) ->
     off.
     off.
 
 
+search_for_safe_filter(_, [{identifier, _, 'safe'}]) ->
+    off;
+search_for_safe_filter(_, [{identifier, _, 'safeseq'}]) ->
+    off;
+search_for_safe_filter({apply_filter, Variable, Filter}, _) ->
+    search_for_safe_filter(Variable, Filter);
+search_for_safe_filter(_Variable, _Filter) ->
+    on.
 
 
 resolve_variable_ast(VarTuple, Context) ->
 resolve_variable_ast(VarTuple, Context) ->
     resolve_variable_ast(VarTuple, Context, 'find_value').
     resolve_variable_ast(VarTuple, Context, 'find_value').
@@ -739,23 +748,20 @@ resolve_scoped_variable_ast(VarName, Context) ->
                 end
                 end
         end, undefined, Context#dtl_context.local_scopes).
         end, undefined, Context#dtl_context.local_scopes).
 
 
-format(Ast, Context) ->
-    auto_escape(format_number_ast(Ast), Context).
-
+format(Ast, Context, TreeWalker) ->
+    auto_escape(format_number_ast(Ast), Context, TreeWalker).
 
 
 format_number_ast(Ast) ->
 format_number_ast(Ast) ->
     erl_syntax:application(erl_syntax:atom(erlydtl_filters), erl_syntax:atom(format_number),
     erl_syntax:application(erl_syntax:atom(erlydtl_filters), erl_syntax:atom(format_number),
         [Ast]).
         [Ast]).
 
 
 
 
-auto_escape(Value, Context) ->
-    case Context#dtl_context.auto_escape of
-        on ->
-            erl_syntax:application(erl_syntax:atom(erlydtl_filters), erl_syntax:atom(force_escape),
-                [Value]);
-        _ ->
-            Value
-    end.
+auto_escape(Value, _, #treewalker{safe = true}) ->
+    Value;
+auto_escape(Value, #dtl_context{auto_escape = on}, _) ->
+    erl_syntax:application(erl_syntax:atom(erlydtl_filters), erl_syntax:atom(force_escape), [Value]);
+auto_escape(Value, _, _) ->
+    Value.
 
 
 firstof_ast(Vars, Context, TreeWalker) ->
 firstof_ast(Vars, Context, TreeWalker) ->
 	body_ast([lists:foldl(fun
 	body_ast([lists:foldl(fun
@@ -831,7 +837,7 @@ cycle_ast(Names, Context, TreeWalker) ->
                                    {V, _} = resolve_variable_ast(Var, Context),
                                    {V, _} = resolve_variable_ast(Var, Context),
                                    V;
                                    V;
                               ({number_literal, _, Num}) ->
                               ({number_literal, _, Num}) ->
-                                   format(erl_syntax:integer(Num), Context);
+                                   format(erl_syntax:integer(Num), Context, TreeWalker);
                               (_) ->
                               (_) ->
                                    []
                                    []
                            end, Names),
                            end, Names),
@@ -890,7 +896,7 @@ tag_ast(Name, Args, Context, TreeWalker) ->
                 {erl_syntax:tuple([erl_syntax:string(Key), erl_syntax:string(unescape_string_literal(Value))]), AstInfoAcc};
                 {erl_syntax:tuple([erl_syntax:string(Key), erl_syntax:string(unescape_string_literal(Value))]), AstInfoAcc};
             ({{identifier, _, Key}, Value}, AstInfoAcc) ->
             ({{identifier, _, Key}, Value}, AstInfoAcc) ->
                 {AST, VarName} = resolve_variable_ast(Value, Context),
                 {AST, VarName} = resolve_variable_ast(Value, Context),
-                {erl_syntax:tuple([erl_syntax:string(Key), format(AST,Context)]), merge_info(#ast_info{var_names=[VarName]}, AstInfoAcc)}
+                {erl_syntax:tuple([erl_syntax:string(Key), format(AST,Context, TreeWalker)]), merge_info(#ast_info{var_names=[VarName]}, AstInfoAcc)}
         end, #ast_info{}, Args),
         end, #ast_info{}, Args),
 
 
     {RenderAst, RenderInfo} = case Context#dtl_context.custom_tags_module of
     {RenderAst, RenderInfo} = case Context#dtl_context.custom_tags_module of

+ 12 - 14
src/erlydtl_filters.erl

@@ -57,7 +57,7 @@
         dictsort/2,
         dictsort/2,
         dictsortreversed/2,
         dictsortreversed/2,
         divisibleby/2,
         divisibleby/2,
-        %escape/,
+        %escape/, - implemented in erlydtl_compiler
         escapejs/1,
         escapejs/1,
         filesizeformat/1,
         filesizeformat/1,
         first/1,
         first/1,
@@ -87,8 +87,8 @@
         random_range/1,
         random_range/1,
         removetags/2,
         removetags/2,
         rjust/2,
         rjust/2,
-        %safe/,
-        %safeseq/,
+        %safe/, - implemented in erlydtl_compiler
+        %safeseq/, - implemented in erlydtl_compiler
         slice/2,
         slice/2,
         slugify/1,
         slugify/1,
         stringformat/2,
         stringformat/2,
@@ -862,26 +862,24 @@ lower(Input, Index) ->
 phone2numeric([], Acc) ->
 phone2numeric([], Acc) ->
     lists:reverse(Acc);
     lists:reverse(Acc);
 phone2numeric([H|T], Acc) when H >= $a, H =< $c; H >= $A, H =< $C ->
 phone2numeric([H|T], Acc) when H >= $a, H =< $c; H >= $A, H =< $C ->
-phone2numeric(T, [$2|Acc]);
+    phone2numeric(T, [$2|Acc]);
 phone2numeric([H|T], Acc) when H >= $d, H =< $f; H >= $D, H =< $F ->
 phone2numeric([H|T], Acc) when H >= $d, H =< $f; H >= $D, H =< $F ->
-phone2numeric(T, [$3|Acc]);
+    phone2numeric(T, [$3|Acc]);
 phone2numeric([H|T], Acc) when H >= $g, H =< $i; H >= $G, H =< $I ->
 phone2numeric([H|T], Acc) when H >= $g, H =< $i; H >= $G, H =< $I ->
-phone2numeric(T, [$4|Acc]);
+    phone2numeric(T, [$4|Acc]);
 phone2numeric([H|T], Acc) when H >= $j, H =< $l; H >= $J, H =< $L ->
 phone2numeric([H|T], Acc) when H >= $j, H =< $l; H >= $J, H =< $L ->
-phone2numeric(T, [$5|Acc]);
+    phone2numeric(T, [$5|Acc]);
 phone2numeric([H|T], Acc) when H >= $m, H =< $o; H >= $M, H =< $O ->
 phone2numeric([H|T], Acc) when H >= $m, H =< $o; H >= $M, H =< $O ->
-phone2numeric(T, [$6|Acc]);
+    phone2numeric(T, [$6|Acc]);
 phone2numeric([H|T], Acc) when H >= $p, H =< $s; H >= $P, H =< $S ->
 phone2numeric([H|T], Acc) when H >= $p, H =< $s; H >= $P, H =< $S ->
-phone2numeric(T, [$7|Acc]);
+    phone2numeric(T, [$7|Acc]);
 phone2numeric([H|T], Acc) when H >= $t, H =< $v; H >= $T, H =< $V ->
 phone2numeric([H|T], Acc) when H >= $t, H =< $v; H >= $T, H =< $V ->
-phone2numeric(T, [$8|Acc]);
+    phone2numeric(T, [$8|Acc]);
 phone2numeric([H|T], Acc) when H >= $w, H =< $z; H >= $W, H =< $Z ->
 phone2numeric([H|T], Acc) when H >= $w, H =< $z; H >= $W, H =< $Z ->
-phone2numeric(T, [$9|Acc]);
+    phone2numeric(T, [$9|Acc]);
 phone2numeric([H|T], Acc) ->
 phone2numeric([H|T], Acc) ->
     phone2numeric(T, [H|Acc]).
     phone2numeric(T, [H|Acc]).
 
 
-
-
 slugify([], Acc) ->
 slugify([], Acc) ->
     lists:reverse(Acc);
     lists:reverse(Acc);
 slugify([H|T], Acc) when H >= $A, H =< $Z ->
 slugify([H|T], Acc) when H >= $A, H =< $Z ->
@@ -889,7 +887,7 @@ slugify([H|T], Acc) when H >= $A, H =< $Z ->
 slugify([$\ |T], Acc) ->
 slugify([$\ |T], Acc) ->
     slugify(T, [$-|Acc]);
     slugify(T, [$-|Acc]);
 slugify([H|T], Acc) when H >= $a, H =< $z; H >= $0, H =< $9; H =:= $_ ->
 slugify([H|T], Acc) when H >= $a, H =< $z; H >= $0, H =< $9; H =:= $_ ->
-slugify(T, [H|Acc]);
+    slugify(T, [H|Acc]);
 slugify([_|T], Acc) ->
 slugify([_|T], Acc) ->
     slugify(T, Acc).
     slugify(T, Acc).
 
 

+ 5 - 0
tests/src/erlydtl_unittests.erl

@@ -389,6 +389,8 @@ tests() ->
                     <<"{% if var1|divisibleby:\"3\" %}yay{% endif %}">>, [{var1, 21}], <<"yay">>},
                     <<"{% if var1|divisibleby:\"3\" %}yay{% endif %}">>, [{var1, 21}], <<"yay">>},
                 {"|divisibleby:\"3\"",
                 {"|divisibleby:\"3\"",
                     <<"{% if var1|divisibleby:\"3\" %}yay{% endif %}">>, [{var1, 22}], <<"">>},
                     <<"{% if var1|divisibleby:\"3\" %}yay{% endif %}">>, [{var1, 22}], <<"">>},
+                {"|escape",
+                    <<"{% autoescape on %}{{ var1|escape|escape|escape }}{% endautoescape %}">>, [{var1, ">&1"}], <<"&gt;&amp;1">>},
                 {"|escapejs",
                 {"|escapejs",
                     <<"{{ var1|escapejs }}">>, [{var1, "testing\r\njavascript 'string\" <b>escaping</b>"}],
                     <<"{{ var1|escapejs }}">>, [{var1, "testing\r\njavascript 'string\" <b>escaping</b>"}],
                     <<"testing\\u000D\\u000Ajavascript \\u0027string\\u0022 \\u003Cb\\u003Eescaping\\u003C/b\\u003E">>},
                     <<"testing\\u000D\\u000Ajavascript \\u0027string\\u0022 \\u003Cb\\u003Eescaping\\u003C/b\\u003E">>},
@@ -504,6 +506,9 @@ tests() ->
                 {"|rjust:10",
                 {"|rjust:10",
                     <<"{{ var1|rjust:10 }}">>, [{var1, "Bush"}],
                     <<"{{ var1|rjust:10 }}">>, [{var1, "Bush"}],
                     <<"      Bush">>},
                     <<"      Bush">>},
+                {"|safe",
+                    <<"{% autoescape on %}{{ var1|safe|escape }}{% endautoescape %}">>, [{var1, "&"}],
+                    <<"&">>},
                 %%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