dsandberg 2002/11/25 02:15:43 Modified: catalina/src/share/org/apache/catalina/ssi SSICommand.java SSIConfig.java SSIEcho.java SSIExec.java SSIFlastmod.java SSIFsize.java SSIInclude.java SSIMediator.java SSIPrintenv.java SSIProcessor.java SSISet.java SSIStopProcessingException.java tester/src/bin tester.xml Added: catalina/src/share/org/apache/catalina/ssi ExpressionParseTree.java ExpressionTokenizer.java SSIConditional.java SSIConditionalState.java tester/web SSIConditional09.shtml tester/web/golden SSIConditional09.txt Log: Added back Paul Speed's conditional SSI enhancement. Updated code/regression tests to better emulate Apache SSI. Fixed bug w/ expression parser's handling of literals. Revision Changes Path 1.2 +5 -3 jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/ssi/SSICommand.java Index: SSICommand.java =================================================================== RCS file: /home/cvs/jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/ssi/SSICommand.java,v retrieving revision 1.1 retrieving revision 1.2 diff -u -r1.1 -r1.2 --- SSICommand.java 24 May 2002 16:35:39 -0000 1.1 +++ SSICommand.java 25 Nov 2002 10:15:42 -0000 1.2 @@ -79,12 +79,14 @@ * Write the output of the command to the writer. * * @param ssiMediator the ssi mediator + * @param commandName the name of the actual command ( ie. echo ) * @param paramNames The parameter names * @param paramValues The parameter values * @param writer the writer to output to * @throws SSIStopProcessingException if SSI processing should be aborted */ public void process(SSIMediator ssiMediator, + String commandName, String[] paramNames, String[] paramValues, PrintWriter writer) throws SSIStopProcessingException; 1.3 +6 -4 jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/ssi/SSIConfig.java Index: SSIConfig.java =================================================================== RCS file: /home/cvs/jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/ssi/SSIConfig.java,v retrieving revision 1.2 retrieving revision 1.3 diff -u -r1.2 -r1.3 --- SSIConfig.java 24 Nov 2002 06:22:36 -0000 1.2 +++ SSIConfig.java 25 Nov 2002 10:15:42 -0000 1.3 @@ -72,6 +72,7 @@ * Implements the Server-side #exec command * * @author Bip Thelin + * @author Paul Speed * @author Dan Sandberg * @version $Revision$, $Date$ */ @@ -80,6 +81,7 @@ * @see SSICommand */ public void process(SSIMediator ssiMediator, + String commandName, String[] paramNames, String[] paramValues, PrintWriter writer ) { 1.2 +6 -4 jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/ssi/SSIEcho.java Index: SSIEcho.java =================================================================== RCS file: /home/cvs/jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/ssi/SSIEcho.java,v retrieving revision 1.1 retrieving revision 1.2 diff -u -r1.1 -r1.2 --- SSIEcho.java 24 May 2002 04:38:58 -0000 1.1 +++ SSIEcho.java 25 Nov 2002 10:15:42 -0000 1.2 @@ -71,6 +71,7 @@ * Return the result associated with the supplied Server Variable. * * @author Bip Thelin + * @author Paul Speed * @author Dan Sandberg * @version $Revision$, $Date$ */ @@ -82,6 +83,7 @@ * @see SSICommand */ public void process(SSIMediator ssiMediator, + String commandName, String[] paramNames, String[] paramValues, PrintWriter writer) { 1.3 +7 -5 jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/ssi/SSIExec.java Index: SSIExec.java =================================================================== RCS file: /home/cvs/jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/ssi/SSIExec.java,v retrieving revision 1.2 retrieving revision 1.3 diff -u -r1.2 -r1.3 --- SSIExec.java 24 Nov 2002 06:22:36 -0000 1.2 +++ SSIExec.java 25 Nov 2002 10:15:42 -0000 1.3 @@ -89,6 +89,7 @@ * * @author Bip Thelin * @author Amy Roh + * @author Paul Speed * @author Dan Sandberg * @version $Revision$, $Date$ * @@ -101,6 +102,7 @@ * @see SSICommand */ public void process(SSIMediator ssiMediator, + String commandName, String[] paramNames, String[] paramValues, PrintWriter writer) { @@ -111,7 +113,7 @@ String substitutedValue = ssiMediator.substituteVariables( paramValue ); if ( paramName.equalsIgnoreCase("cgi") ) { - ssiInclude.process( ssiMediator, new String[] {"virtual"}, new String[] {substitutedValue}, writer ); + ssiInclude.process( ssiMediator, "include", new String[] {"virtual"}, new String[] {substitutedValue}, writer ); } else if ( paramName.equalsIgnoreCase("cmd") ) { boolean foundProgram = false; try { 1.3 +6 -4 jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/ssi/SSIFlastmod.java Index: SSIFlastmod.java =================================================================== RCS file: /home/cvs/jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/ssi/SSIFlastmod.java,v retrieving revision 1.2 retrieving revision 1.3 diff -u -r1.2 -r1.3 --- SSIFlastmod.java 24 Nov 2002 06:22:36 -0000 1.2 +++ SSIFlastmod.java 25 Nov 2002 10:15:42 -0000 1.3 @@ -75,6 +75,7 @@ * Implements the Server-side #flastmod command * * @author Bip Thelin + * @author Paul Speed * @author Dan Sandberg * @version $Revision$, $Date$ */ @@ -83,6 +84,7 @@ * @see SSICommand */ public void process(SSIMediator ssiMediator, + String commandName, String[] paramNames, String[] paramValues, PrintWriter writer) { 1.4 +9 -7 jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/ssi/SSIFsize.java Index: SSIFsize.java =================================================================== RCS file: /home/cvs/jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/ssi/SSIFsize.java,v retrieving revision 1.3 retrieving revision 1.4 diff -u -r1.3 -r1.4 --- SSIFsize.java 24 Nov 2002 06:22:36 -0000 1.3 +++ SSIFsize.java 25 Nov 2002 10:15:42 -0000 1.4 @@ -72,6 +72,7 @@ * Implements the Server-side #fsize command * * @author Bip Thelin + * @author Paul Speed * @author Dan Sandberg * @version $Revision$, $Date$ */ @@ -83,9 +84,10 @@ * @see SSICommand */ public void process(SSIMediator ssiMediator, - String[] paramNames, - String[] paramValues, - PrintWriter writer) { + String commandName, + String[] paramNames, + String[] paramValues, + PrintWriter writer) { String configErrMsg = ssiMediator.getConfigErrMsg(); for(int i=0;i<paramNames.length;i++) { 1.3 +6 -4 jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/ssi/SSIInclude.java Index: SSIInclude.java =================================================================== RCS file: /home/cvs/jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/ssi/SSIInclude.java,v retrieving revision 1.2 retrieving revision 1.3 diff -u -r1.2 -r1.3 --- SSIInclude.java 24 Nov 2002 06:22:36 -0000 1.2 +++ SSIInclude.java 25 Nov 2002 10:15:42 -0000 1.3 @@ -75,6 +75,7 @@ * Implements the Server-side #include command * * @author Bip Thelin + * @author Paul Speed * @author Dan Sandberg * @version $Revision$, $Date$ */ @@ -83,6 +84,7 @@ * @see SSICommand */ public void process(SSIMediator ssiMediator, + String commandName, String[] paramNames, String[] paramValues, PrintWriter writer) { 1.3 +10 -4 jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/ssi/SSIMediator.java Index: SSIMediator.java =================================================================== RCS file: /home/cvs/jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/ssi/SSIMediator.java,v retrieving revision 1.2 retrieving revision 1.3 diff -u -r1.2 -r1.3 --- SSIMediator.java 24 Nov 2002 06:22:36 -0000 1.2 +++ SSIMediator.java 25 Nov 2002 10:15:42 -0000 1.3 @@ -86,6 +86,7 @@ * * @author Bip Thelin * @author Amy Roh + * @author Paul Speed * @author Dan Sandberg * @version $Revision$, $Date$ */ @@ -102,6 +103,7 @@ protected Date lastModifiedDate; protected int debug; protected Strftime strftime; + protected SSIConditionalState conditionalState = new SSIConditionalState(); static { //We try to encode only the same characters that apache does @@ -163,6 +165,10 @@ public String getConfigSizeFmt() { return configSizeFmt; + } + + public SSIConditionalState getConditionalState() { + return conditionalState; } public Collection getVariableNames() { 1.2 +5 -4 jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/ssi/SSIPrintenv.java Index: SSIPrintenv.java =================================================================== RCS file: /home/cvs/jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/ssi/SSIPrintenv.java,v retrieving revision 1.1 retrieving revision 1.2 diff -u -r1.1 -r1.2 --- SSIPrintenv.java 24 May 2002 04:38:58 -0000 1.1 +++ SSIPrintenv.java 25 Nov 2002 10:15:42 -0000 1.2 @@ -79,6 +79,7 @@ * @see SSICommand */ public void process(SSIMediator ssiMediator, + String commandName, String[] paramNames, String[] paramValues, PrintWriter writer) { 1.2 +82 -41 jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/ssi/SSIProcessor.java Index: SSIProcessor.java =================================================================== RCS file: /home/cvs/jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/ssi/SSIProcessor.java,v retrieving revision 1.1 retrieving revision 1.2 diff -u -r1.1 -r1.2 --- SSIProcessor.java 24 May 2002 04:38:58 -0000 1.1 +++ SSIProcessor.java 25 Nov 2002 10:15:42 -0000 1.2 @@ -108,6 +108,11 @@ addCommand( "fsize", new SSIFsize() ); addCommand( "printenv", new SSIPrintenv() ); addCommand( "set", new SSISet() ); + SSIConditional ssiConditional = new SSIConditional(); + addCommand( "if", ssiConditional ); + addCommand( "elif", ssiConditional ); + addCommand( "endif", ssiConditional ); + addCommand( "else", ssiConditional ); } public void addCommand( String name, SSICommand command ) { @@ -147,7 +152,9 @@ index += COMMAND_START.length(); command.setLength( 0 ); //clear the command string } else { - writer.write( c ); + if ( !ssiMediator.getConditionalState().processConditionalCommandsOnly ) { + writer.write( c ); + } index++; } } else { @@ -159,20 +166,29 @@ ssiExternalResolver.log( "SSIProcessor.process -- processing command: " + strCmd, null ); } String[] paramNames = parseParamNames(command, strCmd.length()); - String[] paramValues = parseParamValues(command, strCmd.length()); + String[] paramValues = parseParamValues(command, strCmd.length(), paramNames.length ); //We need to fetch this value each time, since it may change during the loop String configErrMsg = ssiMediator.getConfigErrMsg(); SSICommand ssiCommand = (SSICommand) commands.get(strCmd.toLowerCase()); - if ( ssiCommand != null ) { - if ( paramNames.length==paramValues.length ) { - ssiCommand.process( ssiMediator, paramNames, paramValues, writer ); - } else { - ssiExternalResolver.log( "Parameter names count does not match parameter values count on command: " + strCmd, null ); - writer.write( configErrMsg ); - } + String errorMessage = null; + if ( ssiCommand == null ) { + errorMessage = "Unknown command: " + strCmd; + } else if ( paramValues == null ) { + errorMessage = "Error parsing directive parameters."; + } else if ( paramNames.length!=paramValues.length ) { + errorMessage = "Parameter names count does not match parameter values count on command: " + strCmd; } else { - ssiExternalResolver.log( "Unknown command: " + strCmd, null); + // don't process the command if we are processing conditional commands only and the + // command is not conditional + if ( !ssiMediator.getConditionalState().processConditionalCommandsOnly || + ssiCommand instanceof SSIConditional ) { + ssiCommand.process( ssiMediator, strCmd, paramNames, paramValues, writer ); + } + } + + if ( errorMessage != null ) { + ssiExternalResolver.log( errorMessage, null ); writer.write( configErrMsg ); } } else { @@ -214,20 +230,29 @@ bIdx++; } - retBuf.append('"'); + retBuf.append('='); inside=!inside; quotes=0; - while(bIdx < cmd.length()&"es!=2) { - if(cmd.charAt(bIdx)=='"') - quotes++; + boolean escaped=false; + for ( ; bIdx < cmd.length() && quotes != 2; bIdx++ ) { + char c = cmd.charAt(bIdx); + + // Need to skip escaped characters + if (c=='\\' && !escaped) { + escaped = true; + bIdx++; + continue; + } + escaped = false; - bIdx++; + if (c=='"') + quotes++; } } } - StringTokenizer str = new StringTokenizer(retBuf.toString(), "\""); + StringTokenizer str = new StringTokenizer(retBuf.toString(), "="); String[] retString = new String[str.countTokens()]; while(str.hasMoreTokens()) { @@ -243,15 +268,14 @@ * @param cmd a value of type 'StringBuffer' * @return a value of type 'String[]' */ - protected String[] parseParamValues(StringBuffer cmd, int start) { - int bIdx = start; - int i = 0; - int quotes = 0; + protected String[] parseParamValues(StringBuffer cmd, int start, int count) { + int valIndex = 0; boolean inside = false; - StringBuffer retBuf = new StringBuffer(); + String[] vals = new String[count]; + StringBuffer sb = new StringBuffer(); - while(bIdx < cmd.length()) { - if(!inside) { + for (int bIdx = start; bIdx < cmd.length(); bIdx++ ) { + if (!inside) { while(bIdx < cmd.length()&& cmd.charAt(bIdx)!='"') bIdx++; @@ -261,26 +285,43 @@ inside=!inside; } else { - while(bIdx < cmd.length() && cmd.charAt(bIdx)!='"') { - retBuf.append(cmd.charAt(bIdx)); - bIdx++; - } + boolean escaped=false; + for ( ; bIdx < cmd.length(); bIdx++) { - retBuf.append('"'); - inside=!inside; - } + char c = cmd.charAt(bIdx); - bIdx++; - } + // Check for escapes + if (c=='\\' && !escaped) { + escaped = true; + continue; + } + + // If we reach the other " then stop + if (c=='"' && !escaped) + break; + + // Since parsing of attributes and var + // substitution is done in separate places, + // we need to leave escape in the string + if (c=='$' && escaped) + sb.append( '\\' ); - StringTokenizer str = new StringTokenizer(retBuf.toString(), "\""); - String[] retString = new String[str.countTokens()]; + escaped = false; + sb.append(c); + } - while(str.hasMoreTokens()) { - retString[i++] = str.nextToken(); + // If we hit the end without seeing a quote + // the signal an error + if (bIdx == cmd.length()) + return null; + + vals[valIndex++] = sb.toString(); + sb.delete( 0, sb.length() ); // clear the buffer + inside=!inside; + } } - return retString; + return vals; } /** 1.3 +10 -8 jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/ssi/SSISet.java Index: SSISet.java =================================================================== RCS file: /home/cvs/jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/ssi/SSISet.java,v retrieving revision 1.2 retrieving revision 1.3 diff -u -r1.2 -r1.3 --- SSISet.java 24 Nov 2002 06:22:36 -0000 1.2 +++ SSISet.java 25 Nov 2002 10:15:42 -0000 1.3 @@ -70,6 +70,7 @@ /** * Implements the Server-side #set command * + * @author Paul Speed * @author Dan Sandberg * @version $Revision$, $Date$ */ @@ -77,10 +78,11 @@ /** * @see SSICommand */ - public void process(SSIMediator ssiMediator, - String[] paramNames, - String[] paramValues, - PrintWriter writer) throws SSIStopProcessingException { + public void process( SSIMediator ssiMediator, + String commandName, + String[] paramNames, + String[] paramValues, + PrintWriter writer) throws SSIStopProcessingException { String errorMessage = ssiMediator.getConfigErrMsg(); String variableName = null; 1.2 +5 -4 jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/ssi/SSIStopProcessingException.java Index: SSIStopProcessingException.java =================================================================== RCS file: /home/cvs/jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/ssi/SSIStopProcessingException.java,v retrieving revision 1.1 retrieving revision 1.2 diff -u -r1.1 -r1.2 --- SSIStopProcessingException.java 24 May 2002 04:38:58 -0000 1.1 +++ SSIStopProcessingException.java 25 Nov 2002 10:15:42 -0000 1.2 @@ -67,6 +67,7 @@ * Exception used to tell SSIProcessor that it should stop processing SSI commands. * This is used to mimick the Apache behavior in #set with invalid attributes. * + * @author Paul Speed * @author Dan Sandberg * @version $Revision$, $Date$ */ 1.1 jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/ssi/ExpressionParseTree.java Index: ExpressionParseTree.java =================================================================== /* * ExpressionParseTree.java * $Header: /home/cvs/jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/ssi/ExpressionParseTree.java,v 1.1 2002/11/25 10:15:42 dsandberg Exp $ * $Revision: 1.1 $ * $Date: 2002/11/25 10:15:42 $ * * ==================================================================== * * The Apache Software License, Version 1.1 * * Copyright (c) 1999 The Apache Software Foundation. All rights * reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. The end-user documentation included with the redistribution, if * any, must include the following acknowlegement: * "This product includes software developed by the * Apache Software Foundation (http://www.apache.org/)." * Alternately, this acknowlegement may appear in the software itself, * if and wherever such third-party acknowlegements normally appear. * * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software * Foundation" must not be used to endorse or promote products derived * from this software without prior written permission. For written * permission, please contact [EMAIL PROTECTED] * * 5. Products derived from this software may not be called "Apache" * nor may "Apache" appear in their names without prior written * permission of the Apache Group. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * ==================================================================== * * This software consists of voluntary contributions made by many * individuals on behalf of the Apache Software Foundation. For more * information on the Apache Software Foundation, please see * <http://www.apache.org/>. * * [Additional notices, if required by prior licensing conditions] * */ package org.apache.catalina.ssi; import java.util.LinkedList; import java.util.List; import java.text.ParseException; /** * Represents a parsed expression. * * @version $Revision: 1.1 $ * @author Paul Speed */ public class ExpressionParseTree { /** * Contains the current set of completed nodes. This * is a workspace for the parser. */ private LinkedList nodeStack = new LinkedList(); /** * Contains operator nodes that don't yet have values. * This is a workspace for the parser. */ private LinkedList oppStack = new LinkedList(); /** * The root node after the expression has been parsed. */ private Node root; /** * The SSIMediator to use when evaluating the expressions. */ private SSIMediator ssiMediator; /** * Creates a new parse tree for the specified expression. */ 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. */ public boolean evaluateTree() { return root.evaluate(); } /** * Pushes a new operator onto the opp stack, resolving existing * opps as needed. */ 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 = (OppNode)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. */ private void resolveGroup() { OppNode top = null; while ((top=(OppNode)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. */ 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 stategy to NOT_EQ above, except this // is NOT less than oppStack.add( 0, new LessThanNode() ); break; case ExpressionTokenizer.TOKEN_LE: pushOpp( new NotNode() ); // Similar stategy 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( "No nodes created.", et.getIndex() ); } if (nodeStack.size() > 1) { throw new ParseException( "Extra nodes created.", et.getIndex() ); } if (oppStack.size() != 0) { throw new ParseException( "Unused opp nodes exist.", et.getIndex() ); } root = (Node)nodeStack.get(0); } /** * A node in the expression parse tree. */ private abstract class Node { /** * Return true if the node evaluates to true. */ public abstract boolean evaluate(); } /** * A node the represents a String value */ private class StringNode extends Node { StringBuffer value; String resolved = null; public StringNode( String value ) { this.value = new StringBuffer(value); } /** * Resolves any variable references and returns the * value string. */ public String getValue() { if (resolved == null) resolved = ssiMediator.substituteVariables( value.toString() ) ; return resolved; } /** * Returns true if the string is not empty. */ public boolean evaluate() { return !(getValue().length() == 0); } 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. */ private abstract class OppNode extends Node { /** * The left branch. */ Node left; /** * The right branch. */ Node right; /** * Returns a preference 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. */ public void popValues( List values ) { right = (Node)values.remove(0); left = (Node)values.remove(0); } } private final class NotNode extends OppNode { public boolean evaluate() { return !left.evaluate(); } public int getPrecedence() { return PRECEDENCE_NOT; } /** * Overridden to pop only one value. */ public void popValues( List values ) { left = (Node)values.remove(0); } public String toString() { return left + " NOT"; } } private final class AndNode extends OppNode { public boolean evaluate() { if (!left.evaluate()) // Short circuit return false; return right.evaluate(); } public int getPrecedence() { return PRECEDENCE_LOGICAL; } public String toString() { return left + " " + right + " AND"; } } private final class OrNode extends OppNode { public boolean evaluate() { if (left.evaluate()) // Short circuit return true; return right.evaluate(); } public int getPrecedence() { return PRECEDENCE_LOGICAL; } 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(); return val1.compareTo(val2); } } private final class EqualNode extends CompareNode { public boolean evaluate() { return (compareBranches() == 0); } public int getPrecedence() { return PRECEDENCE_COMPARE; } public String toString() { return left + " " + right + " EQ"; } } private final class GreaterThanNode extends CompareNode { public boolean evaluate() { return (compareBranches() > 0); } public int getPrecedence() { return PRECEDENCE_COMPARE; } public String toString() { return left + " " + right + " GT"; } } private final class LessThanNode extends CompareNode { public boolean evaluate() { return (compareBranches() < 0); } public int getPrecedence() { return PRECEDENCE_COMPARE; } public String toString() { return left + " " + right + " LT"; } } } 1.1 jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/ssi/ExpressionTokenizer.java Index: ExpressionTokenizer.java =================================================================== /* * ExpressionTokenizer.java * $Header: /home/cvs/jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/ssi/ExpressionTokenizer.java,v 1.1 2002/11/25 10:15:42 dsandberg Exp $ * $Revision: 1.1 $ * $Date: 2002/11/25 10:15:42 $ * * ==================================================================== * * The Apache Software License, Version 1.1 * * Copyright (c) 1999 The Apache Software Foundation. All rights * reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. The end-user documentation included with the redistribution, if * any, must include the following acknowlegement: * "This product includes software developed by the * Apache Software Foundation (http://www.apache.org/)." * Alternately, this acknowlegement may appear in the software itself, * if and wherever such third-party acknowlegements normally appear. * * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software * Foundation" must not be used to endorse or promote products derived * from this software without prior written permission. For written * permission, please contact [EMAIL PROTECTED] * * 5. Products derived from this software may not be called "Apache" * nor may "Apache" appear in their names without prior written * permission of the Apache Group. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * ==================================================================== * * This software consists of voluntary contributions made by many * individuals on behalf of the Apache Software Foundation. For more * information on the Apache Software Foundation, please see * <http://www.apache.org/>. * * [Additional notices, if required by prior licensing conditions] * */ package org.apache.catalina.ssi; /** * Parses an expression string to return the individual tokens. * This is patterned similar to the StreamTokenizer in the JDK * but customized for SSI conditional expression parsing. * * @version $Revision: 1.1 $ * @author Paul Speed */ public class ExpressionTokenizer { public static final int TOKEN_STRING = 0; public static final int TOKEN_AND = 1; public static final int TOKEN_OR = 2; public static final int TOKEN_NOT = 3; public static final int TOKEN_EQ = 4; public static final int TOKEN_NOT_EQ = 5; public static final int TOKEN_RBRACE = 6; public static final int TOKEN_LBRACE = 7; public static final int TOKEN_GE = 8; public static final int TOKEN_LE = 9; public static final int TOKEN_GT = 10; public static final int TOKEN_LT = 11; public static final int TOKEN_END = 12; private char[] expr; private int tokenType = TOKEN_STRING; private String tokenVal = null; private int index; private int length; /** * Creates a new parser for the specified expression. */ public ExpressionTokenizer( String expr ) { this.expr = expr.trim().toCharArray(); this.length = this.expr.length; } /** * Returns true if there are more tokens. */ public boolean hasMoreTokens() { return index < length; } /** * Returns the current index for error reporting purposes. */ public int getIndex() { return index; } protected boolean isMetaChar( char c ) { return Character.isWhitespace( c ) || c == '(' || c == ')' || c == '!' || c == '<' || c == '>' || c == '|' || c == '&' || c == '='; } /** * Returns the next token type and initializes any * state variables accordingly. */ public int nextToken() { // Skip any leading white space while (index<length && Character.isWhitespace(expr[index])) index++; // Clear the current token val tokenVal = null; if (index == length) return TOKEN_END; // End of string int start = index; char currentChar = expr[index]; char nextChar = (char)0; index++; if (index < length) nextChar = expr[index]; // Check for a known token start switch (currentChar) { case '(': return TOKEN_LBRACE; case ')': return TOKEN_RBRACE; case '=': return TOKEN_EQ; case '!': if (nextChar == '=') { index++; return TOKEN_NOT_EQ; } else { return TOKEN_NOT; } case '|': if (nextChar == '|') { index++; return TOKEN_OR; } break; case '&': if (nextChar == '&') { index++; return TOKEN_AND; } break; case '>': if (nextChar == '=') { index++; return TOKEN_GE; // Greater than or equal } else { return TOKEN_GT; // Greater than } case '<': if (nextChar == '=') { index++; return TOKEN_LE; // Less than or equal } else { return TOKEN_LT; // Less than } default: // Otherwise it's a string break; } int end = index; // If it's a quoted string then end is the next unescaped quote if (currentChar == '"' || currentChar == '\'') { char endChar = currentChar; boolean escaped = false; start++; for ( ; index < length; index++) { if (expr[index] == '\\' && !escaped) { escaped = true; continue; } if (expr[index] == endChar && !escaped) break; escaped = false; } end = index; index++; // Skip the end quote } else { // End is the next whitespace character for ( ; index < length; index++) { if ( isMetaChar(expr[index]) ) break; } end = index; } // Extract the string from the array this.tokenVal = new String( expr, start, end - start ); return TOKEN_STRING; } /** * Returns the String value of the token if it was type * TOKEN_STRING. Otherwise null is returned. */ public String getTokenValue() { return tokenVal; } } 1.1 jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/ssi/SSIConditional.java Index: SSIConditional.java =================================================================== /* * SSIConditional.java * $Header: /home/cvs/jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/ssi/SSIConditional.java,v 1.1 2002/11/25 10:15:42 dsandberg Exp $ * $Revision: 1.1 $ * $Date: 2002/11/25 10:15:42 $ * * ==================================================================== * * The Apache Software License, Version 1.1 * * Copyright (c) 1999 The Apache Software Foundation. All rights * reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. The end-user documentation included with the redistribution, if * any, must include the following acknowlegement: * "This product includes software developed by the * Apache Software Foundation (http://www.apache.org/)." * Alternately, this acknowlegement may appear in the software itself, * if and wherever such third-party acknowlegements normally appear. * * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software * Foundation" must not be used to endorse or promote products derived * from this software without prior written permission. For written * permission, please contact [EMAIL PROTECTED] * * 5. Products derived from this software may not be called "Apache" * nor may "Apache" appear in their names without prior written * permission of the Apache Group. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * ==================================================================== * * This software consists of voluntary contributions made by many * individuals on behalf of the Apache Software Foundation. For more * information on the Apache Software Foundation, please see * <http://www.apache.org/>. * * [Additional notices, if required by prior licensing conditions] * */ package org.apache.catalina.ssi; import java.io.PrintWriter; import java.text.ParseException; import java.util.LinkedList; import java.util.List; import javax.servlet.ServletOutputStream; /** * SSI command that handles all conditional directives. * * @version $Revision: 1.1 $ * @author Paul Speed */ public class SSIConditional implements SSICommand { /** * @see SSICommand */ public void process( SSIMediator ssiMediator, String commandName, String[] paramNames, String[] paramValues, PrintWriter writer) throws SSIStopProcessingException { // Retrieve the current state information SSIConditionalState state = ssiMediator.getConditionalState(); if ( "if".equalsIgnoreCase( commandName ) ) { // Do nothing if we are nested in a false branch // except count it if ( state.processConditionalCommandsOnly ) { state.nestingCount++; return; } state.nestingCount = 0; // Evaluate the expression if ( evaluateArguments(paramNames, paramValues, ssiMediator) ) { // No more branches can be taken for this if block state.branchTaken = true; } else { // Do not process this branch state.processConditionalCommandsOnly = true; state.branchTaken = false; } } else if ( "elif".equalsIgnoreCase( commandName ) ) { // No need to even execute if we are nested in // a false branch if (state.nestingCount > 0) return; // If a branch was already taken in this if block // then disable output and return if ( state.branchTaken ) { state.processConditionalCommandsOnly = true; return; } // Evaluate the expression if ( evaluateArguments(paramNames, paramValues, ssiMediator) ) { // Turn back on output and mark the branch state.processConditionalCommandsOnly = false; state.branchTaken = true; } else { // Do not process this branch state.processConditionalCommandsOnly = true; state.branchTaken = false; } } else if ( "else".equalsIgnoreCase( commandName ) ) { // No need to even execute if we are nested in // a false branch if (state.nestingCount > 0) return; // If we've already taken another branch then // disable output otherwise enable it. state.processConditionalCommandsOnly = state.branchTaken; // And in any case, it's safe to say a branch // has been taken. state.branchTaken = true; } else if ( "endif".equalsIgnoreCase( commandName ) ) { // If we are nested inside a false branch then pop out // one level on the nesting count if (state.nestingCount > 0) { state.nestingCount--; return; } // Turn output back on state.processConditionalCommandsOnly = false; // Reset the branch status for any outer if blocks, // since clearly we took a branch to have gotten here // in the first place. state.branchTaken = true; } else { throw new SSIStopProcessingException(); //throw new SsiCommandException( "Not a conditional command:" + cmdName ); } } /** * Retrieves the expression from the specified arguments * and peforms the necessary evaluation steps. */ private boolean evaluateArguments( String[] names, String[] values, SSIMediator ssiMediator ) throws SSIStopProcessingException { String expr = getExpression( names, values ); if (expr == null) { throw new SSIStopProcessingException(); //throw new SsiCommandException( "No expression specified." ); } try { ExpressionParseTree tree = new ExpressionParseTree( expr, ssiMediator ); return tree.evaluateTree(); } catch (ParseException e) { //throw new SsiCommandException( "Error parsing expression." ); throw new SSIStopProcessingException(); } } /** * Returns the "expr" if the arg name is appropriate, otherwise * returns null. */ private String getExpression( String[] paramNames, String[] paramValues ) { if ( "expr".equalsIgnoreCase( paramNames[0]) ) return paramValues[0]; return null; } } 1.1 jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/ssi/SSIConditionalState.java Index: SSIConditionalState.java =================================================================== /* * SSIConditionalState.java * $Header: /home/cvs/jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/ssi/SSIConditionalState.java,v 1.1 2002/11/25 10:15:42 dsandberg Exp $ * $Revision: 1.1 $ * $Date: 2002/11/25 10:15:42 $ * * ==================================================================== * * The Apache Software License, Version 1.1 * * Copyright (c) 1999 The Apache Software Foundation. All rights * reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. The end-user documentation included with the redistribution, if * any, must include the following acknowlegement: * "This product includes software developed by the * Apache Software Foundation (http://www.apache.org/)." * Alternately, this acknowlegement may appear in the software itself, * if and wherever such third-party acknowlegements normally appear. * * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software * Foundation" must not be used to endorse or promote products derived * from this software without prior written permission. For written * permission, please contact [EMAIL PROTECTED] * * 5. Products derived from this software may not be called "Apache" * nor may "Apache" appear in their names without prior written * permission of the Apache Group. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * ==================================================================== * * This software consists of voluntary contributions made by many * individuals on behalf of the Apache Software Foundation. For more * information on the Apache Software Foundation, please see * <http://www.apache.org/>. * * [Additional notices, if required by prior licensing conditions] * */ package org.apache.catalina.ssi; /** * This class is used by SSIMediator and SSIConditional to keep track of state information necessary to process * the nested conditional commands ( if, elif, else, endif ). * * @version $Revision: 1.1 $ * @author Dan Sandberg * @author Paul Speed */ class SSIConditionalState { /** * Set to true if the current conditional has already been * completed, i.e.: a branch was taken. */ boolean branchTaken = false; /** * Counts the number of nested false branches. */ int nestingCount = 0; /** * Set to true if only conditional commands ( if, elif, else, endif ) should be processed. */ boolean processConditionalCommandsOnly = false; } 1.85 +36 -0 jakarta-tomcat-4.0/tester/src/bin/tester.xml Index: tester.xml =================================================================== RCS file: /home/cvs/jakarta-tomcat-4.0/tester/src/bin/tester.xml,v retrieving revision 1.84 retrieving revision 1.85 diff -u -r1.84 -r1.85 --- tester.xml 24 Nov 2002 06:22:36 -0000 1.84 +++ tester.xml 25 Nov 2002 10:15:43 -0000 1.85 @@ -1849,6 +1849,42 @@ golden="${golden.path}/SSIFsize02.txt"/> <tester host="${host}" port="${port}" protocol="${protocol}" + request="${context.path}/SSIConditional01.shtml" debug="${debug}" + golden="${golden.path}/SSIConditional01.txt"/> + + <tester host="${host}" port="${port}" protocol="${protocol}" + request="${context.path}/SSIConditional02.shtml" debug="${debug}" + golden="${golden.path}/SSIConditional02.txt"/> + + <tester host="${host}" port="${port}" protocol="${protocol}" + request="${context.path}/SSIConditional03.shtml" debug="${debug}" + golden="${golden.path}/SSIConditional03.txt"/> + + <tester host="${host}" port="${port}" protocol="${protocol}" + request="${context.path}/SSIConditional04.shtml" debug="${debug}" + golden="${golden.path}/SSIConditional04.txt"/> + + <tester host="${host}" port="${port}" protocol="${protocol}" + request="${context.path}/SSIConditional05.shtml" debug="${debug}" + golden="${golden.path}/SSIConditional05.txt"/> + + <tester host="${host}" port="${port}" protocol="${protocol}" + request="${context.path}/SSIConditional06.shtml" debug="${debug}" + golden="${golden.path}/SSIConditional06.txt"/> + + <tester host="${host}" port="${port}" protocol="${protocol}" + request="${context.path}/SSIConditional07.shtml" debug="${debug}" + golden="${golden.path}/SSIConditional07.txt"/> + + <tester host="${host}" port="${port}" protocol="${protocol}" + request="${context.path}/SSIConditional08.shtml" debug="${debug}" + golden="${golden.path}/SSIConditional08.txt"/> + + <tester host="${host}" port="${port}" protocol="${protocol}" + request="${context.path}/SSIConditional09.shtml" debug="${debug}" + golden="${golden.path}/SSIConditional09.txt"/> + + <tester host="${host}" port="${port}" protocol="${protocol}" request="${context.path}/SSIVarSub01.shtml" debug="${debug}" golden="${golden.path}/SSIVarSub01.txt"/> 1.1 jakarta-tomcat-4.0/tester/web/SSIConditional09.shtml Index: SSIConditional09.shtml =================================================================== 1 <!--#if expr="1=2" --> a ##<!--#if expr="1=2" --> b ##<!--#else --> c ##<!--#endif --> d <!--#elif expr="2=2" --> e ##<!--#if expr="2=2" --> f ####<!--#if expr="1=2" --> **11 ####<!--#elif expr="2=3" --> **22 ####<!--#endif --> **33 ##<!--#else --> g ##<!--#endif --> h <!--#else --> i ##<!--#if expr="1=1" --> j ##<!--#else --> k ##<!--#endif --> l <!--#endif --> # now we test extra #endif commands <!--#endif --> n <!--#endif --> o 1.1 jakarta-tomcat-4.0/tester/web/golden/SSIConditional09.txt Index: SSIConditional09.txt =================================================================== 1 e ## f #### **33 ## h # now we test extra #endif commands n o
-- To unsubscribe, e-mail: <mailto:[EMAIL PROTECTED]> For additional commands, e-mail: <mailto:[EMAIL PROTECTED]>