/* * Copyright (C) 2000 - 2010 TagServlet Ltd * * This file is part of Open BlueDragon (OpenBD) CFML Server Engine. * * OpenBD is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * Free Software Foundation,version 3. * * OpenBD is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OpenBD. If not, see http://www.gnu.org/licenses/ * * Additional permission under GNU GPL version 3 section 7 * * If you modify this Program, or any covered work, by linking or combining * it with any of the JARS listed in the README.txt (or a modified version of * (that library), containing parts covered by the terms of that JAR, the * licensors of this Program grant you additional permission to convey the * resulting work. * README.txt @ http://www.openbluedragon.org/license/README.txt * * http://www.openbluedragon.org/ */ package com.naryx.tagfusion.cfm.parser; import java.io.Serializable; import java.util.ArrayList; import java.util.BitSet; import java.util.List; import java.util.Vector; import org.antlr.runtime.Token; import com.naryx.tagfusion.cfm.engine.cfArgStructData; import com.naryx.tagfusion.cfm.engine.cfComponentData; import com.naryx.tagfusion.cfm.engine.cfData; import com.naryx.tagfusion.cfm.engine.cfJavaObjectData; import com.naryx.tagfusion.cfm.engine.cfNumberData; import com.naryx.tagfusion.cfm.engine.cfQueryColumnData; import com.naryx.tagfusion.cfm.engine.cfQueryResultData; import com.naryx.tagfusion.cfm.engine.cfStringData; import com.naryx.tagfusion.cfm.engine.cfWSObjectData; import com.naryx.tagfusion.cfm.engine.cfcMethodData; import com.naryx.tagfusion.cfm.engine.cfmRunTimeException; import com.naryx.tagfusion.cfm.parser.script.userDefinedFunction; import com.naryx.tagfusion.cfm.xml.cfXmlData; import com.naryx.tagfusion.cfm.xml.cfXmlDataArray; public class cfFullVarExpression extends CFVarExpression implements Serializable { private static final long serialVersionUID = 1; private StringBuilder image; private Token token; private List<CFExpression> expressions; // if true, this variable references the client or cookie scope. These scopes // cannot contain structs but can contain variables with names containing periods. private boolean isCookieClientVariable; /** * The operator list is represented as a BitSet since there are only 2 * operators when it comes to the variables - the '.' and the '[' '.' is * represented as 1 and '[' is represented as 0 in the BitSet */ private BitSet operators; private static final boolean DOT = true, LBRACKET = false; private int exprSize; public cfFullVarExpression( Token _t, CFExpression _main, String _image ) { super(_t); token = _t; image = new StringBuilder(_image); expressions = new ArrayList<CFExpression>(); expressions.add(_main); operators = new BitSet(8); exprSize = 0; isCookieClientVariable = false; if ( _main instanceof CFIdentifier ) { String name = ((CFIdentifier) _main).getName().toLowerCase(); if ( name.equals("cookie") || name.equals("client") ) { isCookieClientVariable = true; } } } public byte getType() { return expressions.get(expressions.size() - 1).getType(); } public boolean isEscapeSingleQuotes() { return expressions.get(expressions.size() - 1).isEscapeSingleQuotes(); } public void addBracketOperation( CFExpression _right ) { // if this is a cookie/client var we want to combine the struct key expressions.add(_right); exprSize++; image.append('['); image.append(_right.Decompile(0)); image.append(']'); } public void addDotOperation( CFExpression _right ) { if ( isCookieClientVariable ) { // a client/cookie expression with only '.'s // should be at most 2 subexpressions - // 'cookie' and variable name in the cookie // scope if ( expressions.size() == 2 && operators.get(1) == DOT ) { CFExpression exp = expressions.get(1); expressions.remove(1); String subImage = exp.Decompile(0) + "." + _right.Decompile(0); expressions.add(new CFIdentifier(token, subImage.toString())); } else { expressions.add(_right); exprSize++; operators.set(exprSize); image.append('.'); image.append(_right.Decompile(0)); } } else { expressions.add(_right); exprSize++; operators.set(exprSize); image.append('.'); image.append(_right.Decompile(0)); } } public cfData Eval( CFContext _context ) throws cfmRunTimeException { setLineCol(_context); // INVARIANT:- operators.size() == expressions.size() - 1; cfData leftData = null; // -- first attempt // evaluate the lhs so we have a cfData to begin the loop with leftData = evalNatural(_context, false); // -- loop thru the combinations of possibles // at this point we know the whole expression doesn't evaluate if ( leftData == null ) { leftData = evalBruteForce(_context); if ( leftData.getDataType() == cfData.CFLDATA ) { if ( !((cfLData) leftData).exists() ) { leftData = null; } else { try { extIndirectReferenceData tmp2 = (extIndirectReferenceData) evalNatural( _context, true); leftData = new combinedCFLData(tmp2, (cfLData) leftData, image.toString()); } catch (cfmRunTimeException e) { // doesn't exist leftData = null; } } } } // -- if we still don't have a value then create try // evaluation with the variable being created if required. if ( leftData == null ) { leftData = evalNatural(_context, true); } _context._lastExpr = leftData; if ( indirect && (_context._lastExpr.getDataType() == cfData.CFLDATA) ) { _context._lastExpr = ((cfLData) _context._lastExpr).Get(_context); } return _context._lastExpr; } private CFExpression combine( int _start, int _end ) { StringBuilder newImage = new StringBuilder(); newImage.append( expressions.get(_start).Decompile(0) ); boolean nextOp; for (int i = _start + 1; i <= _end; i++) { nextOp = operators.get(i); if ( nextOp ) { newImage.append('.'); newImage.append( expressions.get(i).Decompile(0) ); } else { newImage.append('['); newImage.append( expressions.get(i).Decompile(0) ); newImage.append(']'); } } return new CFIdentifier(token, newImage.toString()); } private cfData evalBruteForce( CFContext _context ) throws cfmRunTimeException { int expressionCount = 1; // number of expressions that have been combined // from the LHS cfData nextLHS; cfData result = null; CFExpression nextExpression = expressions.get(0); int noExprs = expressions.size(); // loop thru expressions on the lhs combining those do { try { nextLHS = evalLHS(_context, nextExpression, true); if ( nextLHS != null ) result = recurseLeft(_context, expressionCount - 1, nextLHS); } catch (cfmRunTimeException ignore) { } nextExpression = combine(0, expressionCount); expressionCount++; } while (result == null && expressionCount < noExprs); if ( result == null ) { // last expression ["a.b.c...."] result = evalLHS(_context, nextExpression, true); } return result; } private cfData recurseLeft( CFContext _context, int _leftPtr, cfData _leftData ) throws cfmRunTimeException { int rightPtr = _leftPtr + 1; int leftPtr = _leftPtr; cfData leftData = _leftData; // exit clause if ( rightPtr == expressions.size() ) { return _leftData; } else if ( leftData.getDataType() == cfData.CFLDATA ) { if ( ((cfLData) leftData).exists() ) { leftData = ((cfLData) leftData).Get(_context); } else {// doesn't exist return null; } } CFExpression nextExpression = expressions.get(rightPtr); int joinCount = 1; boolean op; // loop thru the expressions on the RHS of the expression ptr while (rightPtr + joinCount <= expressions.size()) { op = operators.get(leftPtr + 1); cfData subresult = Eval(_context, leftData, 0, nextExpression, op, true, false); if ( subresult != null ) { subresult = recurseLeft(_context, leftPtr + joinCount, subresult); if ( subresult != null ) { return subresult; } } // if we have more combinations to try, create a new nextExpression // for the next iteration if ( rightPtr + joinCount < expressions.size() ) { nextExpression = combine(rightPtr, rightPtr + joinCount); } joinCount++; } return null; } private cfData evalNatural( CFContext _context, boolean _create ) throws cfmRunTimeException { CFExpression rightExp; CFExpression leftExp = expressions.get(0); boolean op; cfData leftData = evalLHS(_context, leftExp, true); int noExprs = expressions.size(); for (int i = 1; i < noExprs; i++) { // apply the RH expression to the cfData rightExp = expressions.get(i); op = operators.get(i); leftData = Eval(_context, leftData, i, rightExp, op, true, _create); if ( leftData == null ) { break; } } if ( leftData == null ) { leftData = evalLHS(_context, leftExp, false); for (int i = 1; i < noExprs; i++) { // apply the RH expression to the cfData rightExp = expressions.get(i); op = operators.get(i); leftData = Eval(_context, leftData, i, rightExp, op, false, _create); if ( leftData == null ) { break; } } } return leftData; } private static cfData evalLHS( CFContext _context, CFExpression _leftExp, boolean _doQuerySearch ) throws cfmRunTimeException { cfData leftVal = null; if ( _doQuerySearch ) { leftVal = _leftExp.Eval(_context); } else { if ( _leftExp instanceof CFIdentifier ) { leftVal = ((CFIdentifier) _leftExp).Eval(_context, false); } else { leftVal = _leftExp.Eval(_context); } } return leftVal; } private cfData Eval( CFContext _context, cfData _leftData, int _exprIndex, CFExpression _right, boolean _op, boolean _doquerysearch, boolean _create ) throws cfmRunTimeException { cfData leftVal = _leftData; cfData val = null; // the result of this operation cfQueryResultData query = null; // on most occasions we want to evaluate the LHS with a query scope search // included boolean isIndirQuery = (leftVal instanceof indirectQueryReferenceData); boolean isJavaMethodCall = (_right instanceof CFJavaMethodExpression); if ( !_create ) { if ( leftVal.getDataType() == cfData.CFLDATA ) { cfLData lData = (cfLData) leftVal; if ( lData.exists() ) { query = lData.getQueryResult(); leftVal = lData.Get(_context); } else { return null; } } } else { if ( !isJavaMethodCall ) { // if it's a cfLData and hasn't already been created as a // extIndirectReferenceData if ( leftVal.getDataType() == cfData.CFLDATA && !(leftVal instanceof extIndirectReferenceData) ) { if ( ((cfLData) leftVal).exists() ) { query = ((cfLData) leftVal).getQueryResult(); leftVal = ((cfLData) leftVal).Get(_context); } else { // create new extendedIndirectReference with no index // (the index will be added later on when it comes to evaluation) leftVal = new extIndirectReferenceData((cfLData) leftVal, getImage(_exprIndex)); } } } else if ( leftVal.getDataType() == cfData.CFLDATA ) { query = ((cfLData) leftVal).getQueryResult(); leftVal = ((cfLData) leftVal).Get(_context); } } // Java method call if ( isJavaMethodCall && leftVal instanceof cfJavaObjectData ) { if ( leftVal instanceof cfComponentData ) { cfArgStructData namedArgs = (cfArgStructData) _right.Eval(_context); // evaluate // the // arguments if ( namedArgs != null ) { cfcMethodData invocationData = new cfcMethodData(_context .getSession(), ((CFJavaMethodExpression) _right) .getFunctionName(), namedArgs); val = ((cfComponentData) leftVal).invokeComponentFunction(_context .getSession(), invocationData); } else { val = ((cfComponentData) leftVal).invokeComponentFunction(_context .getSession(), (CFJavaMethodExpression) _right); } } else if ( leftVal instanceof cfWSObjectData ) { _right.Eval(_context); // evaluate the arguments val = ((cfWSObjectData) leftVal).getWSData((CFJavaMethodExpression) _right, _context ); } else { // see if it's a CFC method call within the super scope (or some other // scope) cfData udf = leftVal.getData(((CFJavaMethodExpression) _right).getFunctionName()); if ( udf instanceof userDefinedFunction ) { cfArgStructData evaluatedArgs = (cfArgStructData) ((CFJavaMethodExpression) _right).Eval( _context ); if ( evaluatedArgs != null ){ val = ((userDefinedFunction) udf).execute(_context.getSession(), evaluatedArgs, true ); }else{ val = ((userDefinedFunction) udf).execute(_context.getSession(), ((CFJavaMethodExpression) _right).getEvaluatedArguments(_context, true)); } } else { // treat it as a method call on a Java object _right.Eval(_context); // evaluate the arguments val = ((cfJavaObjectData) leftVal).getJavaData( (CFJavaMethodExpression) _right, _context); } } // Java field access } else if ( leftVal.getDataType() == cfData.CFJAVAOBJECTDATA && _right instanceof CFIdentifier ) { val = ((cfJavaObjectData) leftVal).getJavaData(_op == DOT ? new cfStringData(((CFIdentifier) _right).getName()) : evaluateRHS(_context, _right)); } else if ( leftVal.getDataType() == cfData.CFJAVAOBJECTDATA && _op == LBRACKET ) { val = ((cfJavaObjectData) leftVal).getJavaData(evaluateRHS(_context, _right)); } // component property field access else if ( leftVal.getDataType() == cfData.CFCOMPONENTOBJECTDATA && _right instanceof CFIdentifier ) { if ( _op == DOT ) { val = new indirectReferenceData(_context.getSession(), Decompile(0), leftVal, new cfStringData(((CFIdentifier) _right).getName())); } else { // LBRACKET - must be an expression that indicates the index val = new indirectReferenceData(_context.getSession(), Decompile(0), leftVal, evaluateRHS(_context, _right)); } } else if ( leftVal.getDataType() == cfData.CFCOMPONENTOBJECTDATA && _op == LBRACKET ) { val = new indirectReferenceData(_context.getSession(), Decompile(0), leftVal, evaluateRHS(_context, _right)); } else if ( _create && leftVal instanceof extIndirectReferenceData ) { // add the index to the extIndirectReferenceData if ( _op == DOT ) { ((extIndirectReferenceData) leftVal).addIndex_Dot(new cfStringData( ((CFIdentifier) _right).getName())); } else { ((extIndirectReferenceData) leftVal).addIndex_Bracket(evaluateRHS( _context, _right)); } val = leftVal; // Query field but using [] notation to get at rows // Note:- this skips over if the LHS was evaluated as an // indirectQueryReferenceData // to avoid the case where we have query.arrayfield[1][1] } else if ( _op == LBRACKET && (query != null || (leftVal.getQueryTableData() != null && leftVal .getDataType() != cfData.CFQUERYRESULTDATA)) && !isIndirQuery ) { int rowIndex; try { cfData rowIndexData = evaluateRHS(_context, _right); if ( rowIndexData == null ) { throw new CFException("Invalid index to query column: " + _right.Decompile(0), _context); } rowIndex = rowIndexData.getNumber().getInt(); } catch (cfmRunTimeException e) { throw new CFException( "Invalid index to query column. " + _right.Decompile(0) + " cannot be evaluated to an integer value.", _context); } int colIndex = 0; List queryData; if ( query != null && _leftData instanceof indirectReferenceData ) { queryData = query.getQueryTableData(); colIndex = query.getColumnIndexCF(((indirectReferenceData) _leftData).getIndex().getString()); } else { queryData = leftVal.getQueryTableData(); colIndex = leftVal.getQueryColumn(); } val = new indirectQueryReferenceData(Decompile(0), queryData, rowIndex, colIndex); // XML field access, if using [some_number], convert to a cfXmlDataArray } else if ( leftVal instanceof cfXmlData && _op == LBRACKET && (val = evaluateXmlData(_context, _right, leftVal)) != null ) { // do nothing, since val has already been assigned // STRUCT / QUERY field access using . or [] } else if ( leftVal.getDataType() == cfData.CFSTRUCTDATA || leftVal.getDataType() == cfData.CFQUERYRESULTDATA ) { if ( _op == DOT ) { val = new indirectReferenceData(Decompile(0), leftVal, new cfStringData(((CFIdentifier) _right).getName())); } else { // LBRACKET - must be an expression that indicates the index if ( leftVal.getDataType() == cfData.CFQUERYRESULTDATA ) { return new cfQueryColumnData((cfQueryResultData) leftVal, evaluateRHS(_context, _right)); } else { val = new indirectReferenceData(Decompile(0), leftVal, evaluateRHS( _context, _right)); } } // ARRAY } else if ( leftVal.getDataType() == cfData.CFARRAYDATA ) { // create indirect reference cfData index = evaluateRHS(_context, _right); if ( index != null ) { val = new indirectReferenceData(Decompile(0), leftVal, index); } else if ( leftVal instanceof cfXmlDataArray ) { // If this is a cfXmlDataArray and there's no [x] index, assume [1]. // So we must sneak in an xmlArray[1] eval to use with the actual // rhs expression val = new indirectReferenceData(Decompile(0), leftVal, new cfNumberData(1)); _context._lastExpr = val; val = Eval(_context, ((indirectReferenceData) val).Get(_context), _exprIndex, _right, _op, _doquerysearch, _create); }else{ throw new CFException( "Invalid index to array. " + _right.Decompile( 0 ) + " does not exist.", _context ); } } else if ( _doquerysearch ) { // eval() again but without the query scope checking // val = Eval( _context, _leftData, _right, _op, false, _create ); // do nothing - null will be returned and subsequently a this // method can be called again with _doquerysearch = false // otherwise it's not valid } else { throw new CFException("Invalid expression. [" + image + "]", _context); } return val; } private cfData evaluateXmlData( CFContext _context, CFExpression _right, cfData leftVal ) throws cfmRunTimeException { cfData index = evaluateRHS(_context, _right); if ( (index != null) && (index.getDataType() == cfData.CFNUMBERDATA) ) { Vector<cfData> list = new Vector<cfData>(1); list.add(leftVal); // We need to support Document node types and orphened (no parent) // node types as well cfXmlData parent = ((cfXmlData) leftVal).getParent(); // May be null cfData newLeftVal = new cfXmlDataArray(parent, list); return new indirectReferenceData(Decompile(0), newLeftVal, index); } else { return null; } } private static cfData evaluateRHS( CFContext _context, CFExpression _right ) throws cfmRunTimeException { cfData rightVal = _right.Eval(_context); if ( rightVal.getDataType() == cfData.CFLDATA ) { if ( ((cfLData) rightVal).exists() ) { rightVal = ((cfLData) rightVal).Get(_context); } else { rightVal = null; } } return rightVal; } private String getImage( int _exprIndex ) { StringBuilder sb = new StringBuilder(); sb.append( expressions.get(0).Decompile(0) ); for (int i = 1; i < _exprIndex; i++) { if ( operators.get(i) ) { sb.append('.'); sb.append( expressions.get(i).Decompile(0) ); } else { sb.append('['); sb.append( expressions.get(i).Decompile(0) ); sb.append(']'); } } return sb.toString(); } public String Decompile( int indent ) { return image.toString(); } }