blob: a3f9c28990cbf4f34ddcd881289dec7ed6fcee51 [file] [log] [blame]
/*
* Copyright (C) 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.clearsilver.jsilver.compiler;
import static com.google.clearsilver.jsilver.compiler.JavaExpression.BooleanLiteralExpression;
import static com.google.clearsilver.jsilver.compiler.JavaExpression.callOn;
import static com.google.clearsilver.jsilver.compiler.JavaExpression.string;
import com.google.clearsilver.jsilver.syntax.analysis.DepthFirstAdapter;
import com.google.clearsilver.jsilver.syntax.node.AAddExpression;
import com.google.clearsilver.jsilver.syntax.node.AAndExpression;
import com.google.clearsilver.jsilver.syntax.node.ADecimalExpression;
import com.google.clearsilver.jsilver.syntax.node.ADescendVariable;
import com.google.clearsilver.jsilver.syntax.node.ADivideExpression;
import com.google.clearsilver.jsilver.syntax.node.AEqExpression;
import com.google.clearsilver.jsilver.syntax.node.AExistsExpression;
import com.google.clearsilver.jsilver.syntax.node.AFunctionExpression;
import com.google.clearsilver.jsilver.syntax.node.AGtExpression;
import com.google.clearsilver.jsilver.syntax.node.AGteExpression;
import com.google.clearsilver.jsilver.syntax.node.AHexExpression;
import com.google.clearsilver.jsilver.syntax.node.ALtExpression;
import com.google.clearsilver.jsilver.syntax.node.ALteExpression;
import com.google.clearsilver.jsilver.syntax.node.AModuloExpression;
import com.google.clearsilver.jsilver.syntax.node.AMultiplyExpression;
import com.google.clearsilver.jsilver.syntax.node.ANameVariable;
import com.google.clearsilver.jsilver.syntax.node.ANeExpression;
import com.google.clearsilver.jsilver.syntax.node.ANegativeExpression;
import com.google.clearsilver.jsilver.syntax.node.ANotExpression;
import com.google.clearsilver.jsilver.syntax.node.ANumericAddExpression;
import com.google.clearsilver.jsilver.syntax.node.ANumericEqExpression;
import com.google.clearsilver.jsilver.syntax.node.ANumericExpression;
import com.google.clearsilver.jsilver.syntax.node.ANumericNeExpression;
import com.google.clearsilver.jsilver.syntax.node.AOrExpression;
import com.google.clearsilver.jsilver.syntax.node.AStringExpression;
import com.google.clearsilver.jsilver.syntax.node.ASubtractExpression;
import com.google.clearsilver.jsilver.syntax.node.AVariableExpression;
import com.google.clearsilver.jsilver.syntax.node.PExpression;
import java.util.LinkedList;
/**
* Generates a JavaExpression to determine whether a given CS expression should be escaped before
* displaying. If propagateEscapeStatus is enabled, string and numeric literals are not escaped, nor
* is the output of an escaping function. If not, any expression that contains an escaping function
* is not escaped. This maintains compatibility with the way ClearSilver works.
*/
public class EscapingEvaluator extends DepthFirstAdapter {
private JavaExpression currentEscapingExpression;
private boolean propagateEscapeStatus;
private final VariableTranslator variableTranslator;
public EscapingEvaluator(VariableTranslator variableTranslator) {
super();
this.variableTranslator = variableTranslator;
}
/**
* Returns a JavaExpression that can be used to decide whether a given variable should be escaped.
*
* @param expression variable expression to be evaluated.
* @param propagateEscapeStatus Whether to propagate the variable's escape status.
*
* @return Returns a {@code JavaExpression} representing a boolean expression that evaluates to
* {@code true} if {@code expression} should be exempted from escaping and {@code false}
* otherwise.
*/
public JavaExpression computeIfExemptFromEscaping(PExpression expression,
boolean propagateEscapeStatus) {
if (propagateEscapeStatus) {
return computeForPropagateStatus(expression);
}
return computeEscaping(expression, propagateEscapeStatus);
}
private JavaExpression computeForPropagateStatus(PExpression expression) {
// This function generates a boolean expression that evaluates to true
// if the input should be exempt from escaping. As this should only be
// called when PropagateStatus is enabled we must check EscapeMode as
// well as isPartiallyEscaped.
// The interpreter mode equivalent of this boolean expression would be :
// ((value.getEscapeMode() != EscapeMode.ESCAPE_NONE) || value.isPartiallyEscaped())
JavaExpression escapeMode = computeEscaping(expression, true);
JavaExpression partiallyEscaped = computeEscaping(expression, false);
JavaExpression escapeModeCheck =
JavaExpression.infix(JavaExpression.Type.BOOLEAN, "!=", escapeMode, JavaExpression
.symbol("EscapeMode.ESCAPE_NONE"));
return JavaExpression.infix(JavaExpression.Type.BOOLEAN, "||", escapeModeCheck,
partiallyEscaped);
}
/**
* Compute the escaping applied to the given expression. Uses {@code propagateEscapeStatus} to
* determine how to treat constants, and whether escaping is required on a part of the expression
* or the whole expression.
*/
public JavaExpression computeEscaping(PExpression expression, boolean propagateEscapeStatus) {
try {
assert currentEscapingExpression == null : "Not reentrant";
this.propagateEscapeStatus = propagateEscapeStatus;
expression.apply(this);
assert currentEscapingExpression != null : "No escaping calculated";
return currentEscapingExpression;
} finally {
currentEscapingExpression = null;
}
}
private void setEscaping(JavaExpression escaping) {
currentEscapingExpression = escaping;
}
/**
* String concatenation. Do not escape the combined string, if either of the halves has been
* escaped.
*/
@Override
public void caseAAddExpression(AAddExpression node) {
node.getLeft().apply(this);
JavaExpression left = currentEscapingExpression;
node.getRight().apply(this);
JavaExpression right = currentEscapingExpression;
setEscaping(or(left, right));
}
/**
* Process AST node for a function (e.g. dosomething(...)).
*/
@Override
public void caseAFunctionExpression(AFunctionExpression node) {
LinkedList<PExpression> argsList = node.getArgs();
PExpression[] args = argsList.toArray(new PExpression[argsList.size()]);
// Because the function name may have dots in, the parser would have broken
// it into a little node tree which we need to walk to reconstruct the
// full name.
final StringBuilder fullFunctionName = new StringBuilder();
node.getName().apply(new DepthFirstAdapter() {
@Override
public void caseANameVariable(ANameVariable node11) {
fullFunctionName.append(node11.getWord().getText());
}
@Override
public void caseADescendVariable(ADescendVariable node12) {
node12.getParent().apply(this);
fullFunctionName.append('.');
node12.getChild().apply(this);
}
});
setEscaping(function(fullFunctionName.toString(), args));
}
/**
* Do not escape the output of a function if either the function is an escaping function, or any
* of its parameters have been escaped.
*/
private JavaExpression function(String name, PExpression... csExpressions) {
if (propagateEscapeStatus) {
// context.isEscapingFunction("name") ? EscapeMode.ESCAPE_IS_CONSTANT : EscapeMode.ESCAPE_NONE
return JavaExpression.inlineIf(JavaExpression.Type.UNKNOWN, callOn(
JavaExpression.Type.BOOLEAN, TemplateTranslator.CONTEXT, "isEscapingFunction",
string(name)), JavaExpression.symbol("EscapeMode.ESCAPE_IS_CONSTANT"), JavaExpression
.symbol("EscapeMode.ESCAPE_NONE"));
}
JavaExpression finalExpression = BooleanLiteralExpression.FALSE;
for (int i = 0; i < csExpressions.length; i++) {
// Will put result in currentEscapingExpression.
csExpressions[i].apply(this);
finalExpression = or(finalExpression, currentEscapingExpression);
}
JavaExpression funcExpr =
callOn(JavaExpression.Type.BOOLEAN, TemplateTranslator.CONTEXT, "isEscapingFunction",
string(name));
return or(finalExpression, funcExpr);
}
/*
* This function tries to optimize the output expression where possible: instead of
* "(false || context.isEscapingFunction())" it returns "context.isEscapingFunction()".
*/
private JavaExpression or(JavaExpression first, JavaExpression second) {
if (propagateEscapeStatus) {
return JavaExpression.callOn(JavaExpression.symbol("EscapeMode"), "combineModes", first,
second);
}
if (first instanceof BooleanLiteralExpression) {
BooleanLiteralExpression expr = (BooleanLiteralExpression) first;
if (expr.getValue()) {
return expr;
} else {
return second;
}
}
if (second instanceof BooleanLiteralExpression) {
BooleanLiteralExpression expr = (BooleanLiteralExpression) second;
if (expr.getValue()) {
return expr;
} else {
return first;
}
}
return JavaExpression.infix(JavaExpression.Type.BOOLEAN, "||", first, second);
}
/*
* All the following operators have no effect on escaping, so just default to 'false'.
*/
/**
* Process AST node for a variable (e.g. a.b.c).
*/
@Override
public void caseAVariableExpression(AVariableExpression node) {
if (propagateEscapeStatus) {
JavaExpression varName = variableTranslator.translate(node.getVariable());
setEscaping(callOn(TemplateTranslator.DATA_CONTEXT, "findVariableEscapeMode", varName));
} else {
setDefaultEscaping();
}
}
private void setDefaultEscaping() {
if (propagateEscapeStatus) {
setEscaping(JavaExpression.symbol("EscapeMode.ESCAPE_IS_CONSTANT"));
} else {
setEscaping(BooleanLiteralExpression.FALSE);
}
}
/**
* Process AST node for a string (e.g. "hello").
*/
@Override
public void caseAStringExpression(AStringExpression node) {
setDefaultEscaping();
}
/**
* Process AST node for a decimal integer (e.g. 123).
*/
@Override
public void caseADecimalExpression(ADecimalExpression node) {
setDefaultEscaping();
}
/**
* Process AST node for a hex integer (e.g. 0x1AB).
*/
@Override
public void caseAHexExpression(AHexExpression node) {
setDefaultEscaping();
}
@Override
public void caseANumericExpression(ANumericExpression node) {
setDefaultEscaping();
}
@Override
public void caseANotExpression(ANotExpression node) {
setDefaultEscaping();
}
@Override
public void caseAExistsExpression(AExistsExpression node) {
setDefaultEscaping();
}
@Override
public void caseAEqExpression(AEqExpression node) {
setDefaultEscaping();
}
@Override
public void caseANumericEqExpression(ANumericEqExpression node) {
setDefaultEscaping();
}
@Override
public void caseANeExpression(ANeExpression node) {
setDefaultEscaping();
}
@Override
public void caseANumericNeExpression(ANumericNeExpression node) {
setDefaultEscaping();
}
@Override
public void caseALtExpression(ALtExpression node) {
setDefaultEscaping();
}
@Override
public void caseAGtExpression(AGtExpression node) {
setDefaultEscaping();
}
@Override
public void caseALteExpression(ALteExpression node) {
setDefaultEscaping();
}
@Override
public void caseAGteExpression(AGteExpression node) {
setDefaultEscaping();
}
@Override
public void caseAAndExpression(AAndExpression node) {
setDefaultEscaping();
}
@Override
public void caseAOrExpression(AOrExpression node) {
setDefaultEscaping();
}
@Override
public void caseANumericAddExpression(ANumericAddExpression node) {
setDefaultEscaping();
}
@Override
public void caseASubtractExpression(ASubtractExpression node) {
setDefaultEscaping();
}
@Override
public void caseAMultiplyExpression(AMultiplyExpression node) {
setDefaultEscaping();
}
@Override
public void caseADivideExpression(ADivideExpression node) {
setDefaultEscaping();
}
@Override
public void caseAModuloExpression(AModuloExpression node) {
setDefaultEscaping();
}
@Override
public void caseANegativeExpression(ANegativeExpression node) {
setDefaultEscaping();
}
}