Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 93 additions & 3 deletions modules/core/src/main/java/org/apache/synapse/mediators/Value.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,23 @@
import org.apache.synapse.SynapseException;
import org.apache.synapse.util.xpath.SynapseXPath;
import org.jaxen.JaxenException;
import org.jaxen.expr.BinaryExpr;
import org.jaxen.expr.Expr;
import org.jaxen.expr.FilterExpr;
import org.jaxen.expr.FunctionCallExpr;
import org.jaxen.expr.LocationPath;
import org.jaxen.expr.PathExpr;
import org.jaxen.expr.Predicate;
import org.jaxen.expr.Step;
import org.jaxen.expr.UnaryExpr;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

/**
* Represents a Value
Expand All @@ -40,6 +53,16 @@
public class Value {
private static final Log log = LogFactory.getLog(Value.class);

/**
* Functions that a re-compiled dynamic ("{{...}}") template parameter expression is not
* allowed to call. document() resolves an external resource and evaluate() compiles and
* runs a further expression at runtime; neither is evaluated for an expression that was
* itself produced from a runtime-derived value. Such a call is reported as an error.
*/
private static final Set<String> RESTRICTED_DYNAMIC_FUNCTIONS =
Collections.unmodifiableSet(
new HashSet<String>(Arrays.asList("document", "evaluate")));

/**
* Name of the value attribute
*/
Expand Down Expand Up @@ -195,23 +218,90 @@ public boolean hasPropertyEvaluateExpr(){

public Object evalutePropertyExpression(MessageContext synCtx) {
SynapseXPath expr = null;
SynapseXPath expression;
try {
expr = new SynapseXPath(this.keyValue.substring(1, keyValue.length() - 1));
for (OMNamespace aNamespaceList : namespaceList) {
expr.addNamespace(aNamespaceList);
}

String result = expr.stringValueOf(synCtx);
SynapseXPath expression = new SynapseXPath(result);
expression = new SynapseXPath(result);
for (OMNamespace aNamespaceList : namespaceList) {
expression.addNamespace(aNamespaceList);
}
return expression;
} catch (Exception e) {
handleException("Can not evaluate escaped expression : " + expr);
return this.expression;
}
return this.expression;

String restrictedFunction = findRestrictedFunction(expression.getRootExpr());
if (restrictedFunction != null) {
handleException("The function '" + restrictedFunction + "' is not allowed in a "
+ "dynamically evaluated template parameter expression");
}
return expression;
}

/**
* Walks a parsed XPath expression tree and returns the name of the first function call
* that is in {@link #RESTRICTED_DYNAMIC_FUNCTIONS}, or {@code null} if there is none. The
* whole tree is inspected, including function arguments and step predicates.
*
* @param node a node of the parsed expression tree (an {@link Expr}, {@link Step} or
* {@link Predicate}); may be {@code null}
* @return the restricted function name if one is present, otherwise {@code null}
*/
private String findRestrictedFunction(Object node) {
if (node == null) {
return null;
}
if (node instanceof FunctionCallExpr) {
FunctionCallExpr call = (FunctionCallExpr) node;
String prefix = call.getPrefix();
String functionName = call.getFunctionName();
if ((prefix == null || prefix.isEmpty()) && functionName != null
&& RESTRICTED_DYNAMIC_FUNCTIONS.contains(functionName)) {
return functionName;
}
return findRestrictedFunction(call.getParameters());
}
if (node instanceof PathExpr) {
PathExpr path = (PathExpr) node;
String found = findRestrictedFunction(path.getFilterExpr());
return found != null ? found : findRestrictedFunction(path.getLocationPath());
}
if (node instanceof FilterExpr) {
FilterExpr filter = (FilterExpr) node;
String found = findRestrictedFunction(filter.getExpr());
return found != null ? found : findRestrictedFunction(filter.getPredicates());
}
if (node instanceof BinaryExpr) {
BinaryExpr binary = (BinaryExpr) node;
String found = findRestrictedFunction(binary.getLHS());
return found != null ? found : findRestrictedFunction(binary.getRHS());
}
if (node instanceof UnaryExpr) {
return findRestrictedFunction(((UnaryExpr) node).getExpr());
}
if (node instanceof LocationPath) {
return findRestrictedFunction(((LocationPath) node).getSteps());
}
if (node instanceof Step) {
return findRestrictedFunction(((Step) node).getPredicates());
}
if (node instanceof Predicate) {
return findRestrictedFunction(((Predicate) node).getExpr());
}
if (node instanceof List) {
for (Object child : (List<?>) node) {
String found = findRestrictedFunction(child);
if (found != null) {
return found;
}
}
}
return null;
}

public void setNamespaces(OMElement elem){
Expand Down
101 changes: 101 additions & 0 deletions modules/core/src/test/java/org/apache/synapse/mediators/ValueTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* 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.synapse.mediators;

import junit.framework.TestCase;
import org.apache.synapse.MessageContext;
import org.apache.synapse.SynapseException;
import org.apache.synapse.util.xpath.SynapseXPath;

/**
* Tests for {@link Value#evalutePropertyExpression(MessageContext)}, which evaluates a dynamic
* ("{{...}}") template parameter once and re-compiles the resulting string as a new XPath
* expression. The re-compiled expression keeps working for ordinary functions, but a
* {@code document()} or {@code evaluate()} call in it is reported as an error instead of being
* turned into an evaluatable expression.
*/
public class ValueTest extends TestCase {

/**
* Builds an escaped-expression {@link Value} whose first evaluation returns exactly
* {@code recompiledExpression}. The inner text of the {{...}} form is wrapped as an XPath
* string literal, so the result does not depend on any property or message content.
*
* @param recompiledExpression the XPath text the first evaluation should yield
* @return a Value that re-compiles {@code recompiledExpression} on evaluation
*/
private Value escapedValueYielding(String recompiledExpression) {
return new Value("{'" + recompiledExpression + "'}");
}

private void assertRejected(String recompiledExpression) throws Exception {
MessageContext synCtx = TestUtils.getTestContext("<test/>");
try {
escapedValueYielding(recompiledExpression).evalutePropertyExpression(synCtx);
fail("expected a SynapseException for re-compiled expression: " + recompiledExpression);
} catch (SynapseException expected) {
// the re-compiled expression calls a restricted function and must not be evaluated
}
}

/** A re-compiled expression without a restricted function must still be returned. */
public void testBenignExpressionIsStillRecompiled() throws Exception {
MessageContext synCtx = TestUtils.getTestContext("<test/>");
Object result =
escapedValueYielding("concat(\"F010\",\"-\",\"PWN\")").evalutePropertyExpression(synCtx);
assertTrue("a re-compiled expression without a restricted function must be returned as "
+ "a SynapseXPath", result instanceof SynapseXPath);
}

public void testDocumentFunctionIsRejected() throws Exception {
assertRejected("document(\"http://example.com/f010\")");
}

public void testDocumentFunctionTwoArgFormIsRejected() throws Exception {
assertRejected("document(\"http://example.com/f010\", /)");
}

public void testEvaluateFunctionIsRejected() throws Exception {
assertRejected("evaluate(\"count(//a)\")");
}

public void testDocumentNestedInFunctionArgumentIsRejected() throws Exception {
assertRejected("concat(document(\"http://example.com/f010\"), \"\")");
}

public void testDocumentWrappedInFunctionIsRejected() throws Exception {
assertRejected("string(document(\"http://example.com/f010\"))");
}

public void testDocumentInStepPredicateIsRejected() throws Exception {
assertRejected("//a[document(\"http://example.com/f010\")]");
}

/**
* Only the unprefixed document()/evaluate() are restricted; a namespaced function that
* happens to share the local name is a different function and is left alone.
*/
public void testNamespacedDocumentIsNotRestricted() throws Exception {
MessageContext synCtx = TestUtils.getTestContext("<test/>");
Object result = escapedValueYielding("ns:document(\"x\")").evalutePropertyExpression(synCtx);
assertTrue("a namespaced function sharing the local name must not be restricted",
result instanceof SynapseXPath);
}
}
Loading