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
17package com.google.clearsilver.jsilver.compiler;
18
19import static com.google.clearsilver.jsilver.compiler.JavaExpression.BooleanLiteralExpression;
20import static com.google.clearsilver.jsilver.compiler.JavaExpression.callOn;
21import static com.google.clearsilver.jsilver.compiler.JavaExpression.string;
22import com.google.clearsilver.jsilver.syntax.analysis.DepthFirstAdapter;
23import com.google.clearsilver.jsilver.syntax.node.AAddExpression;
24import com.google.clearsilver.jsilver.syntax.node.AAndExpression;
25import com.google.clearsilver.jsilver.syntax.node.ADecimalExpression;
26import com.google.clearsilver.jsilver.syntax.node.ADescendVariable;
27import com.google.clearsilver.jsilver.syntax.node.ADivideExpression;
28import com.google.clearsilver.jsilver.syntax.node.AEqExpression;
29import com.google.clearsilver.jsilver.syntax.node.AExistsExpression;
30import com.google.clearsilver.jsilver.syntax.node.AFunctionExpression;
31import com.google.clearsilver.jsilver.syntax.node.AGtExpression;
32import com.google.clearsilver.jsilver.syntax.node.AGteExpression;
33import com.google.clearsilver.jsilver.syntax.node.AHexExpression;
34import com.google.clearsilver.jsilver.syntax.node.ALtExpression;
35import com.google.clearsilver.jsilver.syntax.node.ALteExpression;
36import com.google.clearsilver.jsilver.syntax.node.AModuloExpression;
37import com.google.clearsilver.jsilver.syntax.node.AMultiplyExpression;
38import com.google.clearsilver.jsilver.syntax.node.ANameVariable;
39import com.google.clearsilver.jsilver.syntax.node.ANeExpression;
40import com.google.clearsilver.jsilver.syntax.node.ANegativeExpression;
41import com.google.clearsilver.jsilver.syntax.node.ANotExpression;
42import com.google.clearsilver.jsilver.syntax.node.ANumericAddExpression;
43import com.google.clearsilver.jsilver.syntax.node.ANumericEqExpression;
44import com.google.clearsilver.jsilver.syntax.node.ANumericExpression;
45import com.google.clearsilver.jsilver.syntax.node.ANumericNeExpression;
46import com.google.clearsilver.jsilver.syntax.node.AOrExpression;
47import com.google.clearsilver.jsilver.syntax.node.AStringExpression;
48import com.google.clearsilver.jsilver.syntax.node.ASubtractExpression;
49import com.google.clearsilver.jsilver.syntax.node.AVariableExpression;
50import com.google.clearsilver.jsilver.syntax.node.PExpression;
51
52import 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 */
60public class EscapingEvaluator extends DepthFirstAdapter {
61
62  private JavaExpression currentEscapingExpression;
63  private boolean propagateEscapeStatus;
64  private final VariableTranslator variableTranslator;
65
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   */
81  public JavaExpression computeIfExemptFromEscaping(PExpression expression,
82      boolean propagateEscapeStatus) {
83    if (propagateEscapeStatus) {
84      return computeForPropagateStatus(expression);
85    }
86    return computeEscaping(expression, propagateEscapeStatus);
87  }
88
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   */
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
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
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
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   */
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   */
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
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
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
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
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
271  public void caseAHexExpression(AHexExpression node) {
272    setDefaultEscaping();
273  }
274
275  @Override
276  public void caseANumericExpression(ANumericExpression node) {
277    setDefaultEscaping();
278  }
279
280  @Override
281  public void caseANotExpression(ANotExpression node) {
282    setDefaultEscaping();
283  }
284
285  @Override
286  public void caseAExistsExpression(AExistsExpression node) {
287    setDefaultEscaping();
288  }
289
290  @Override
291  public void caseAEqExpression(AEqExpression node) {
292    setDefaultEscaping();
293  }
294
295  @Override
296  public void caseANumericEqExpression(ANumericEqExpression node) {
297    setDefaultEscaping();
298  }
299
300  @Override
301  public void caseANeExpression(ANeExpression node) {
302    setDefaultEscaping();
303  }
304
305  @Override
306  public void caseANumericNeExpression(ANumericNeExpression node) {
307    setDefaultEscaping();
308  }
309
310  @Override
311  public void caseALtExpression(ALtExpression node) {
312    setDefaultEscaping();
313  }
314
315  @Override
316  public void caseAGtExpression(AGtExpression node) {
317    setDefaultEscaping();
318  }
319
320  @Override
321  public void caseALteExpression(ALteExpression node) {
322    setDefaultEscaping();
323  }
324
325  @Override
326  public void caseAGteExpression(AGteExpression node) {
327    setDefaultEscaping();
328  }
329
330  @Override
331  public void caseAAndExpression(AAndExpression node) {
332    setDefaultEscaping();
333  }
334
335  @Override
336  public void caseAOrExpression(AOrExpression node) {
337    setDefaultEscaping();
338  }
339
340  @Override
341  public void caseANumericAddExpression(ANumericAddExpression node) {
342    setDefaultEscaping();
343  }
344
345  @Override
346  public void caseASubtractExpression(ASubtractExpression node) {
347    setDefaultEscaping();
348  }
349
350  @Override
351  public void caseAMultiplyExpression(AMultiplyExpression node) {
352    setDefaultEscaping();
353  }
354
355  @Override
356  public void caseADivideExpression(ADivideExpression node) {
357    setDefaultEscaping();
358  }
359
360  @Override
361  public void caseAModuloExpression(AModuloExpression node) {
362    setDefaultEscaping();
363  }
364
365  @Override
366  public void caseANegativeExpression(ANegativeExpression node) {
367    setDefaultEscaping();
368  }
369
370}
371