/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 org.apache.catalina.ssi;


import java.text.ParseException;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

import org.apache.tomcat.util.res.StringManager;
Represents a parsed expression.
Author:Paul Speed
/** * Represents a parsed expression. * * @author Paul Speed */
public class ExpressionParseTree { private static final StringManager sm = StringManager.getManager(ExpressionParseTree.class);
Contains the current set of completed nodes. This is a workspace for the parser.
/** * Contains the current set of completed nodes. This is a workspace for the * parser. */
private final LinkedList<Node> nodeStack = new LinkedList<>();
Contains operator nodes that don't yet have values. This is a workspace for the parser.
/** * Contains operator nodes that don't yet have values. This is a workspace * for the parser. */
private final LinkedList<OppNode> oppStack = new LinkedList<>();
The root node after the expression has been parsed.
/** * The root node after the expression has been parsed. */
private Node root;
The SSIMediator to use when evaluating the expressions.
/** * The SSIMediator to use when evaluating the expressions. */
private final SSIMediator ssiMediator;
Creates a new parse tree for the specified expression.
Params:
  • expr – The expression string
  • ssiMediator – Used to evaluated the expressions
Throws:
/** * Creates a new parse tree for the specified expression. * @param expr The expression string * @param ssiMediator Used to evaluated the expressions * @throws ParseException a parsing error occurred */
public ExpressionParseTree(String expr, SSIMediator ssiMediator) throws ParseException { this.ssiMediator = ssiMediator; parseExpression(expr); }
Evaluates the tree and returns true or false. The specified SSIMediator is used to resolve variable references.
Returns:the evaluation result
/** * Evaluates the tree and returns true or false. The specified SSIMediator * is used to resolve variable references. * @return the evaluation result */
public boolean evaluateTree() { return root.evaluate(); }
Pushes a new operator onto the opp stack, resolving existing opps as needed.
Params:
  • node – The operator node
/** * Pushes a new operator onto the opp stack, resolving existing opps as * needed. * @param node The operator node */
private void pushOpp(OppNode node) { // If node is null then it's just a group marker if (node == null) { oppStack.add(0, node); return; } while (true) { if (oppStack.size() == 0) break; OppNode top = oppStack.get(0); // If the top is a spacer then don't pop // anything if (top == null) break; // If the top node has a lower precedence then // let it stay if (top.getPrecedence() < node.getPrecedence()) break; // Remove the top node oppStack.remove(0); // Let it fill its branches top.popValues(nodeStack); // Stick it on the resolved node stack nodeStack.add(0, top); } // Add the new node to the opp stack oppStack.add(0, node); }
Resolves all pending opp nodes on the stack until the next group marker is reached.
/** * Resolves all pending opp nodes on the stack until the next group marker * is reached. */
private void resolveGroup() { OppNode top = null; while ((top = oppStack.remove(0)) != null) { // Let it fill its branches top.popValues(nodeStack); // Stick it on the resolved node stack nodeStack.add(0, top); } }
Parses the specified expression into a tree of parse nodes.
Params:
  • expr – The expression to parse
Throws:
/** * Parses the specified expression into a tree of parse nodes. * @param expr The expression to parse * @throws ParseException a parsing error occurred */
private void parseExpression(String expr) throws ParseException { StringNode currStringNode = null; // We cheat a little and start an artificial // group right away. It makes finishing easier. pushOpp(null); ExpressionTokenizer et = new ExpressionTokenizer(expr); while (et.hasMoreTokens()) { int token = et.nextToken(); if (token != ExpressionTokenizer.TOKEN_STRING) currStringNode = null; switch (token) { case ExpressionTokenizer.TOKEN_STRING : if (currStringNode == null) { currStringNode = new StringNode(et.getTokenValue()); nodeStack.add(0, currStringNode); } else { // Add to the existing currStringNode.value.append(" "); currStringNode.value.append(et.getTokenValue()); } break; case ExpressionTokenizer.TOKEN_AND : pushOpp(new AndNode()); break; case ExpressionTokenizer.TOKEN_OR : pushOpp(new OrNode()); break; case ExpressionTokenizer.TOKEN_NOT : pushOpp(new NotNode()); break; case ExpressionTokenizer.TOKEN_EQ : pushOpp(new EqualNode()); break; case ExpressionTokenizer.TOKEN_NOT_EQ : pushOpp(new NotNode()); // Sneak the regular node in. The NOT will // be resolved when the next opp comes along. oppStack.add(0, new EqualNode()); break; case ExpressionTokenizer.TOKEN_RBRACE : // Closeout the current group resolveGroup(); break; case ExpressionTokenizer.TOKEN_LBRACE : // Push a group marker pushOpp(null); break; case ExpressionTokenizer.TOKEN_GE : pushOpp(new NotNode()); // Similar strategy to NOT_EQ above, except this // is NOT less than oppStack.add(0, new LessThanNode()); break; case ExpressionTokenizer.TOKEN_LE : pushOpp(new NotNode()); // Similar strategy to NOT_EQ above, except this // is NOT greater than oppStack.add(0, new GreaterThanNode()); break; case ExpressionTokenizer.TOKEN_GT : pushOpp(new GreaterThanNode()); break; case ExpressionTokenizer.TOKEN_LT : pushOpp(new LessThanNode()); break; case ExpressionTokenizer.TOKEN_END : break; } } // Finish off the rest of the opps resolveGroup(); if (nodeStack.size() == 0) { throw new ParseException(sm.getString("expressionParseTree.noNodes"), et.getIndex()); } if (nodeStack.size() > 1) { throw new ParseException(sm.getString("expressionParseTree.extraNodes"), et.getIndex()); } if (oppStack.size() != 0) { throw new ParseException(sm.getString("expressionParseTree.unusedOpCodes"), et.getIndex()); } root = nodeStack.get(0); }
A node in the expression parse tree.
/** * A node in the expression parse tree. */
private abstract class Node {
Returns:true if the node evaluates to true.
/** * @return {@code true} if the node evaluates to true. */
public abstract boolean evaluate(); }
A node the represents a String value
/** * A node the represents a String value */
private class StringNode extends Node { StringBuilder value; String resolved = null; public StringNode(String value) { this.value = new StringBuilder(value); }
Resolves any variable references and returns the value string.
Returns:the value string
/** * Resolves any variable references and returns the value string. * * @return the value string */
public String getValue() { if (resolved == null) resolved = ssiMediator.substituteVariables(value.toString()); return resolved; }
Returns true if the string is not empty.
/** * Returns true if the string is not empty. */
@Override public boolean evaluate() { return !(getValue().length() == 0); } @Override public String toString() { return value.toString(); } } private static final int PRECEDENCE_NOT = 5; private static final int PRECEDENCE_COMPARE = 4; private static final int PRECEDENCE_LOGICAL = 1;
A node implementation that represents an operation.
/** * A node implementation that represents an operation. */
private abstract class OppNode extends Node {
The left branch.
/** * The left branch. */
Node left;
The right branch.
/** * The right branch. */
Node right;
Returns:a precedence level suitable for comparison to other OppNode preference levels.
/** * @return a precedence level suitable for comparison to other OppNode * preference levels. */
public abstract int getPrecedence();
Lets the node pop its own branch nodes off the front of the specified list. The default pulls two.
Params:
  • values – The list from which to pop the values
/** * Lets the node pop its own branch nodes off the front of the * specified list. The default pulls two. * * @param values The list from which to pop the values */
public void popValues(List<Node> values) { right = values.remove(0); left = values.remove(0); } } private final class NotNode extends OppNode { @Override public boolean evaluate() { return !left.evaluate(); } @Override public int getPrecedence() { return PRECEDENCE_NOT; }
Overridden to pop only one value.
/** * Overridden to pop only one value. */
@Override public void popValues(List<Node> values) { left = values.remove(0); } @Override public String toString() { return left + " NOT"; } } private final class AndNode extends OppNode { @Override public boolean evaluate() { if (!left.evaluate()) // Short circuit return false; return right.evaluate(); } @Override public int getPrecedence() { return PRECEDENCE_LOGICAL; } @Override public String toString() { return left + " " + right + " AND"; } } private final class OrNode extends OppNode { @Override public boolean evaluate() { if (left.evaluate()) // Short circuit return true; return right.evaluate(); } @Override public int getPrecedence() { return PRECEDENCE_LOGICAL; } @Override public String toString() { return left + " " + right + " OR"; } } private abstract class CompareNode extends OppNode { protected int compareBranches() { String val1 = ((StringNode)left).getValue(); String val2 = ((StringNode)right).getValue(); int val2Len = val2.length(); if (val2Len > 1 && val2.charAt(0) == '/' && val2.charAt(val2Len - 1) == '/') { // Treat as a regular expression String expr = val2.substring(1, val2Len - 1); ssiMediator.clearMatchGroups(); try { Pattern pattern = Pattern.compile(expr); // Regular expressions will only ever be used with EqualNode // so return zero for equal and non-zero for not equal Matcher matcher = pattern.matcher(val1); if (matcher.find()) { ssiMediator.populateMatchGroups(matcher); return 0; } else { return -1; } } catch (PatternSyntaxException pse) { ssiMediator.log(sm.getString("expressionParseTree.invalidExpression", expr), pse); return 0; } } return val1.compareTo(val2); } } private final class EqualNode extends CompareNode { @Override public boolean evaluate() { return (compareBranches() == 0); } @Override public int getPrecedence() { return PRECEDENCE_COMPARE; } @Override public String toString() { return left + " " + right + " EQ"; } } private final class GreaterThanNode extends CompareNode { @Override public boolean evaluate() { return (compareBranches() > 0); } @Override public int getPrecedence() { return PRECEDENCE_COMPARE; } @Override public String toString() { return left + " " + right + " GT"; } } private final class LessThanNode extends CompareNode { @Override public boolean evaluate() { return (compareBranches() < 0); } @Override public int getPrecedence() { return PRECEDENCE_COMPARE; } @Override public String toString() { return left + " " + right + " LT"; } } }