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