• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2010 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.google.clearsilver.jsilver.compiler;
18 
19 import static com.google.clearsilver.jsilver.compiler.JavaExpression.BooleanLiteralExpression;
20 import static com.google.clearsilver.jsilver.compiler.JavaExpression.callOn;
21 import static com.google.clearsilver.jsilver.compiler.JavaExpression.string;
22 import com.google.clearsilver.jsilver.syntax.analysis.DepthFirstAdapter;
23 import com.google.clearsilver.jsilver.syntax.node.AAddExpression;
24 import com.google.clearsilver.jsilver.syntax.node.AAndExpression;
25 import com.google.clearsilver.jsilver.syntax.node.ADecimalExpression;
26 import com.google.clearsilver.jsilver.syntax.node.ADescendVariable;
27 import com.google.clearsilver.jsilver.syntax.node.ADivideExpression;
28 import com.google.clearsilver.jsilver.syntax.node.AEqExpression;
29 import com.google.clearsilver.jsilver.syntax.node.AExistsExpression;
30 import com.google.clearsilver.jsilver.syntax.node.AFunctionExpression;
31 import com.google.clearsilver.jsilver.syntax.node.AGtExpression;
32 import com.google.clearsilver.jsilver.syntax.node.AGteExpression;
33 import com.google.clearsilver.jsilver.syntax.node.AHexExpression;
34 import com.google.clearsilver.jsilver.syntax.node.ALtExpression;
35 import com.google.clearsilver.jsilver.syntax.node.ALteExpression;
36 import com.google.clearsilver.jsilver.syntax.node.AModuloExpression;
37 import com.google.clearsilver.jsilver.syntax.node.AMultiplyExpression;
38 import com.google.clearsilver.jsilver.syntax.node.ANameVariable;
39 import com.google.clearsilver.jsilver.syntax.node.ANeExpression;
40 import com.google.clearsilver.jsilver.syntax.node.ANegativeExpression;
41 import com.google.clearsilver.jsilver.syntax.node.ANotExpression;
42 import com.google.clearsilver.jsilver.syntax.node.ANumericAddExpression;
43 import com.google.clearsilver.jsilver.syntax.node.ANumericEqExpression;
44 import com.google.clearsilver.jsilver.syntax.node.ANumericExpression;
45 import com.google.clearsilver.jsilver.syntax.node.ANumericNeExpression;
46 import com.google.clearsilver.jsilver.syntax.node.AOrExpression;
47 import com.google.clearsilver.jsilver.syntax.node.AStringExpression;
48 import com.google.clearsilver.jsilver.syntax.node.ASubtractExpression;
49 import com.google.clearsilver.jsilver.syntax.node.AVariableExpression;
50 import com.google.clearsilver.jsilver.syntax.node.PExpression;
51 
52 import java.util.LinkedList;
53 
54 /**
55  * Generates a JavaExpression to determine whether a given CS expression should be escaped before
56  * displaying. If propagateEscapeStatus is enabled, string and numeric literals are not escaped, nor
57  * is the output of an escaping function. If not, any expression that contains an escaping function
58  * is not escaped. This maintains compatibility with the way ClearSilver works.
59  */
60 public class EscapingEvaluator extends DepthFirstAdapter {
61 
62   private JavaExpression currentEscapingExpression;
63   private boolean propagateEscapeStatus;
64   private final VariableTranslator variableTranslator;
65 
EscapingEvaluator(VariableTranslator variableTranslator)66   public EscapingEvaluator(VariableTranslator variableTranslator) {
67     super();
68     this.variableTranslator = variableTranslator;
69   }
70 
71   /**
72    * Returns a JavaExpression that can be used to decide whether a given variable should be escaped.
73    *
74    * @param expression variable expression to be evaluated.
75    * @param propagateEscapeStatus Whether to propagate the variable's escape status.
76    *
77    * @return Returns a {@code JavaExpression} representing a boolean expression that evaluates to
78    *         {@code true} if {@code expression} should be exempted from escaping and {@code false}
79    *         otherwise.
80    */
computeIfExemptFromEscaping(PExpression expression, boolean propagateEscapeStatus)81   public JavaExpression computeIfExemptFromEscaping(PExpression expression,
82       boolean propagateEscapeStatus) {
83     if (propagateEscapeStatus) {
84       return computeForPropagateStatus(expression);
85     }
86     return computeEscaping(expression, propagateEscapeStatus);
87   }
88 
computeForPropagateStatus(PExpression expression)89   private JavaExpression computeForPropagateStatus(PExpression expression) {
90     // This function generates a boolean expression that evaluates to true
91     // if the input should be exempt from escaping. As this should only be
92     // called when PropagateStatus is enabled we must check EscapeMode as
93     // well as isPartiallyEscaped.
94     // The interpreter mode equivalent of this boolean expression would be :
95     // ((value.getEscapeMode() != EscapeMode.ESCAPE_NONE) || value.isPartiallyEscaped())
96 
97     JavaExpression escapeMode = computeEscaping(expression, true);
98     JavaExpression partiallyEscaped = computeEscaping(expression, false);
99 
100     JavaExpression escapeModeCheck =
101         JavaExpression.infix(JavaExpression.Type.BOOLEAN, "!=", escapeMode, JavaExpression
102             .symbol("EscapeMode.ESCAPE_NONE"));
103 
104     return JavaExpression.infix(JavaExpression.Type.BOOLEAN, "||", escapeModeCheck,
105         partiallyEscaped);
106   }
107 
108   /**
109    * Compute the escaping applied to the given expression. Uses {@code propagateEscapeStatus} to
110    * determine how to treat constants, and whether escaping is required on a part of the expression
111    * or the whole expression.
112    */
computeEscaping(PExpression expression, boolean propagateEscapeStatus)113   public JavaExpression computeEscaping(PExpression expression, boolean propagateEscapeStatus) {
114     try {
115       assert currentEscapingExpression == null : "Not reentrant";
116       this.propagateEscapeStatus = propagateEscapeStatus;
117       expression.apply(this);
118       assert currentEscapingExpression != null : "No escaping calculated";
119       return currentEscapingExpression;
120     } finally {
121       currentEscapingExpression = null;
122     }
123   }
124 
setEscaping(JavaExpression escaping)125   private void setEscaping(JavaExpression escaping) {
126     currentEscapingExpression = escaping;
127   }
128 
129   /**
130    * String concatenation. Do not escape the combined string, if either of the halves has been
131    * escaped.
132    */
133   @Override
caseAAddExpression(AAddExpression node)134   public void caseAAddExpression(AAddExpression node) {
135     node.getLeft().apply(this);
136     JavaExpression left = currentEscapingExpression;
137     node.getRight().apply(this);
138     JavaExpression right = currentEscapingExpression;
139 
140     setEscaping(or(left, right));
141   }
142 
143   /**
144    * Process AST node for a function (e.g. dosomething(...)).
145    */
146   @Override
caseAFunctionExpression(AFunctionExpression node)147   public void caseAFunctionExpression(AFunctionExpression node) {
148     LinkedList<PExpression> argsList = node.getArgs();
149     PExpression[] args = argsList.toArray(new PExpression[argsList.size()]);
150 
151     // Because the function name may have dots in, the parser would have broken
152     // it into a little node tree which we need to walk to reconstruct the
153     // full name.
154     final StringBuilder fullFunctionName = new StringBuilder();
155     node.getName().apply(new DepthFirstAdapter() {
156 
157       @Override
158       public void caseANameVariable(ANameVariable node11) {
159         fullFunctionName.append(node11.getWord().getText());
160       }
161 
162       @Override
163       public void caseADescendVariable(ADescendVariable node12) {
164         node12.getParent().apply(this);
165         fullFunctionName.append('.');
166         node12.getChild().apply(this);
167       }
168     });
169 
170     setEscaping(function(fullFunctionName.toString(), args));
171   }
172 
173   /**
174    * Do not escape the output of a function if either the function is an escaping function, or any
175    * of its parameters have been escaped.
176    */
function(String name, PExpression... csExpressions)177   private JavaExpression function(String name, PExpression... csExpressions) {
178     if (propagateEscapeStatus) {
179       // context.isEscapingFunction("name") ? EscapeMode.ESCAPE_IS_CONSTANT : EscapeMode.ESCAPE_NONE
180       return JavaExpression.inlineIf(JavaExpression.Type.UNKNOWN, callOn(
181           JavaExpression.Type.BOOLEAN, TemplateTranslator.CONTEXT, "isEscapingFunction",
182           string(name)), JavaExpression.symbol("EscapeMode.ESCAPE_IS_CONSTANT"), JavaExpression
183           .symbol("EscapeMode.ESCAPE_NONE"));
184     }
185     JavaExpression finalExpression = BooleanLiteralExpression.FALSE;
186     for (int i = 0; i < csExpressions.length; i++) {
187       // Will put result in currentEscapingExpression.
188       csExpressions[i].apply(this);
189       finalExpression = or(finalExpression, currentEscapingExpression);
190     }
191     JavaExpression funcExpr =
192         callOn(JavaExpression.Type.BOOLEAN, TemplateTranslator.CONTEXT, "isEscapingFunction",
193             string(name));
194     return or(finalExpression, funcExpr);
195   }
196 
197   /*
198    * This function tries to optimize the output expression where possible: instead of
199    * "(false || context.isEscapingFunction())" it returns "context.isEscapingFunction()".
200    */
or(JavaExpression first, JavaExpression second)201   private JavaExpression or(JavaExpression first, JavaExpression second) {
202     if (propagateEscapeStatus) {
203       return JavaExpression.callOn(JavaExpression.symbol("EscapeMode"), "combineModes", first,
204           second);
205     }
206 
207     if (first instanceof BooleanLiteralExpression) {
208       BooleanLiteralExpression expr = (BooleanLiteralExpression) first;
209       if (expr.getValue()) {
210         return expr;
211       } else {
212         return second;
213       }
214     }
215     if (second instanceof BooleanLiteralExpression) {
216       BooleanLiteralExpression expr = (BooleanLiteralExpression) second;
217       if (expr.getValue()) {
218         return expr;
219       } else {
220         return first;
221       }
222     }
223     return JavaExpression.infix(JavaExpression.Type.BOOLEAN, "||", first, second);
224   }
225 
226   /*
227    * All the following operators have no effect on escaping, so just default to 'false'.
228    */
229 
230   /**
231    * Process AST node for a variable (e.g. a.b.c).
232    */
233   @Override
caseAVariableExpression(AVariableExpression node)234   public void caseAVariableExpression(AVariableExpression node) {
235     if (propagateEscapeStatus) {
236       JavaExpression varName = variableTranslator.translate(node.getVariable());
237       setEscaping(callOn(TemplateTranslator.DATA_CONTEXT, "findVariableEscapeMode", varName));
238     } else {
239       setDefaultEscaping();
240     }
241   }
242 
setDefaultEscaping()243   private void setDefaultEscaping() {
244     if (propagateEscapeStatus) {
245       setEscaping(JavaExpression.symbol("EscapeMode.ESCAPE_IS_CONSTANT"));
246     } else {
247       setEscaping(BooleanLiteralExpression.FALSE);
248     }
249   }
250 
251   /**
252    * Process AST node for a string (e.g. "hello").
253    */
254   @Override
caseAStringExpression(AStringExpression node)255   public void caseAStringExpression(AStringExpression node) {
256     setDefaultEscaping();
257   }
258 
259   /**
260    * Process AST node for a decimal integer (e.g. 123).
261    */
262   @Override
caseADecimalExpression(ADecimalExpression node)263   public void caseADecimalExpression(ADecimalExpression node) {
264     setDefaultEscaping();
265   }
266 
267   /**
268    * Process AST node for a hex integer (e.g. 0x1AB).
269    */
270   @Override
caseAHexExpression(AHexExpression node)271   public void caseAHexExpression(AHexExpression node) {
272     setDefaultEscaping();
273   }
274 
275   @Override
caseANumericExpression(ANumericExpression node)276   public void caseANumericExpression(ANumericExpression node) {
277     setDefaultEscaping();
278   }
279 
280   @Override
caseANotExpression(ANotExpression node)281   public void caseANotExpression(ANotExpression node) {
282     setDefaultEscaping();
283   }
284 
285   @Override
caseAExistsExpression(AExistsExpression node)286   public void caseAExistsExpression(AExistsExpression node) {
287     setDefaultEscaping();
288   }
289 
290   @Override
caseAEqExpression(AEqExpression node)291   public void caseAEqExpression(AEqExpression node) {
292     setDefaultEscaping();
293   }
294 
295   @Override
caseANumericEqExpression(ANumericEqExpression node)296   public void caseANumericEqExpression(ANumericEqExpression node) {
297     setDefaultEscaping();
298   }
299 
300   @Override
caseANeExpression(ANeExpression node)301   public void caseANeExpression(ANeExpression node) {
302     setDefaultEscaping();
303   }
304 
305   @Override
caseANumericNeExpression(ANumericNeExpression node)306   public void caseANumericNeExpression(ANumericNeExpression node) {
307     setDefaultEscaping();
308   }
309 
310   @Override
caseALtExpression(ALtExpression node)311   public void caseALtExpression(ALtExpression node) {
312     setDefaultEscaping();
313   }
314 
315   @Override
caseAGtExpression(AGtExpression node)316   public void caseAGtExpression(AGtExpression node) {
317     setDefaultEscaping();
318   }
319 
320   @Override
caseALteExpression(ALteExpression node)321   public void caseALteExpression(ALteExpression node) {
322     setDefaultEscaping();
323   }
324 
325   @Override
caseAGteExpression(AGteExpression node)326   public void caseAGteExpression(AGteExpression node) {
327     setDefaultEscaping();
328   }
329 
330   @Override
caseAAndExpression(AAndExpression node)331   public void caseAAndExpression(AAndExpression node) {
332     setDefaultEscaping();
333   }
334 
335   @Override
caseAOrExpression(AOrExpression node)336   public void caseAOrExpression(AOrExpression node) {
337     setDefaultEscaping();
338   }
339 
340   @Override
caseANumericAddExpression(ANumericAddExpression node)341   public void caseANumericAddExpression(ANumericAddExpression node) {
342     setDefaultEscaping();
343   }
344 
345   @Override
caseASubtractExpression(ASubtractExpression node)346   public void caseASubtractExpression(ASubtractExpression node) {
347     setDefaultEscaping();
348   }
349 
350   @Override
caseAMultiplyExpression(AMultiplyExpression node)351   public void caseAMultiplyExpression(AMultiplyExpression node) {
352     setDefaultEscaping();
353   }
354 
355   @Override
caseADivideExpression(ADivideExpression node)356   public void caseADivideExpression(ADivideExpression node) {
357     setDefaultEscaping();
358   }
359 
360   @Override
caseAModuloExpression(AModuloExpression node)361   public void caseAModuloExpression(AModuloExpression node) {
362     setDefaultEscaping();
363   }
364 
365   @Override
caseANegativeExpression(ANegativeExpression node)366   public void caseANegativeExpression(ANegativeExpression node) {
367     setDefaultEscaping();
368   }
369 
370 }
371