From 07127247426761289c1646715b1b5b6a42f3e300 Mon Sep 17 00:00:00 2001 From: bhmohanr-techie Date: Thu, 13 Oct 2022 17:33:10 +0530 Subject: [PATCH] # Issue ID: CVE-2022-41852 # Issue Description: Those using JXPath to interpret untrusted XPath expressions may be vulnerable to a remote code execution attack. All JXPathContext class functions processing a XPath string are vulnerable except compile() and compilePath() function. The XPath expression can be used by an attacker to load any Java class from the classpath resulting in code execution. # CVSS Version 3.x: 9.8 Critical # Affected versions: 1.3 and earlier # Vulnerability Type: Remote Code Execution (RCE) # Steps to Reproduce: - Issue reported here is, all functions in the class JXPathContext (except compile and compilePath) are vulnerable to a remote code execution attack. - An arbitrary code can be injected in the xpath values passed to these functions, and it allows triggering java classes that can exploit the target machine. - For instance, the iterate() method in the JXPathContext class, can be invoked by passing the xpath argument value as, java.lang.Thread.sleep(9999999) or java.lang.Class.forName("ExploitTest"). These examples can result in triggering the injected java code, and can exploit the target machine. - Example: JXPathContext context = JXPathContext.newContext(new Test() ); Iterator result = context.iterate("java.lang.Thread.sleep(9999999)"); System.out.println("result.hasNext() - " + result.hasNext()); # Workarounds: - No workaround available # Fix: Please note that, the fix added here is via a new system property (enabling this property, will address the issue). This will ensure that we are not breaking functionality for existing users. - In order to fix this issue, a filter class "JXPathFilter.java" is added in JXPath. This filter object will be used to validate if the xpath passed to JXPath is a valid one - This new filter class, for now implements JXPathClassFilter interface, which is used to check the java classes passed in xpath. In future, we could implement any other interface, to verify other cases as well. - In this filter, the class filter validates to see if the class being loaded in JXPath is part of the restriction list. The restriction can be configured via a system property "jxpath.class.deny" - System property "jxpath.class.deny" can be set to specify the list of restricted classnames. This property takes a list of java classnames (use comma as separator to specify more than one class). If this property is not set, it exposes any java class to javascript Example: jxpath.class.deny=java.lang.Runtime will deny exposing java.lang.Runtime class via xpath, while all other classes will be exposed. - When a new extension function is created by JXPath, it uses the filter to check if the xpath supplied is a valid string, only then function instances are created. - The changes are added to pass JXPathFilter instance as an additional argument to the methods that create the ExtensionFunction objects. In addition, the ClassLoaderUtil class is modified to use the JXPathFilter instance when a new Class instance is created. Please note that, the changes here are added as overloaded methods, so that we are not affecting existing code for anyone. # Unit Testing: - Verified that the RCE vulnerability is no more applicable for any of the functions in the JXPathContext class. - Verified different code areas and found that the fix is addressed in all cases. Testing covers Class Functions, Package Funcions, Function Libraries and functions exposed in JXPathContext. - All the test cases for this change is covered in unit test code: in ExtensionFunctionTest.java. 10 new test methods are added in this class, to make sure all possible areas are covered. # Summary: - The changes added in the pull request, takes effect only if the newly added System property "jxpath.class.deny" is set. This ensures that existing users of jxpath are not affected by this change. - The new system property can be configured based on the environment and user needs. - In order to fix CVE-2022-41852, we need to configure "jxpath.class.deny=java.lang.Class" which will ensure that Class.forName() call is blocked when passed via xpath strings. --- .../apache/commons/jxpath/ClassFunctions.java | 23 +++ .../commons/jxpath/FunctionLibrary.java | 25 ++- .../org/apache/commons/jxpath/Functions.java | 13 ++ .../commons/jxpath/PackageFunctions.java | 36 +++- .../commons/jxpath/ri/JXPathClassFilter.java | 40 +++++ .../jxpath/ri/JXPathContextReferenceImpl.java | 19 +-- .../commons/jxpath/ri/JXPathFilter.java | 67 ++++++++ .../commons/jxpath/util/ClassLoaderUtil.java | 82 ++++++++- .../ri/compiler/ExtensionFunctionTest.java | 155 ++++++++++++++++-- .../jxpath/ri/compiler/TestFunctions3.java | 55 +++++++ 10 files changed, 473 insertions(+), 42 deletions(-) create mode 100644 src/main/java/org/apache/commons/jxpath/ri/JXPathClassFilter.java create mode 100644 src/main/java/org/apache/commons/jxpath/ri/JXPathFilter.java create mode 100644 src/test/java/org/apache/commons/jxpath/ri/compiler/TestFunctions3.java diff --git a/src/main/java/org/apache/commons/jxpath/ClassFunctions.java b/src/main/java/org/apache/commons/jxpath/ClassFunctions.java index dbe29237f..e209400be 100644 --- a/src/main/java/org/apache/commons/jxpath/ClassFunctions.java +++ b/src/main/java/org/apache/commons/jxpath/ClassFunctions.java @@ -23,6 +23,7 @@ import org.apache.commons.jxpath.functions.ConstructorFunction; import org.apache.commons.jxpath.functions.MethodFunction; +import org.apache.commons.jxpath.ri.JXPathFilter; import org.apache.commons.jxpath.util.MethodLookupUtils; /** @@ -92,6 +93,28 @@ public Function getFunction( String namespace, String name, Object[] parameters) { + return getFunction(namespace, name, parameters, null); + } + + public Function getFunction( + String namespace, + String name, + Object[] parameters, + JXPathFilter jxPathFilter) { + + // give chance to ClassFilter to filter out, if present + try { + if (jxPathFilter != null && !jxPathFilter.exposeToXPath(functionClass.getName())) { + throw new ClassNotFoundException(functionClass.getName()); + } + } + catch (ClassNotFoundException ex) { + throw new JXPathException( + "Cannot invoke extension function " + + (namespace != null ? namespace + ":" + name : name), + ex); + } + if (namespace == null) { if (this.namespace != null) { return null; diff --git a/src/main/java/org/apache/commons/jxpath/FunctionLibrary.java b/src/main/java/org/apache/commons/jxpath/FunctionLibrary.java index 171adbd75..13443a226 100644 --- a/src/main/java/org/apache/commons/jxpath/FunctionLibrary.java +++ b/src/main/java/org/apache/commons/jxpath/FunctionLibrary.java @@ -16,6 +16,8 @@ */ package org.apache.commons.jxpath; +import org.apache.commons.jxpath.ri.JXPathFilter; + import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; @@ -77,12 +79,30 @@ public Set getUsedNamespaces() { */ public Function getFunction(String namespace, String name, Object[] parameters) { + return getFunction(namespace, name, parameters, null); + } + + /** + * Returns a Function, if any, for the specified namespace, + * name and parameter types. + * @param namespace function namespace + * @param name function name + * @param parameters parameters + * @param jxPathFilter the XPath filter + * @return Function found + */ + public Function getFunction( + String namespace, + String name, + Object[] parameters, + JXPathFilter jxPathFilter) { Object candidates = functionCache().get(namespace); if (candidates instanceof Functions) { return ((Functions) candidates).getFunction( namespace, name, - parameters); + parameters, + jxPathFilter); } if (candidates instanceof List) { List list = (List) candidates; @@ -92,7 +112,8 @@ public Function getFunction(String namespace, String name, ((Functions) list.get(i)).getFunction( namespace, name, - parameters); + parameters, + jxPathFilter); if (function != null) { return function; } diff --git a/src/main/java/org/apache/commons/jxpath/Functions.java b/src/main/java/org/apache/commons/jxpath/Functions.java index 216f7229f..4bce6ffab 100644 --- a/src/main/java/org/apache/commons/jxpath/Functions.java +++ b/src/main/java/org/apache/commons/jxpath/Functions.java @@ -16,6 +16,8 @@ */ package org.apache.commons.jxpath; +import org.apache.commons.jxpath.ri.JXPathFilter; + import java.util.Set; /** @@ -46,4 +48,15 @@ public interface Functions { * @return Function */ Function getFunction(String namespace, String name, Object[] parameters); + + /** + * Returns a Function, if any, for the specified namespace, + * name and parameter types. + * @param namespace ns + * @param name function name + * @param parameters Object[] + * @param jxPathFilter the XPath filter + * @return Function + */ + Function getFunction(String namespace, String name, Object[] parameters, JXPathFilter jxPathFilter); } diff --git a/src/main/java/org/apache/commons/jxpath/PackageFunctions.java b/src/main/java/org/apache/commons/jxpath/PackageFunctions.java index 86b09e426..612cfc678 100644 --- a/src/main/java/org/apache/commons/jxpath/PackageFunctions.java +++ b/src/main/java/org/apache/commons/jxpath/PackageFunctions.java @@ -25,6 +25,7 @@ import org.apache.commons.jxpath.functions.ConstructorFunction; import org.apache.commons.jxpath.functions.MethodFunction; +import org.apache.commons.jxpath.ri.JXPathFilter; import org.apache.commons.jxpath.util.ClassLoaderUtil; import org.apache.commons.jxpath.util.MethodLookupUtils; import org.apache.commons.jxpath.util.TypeUtils; @@ -112,10 +113,41 @@ public Set getUsedNamespaces() { * @return a MethodFunction, a ConstructorFunction or null if no function * is found */ + public Function getFunction( + String namespace, + String name, + Object[] parameters) { + return getFunction(namespace, name, parameters, null); + } + + /** + * Returns a {@link Function}, if found, for the specified namespace, + * name and parameter types. + *

+ * @param namespace - if it is not the same as specified in the + * construction, this method returns null + * @param name - name of the method, which can one these forms: + *

+ * @param parameters Object[] of parameters + * @param jxPathFilter the XPath filter + * @return a MethodFunction, a ConstructorFunction or null if no function + * is found + */ public Function getFunction( String namespace, String name, - Object[] parameters) { + Object[] parameters, + JXPathFilter jxPathFilter) { + if ((namespace == null && this.namespace != null) //NOPMD || (namespace != null && !namespace.equals(this.namespace))) { return null; @@ -186,7 +218,7 @@ public Function getFunction( Class functionClass; try { - functionClass = ClassLoaderUtil.getClass(className, true); + functionClass = ClassLoaderUtil.getClass(className, true, jxPathFilter); } catch (ClassNotFoundException ex) { throw new JXPathException( diff --git a/src/main/java/org/apache/commons/jxpath/ri/JXPathClassFilter.java b/src/main/java/org/apache/commons/jxpath/ri/JXPathClassFilter.java new file mode 100644 index 000000000..19bdb789f --- /dev/null +++ b/src/main/java/org/apache/commons/jxpath/ri/JXPathClassFilter.java @@ -0,0 +1,40 @@ +/* + * 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.commons.jxpath.ri; + +/** + * Class filter (optional) to be used by JXPath. + * + * System property "jxpath.class.deny" can be set to specify the list of restricted classnames. + * This property takes a list of java classnames (use comma as separator to specify more than one class). + * If this property is not set, it exposes any java class to javascript + * Ex: jxpath.class.deny=java.lang.Runtime will deny exposing java.lang.Runtime class via xpath, while all other classes will be exposed. + * + * @author bhmohanr-techie + * @version $Revision$ $Date$ + */ +public interface JXPathClassFilter { + + /** + * Should the Java class of the specified name be exposed via xpath? + * @param className is the fully qualified name of the java class being + * checked. This will not be null. Only non-array class names will be + * passed. + * @return true if the java class can be exposed via xpath, false otherwise + */ + public boolean exposeToXPath(String className); +} diff --git a/src/main/java/org/apache/commons/jxpath/ri/JXPathContextReferenceImpl.java b/src/main/java/org/apache/commons/jxpath/ri/JXPathContextReferenceImpl.java index 54214b89b..779350946 100644 --- a/src/main/java/org/apache/commons/jxpath/ri/JXPathContextReferenceImpl.java +++ b/src/main/java/org/apache/commons/jxpath/ri/JXPathContextReferenceImpl.java @@ -27,23 +27,10 @@ import java.util.Vector; import java.util.Map.Entry; -import org.apache.commons.jxpath.CompiledExpression; -import org.apache.commons.jxpath.ExceptionHandler; -import org.apache.commons.jxpath.Function; -import org.apache.commons.jxpath.Functions; -import org.apache.commons.jxpath.JXPathContext; -import org.apache.commons.jxpath.JXPathException; -import org.apache.commons.jxpath.JXPathFunctionNotFoundException; -import org.apache.commons.jxpath.JXPathInvalidSyntaxException; -import org.apache.commons.jxpath.JXPathNotFoundException; -import org.apache.commons.jxpath.JXPathTypeConversionException; -import org.apache.commons.jxpath.Pointer; +import org.apache.commons.jxpath.*; import org.apache.commons.jxpath.ri.axes.InitialContext; import org.apache.commons.jxpath.ri.axes.RootContext; -import org.apache.commons.jxpath.ri.compiler.Expression; -import org.apache.commons.jxpath.ri.compiler.LocationPath; -import org.apache.commons.jxpath.ri.compiler.Path; -import org.apache.commons.jxpath.ri.compiler.TreeCompiler; +import org.apache.commons.jxpath.ri.compiler.*; import org.apache.commons.jxpath.ri.model.NodePointer; import org.apache.commons.jxpath.ri.model.NodePointerFactory; import org.apache.commons.jxpath.ri.model.VariablePointerFactory; @@ -739,7 +726,7 @@ public Function getFunction(QName functionName, Object[] parameters) { while (funcCtx != null) { funcs = funcCtx.getFunctions(); if (funcs != null) { - func = funcs.getFunction(namespace, name, parameters); + func = funcs.getFunction(namespace, name, parameters, new JXPathFilter()); if (func != null) { return func; } diff --git a/src/main/java/org/apache/commons/jxpath/ri/JXPathFilter.java b/src/main/java/org/apache/commons/jxpath/ri/JXPathFilter.java new file mode 100644 index 000000000..be4ea1233 --- /dev/null +++ b/src/main/java/org/apache/commons/jxpath/ri/JXPathFilter.java @@ -0,0 +1,67 @@ +/* + * 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.commons.jxpath.ri; + +import java.util.ArrayList; +import java.util.Arrays; + +/** + * A filter to be used by JXPath, to evaluate the xpath string values to impose any restrictions. + * This class implements specific filter interfaces, and implements methods in those. + * For instance, it JXPathClassFilter interface, which is used to check if any restricted java classes are passed via xpath + * JXPath uses this filter instance when an extension function instance is created. + * + * @author bhmohanr-techie + * @version $Revision$ $Date$ + */ +public class JXPathFilter implements JXPathClassFilter { + ArrayList restrictedClassesList = null; + + public JXPathFilter() { + init(); + } + + public void init() { + String restrictedClasses = System.getProperty("jxpath.class.deny"); + restrictedClassesList = null; + if ((restrictedClasses != null) && (restrictedClasses.trim().length() > 0)) { + restrictedClassesList = new ArrayList(); + restrictedClassesList.addAll(Arrays.asList(restrictedClasses.split(","))); + } + } + + /** + * Specifies whether the Java class of the specified name be exposed via xpath + * + * @param className is the fully qualified name of the java class being checked. + * This will not be null. Only non-array class names will be passed. + * @return true if the java class can be exposed via xpath, false otherwise + */ + @Override + public boolean exposeToXPath(String className) { + if ((restrictedClassesList == null) || (restrictedClassesList.size() < 1) || restrictedClassesList.contains("*")) { + return true; + } + + if (restrictedClassesList.contains(className)) { + return false; + } + + return true; + } +} + diff --git a/src/main/java/org/apache/commons/jxpath/util/ClassLoaderUtil.java b/src/main/java/org/apache/commons/jxpath/util/ClassLoaderUtil.java index abb4d2f5f..c66e92dcb 100644 --- a/src/main/java/org/apache/commons/jxpath/util/ClassLoaderUtil.java +++ b/src/main/java/org/apache/commons/jxpath/util/ClassLoaderUtil.java @@ -16,6 +16,8 @@ */ package org.apache.commons.jxpath.util; +import org.apache.commons.jxpath.ri.JXPathFilter; + import java.util.HashMap; import java.util.Map; @@ -75,12 +77,19 @@ private static void addAbbreviation(String primitive, String abbreviation) { * @param classLoader the class loader to use to load the class * @param className the class name * @param initialize whether the class must be initialized + * @param jxPathFilter the XPath filter * @return the class represented by className using the classLoader * @throws ClassNotFoundException if the class is not found */ - public static Class getClass(ClassLoader classLoader, String className, boolean initialize) + public static Class getClass(ClassLoader classLoader, String className, boolean initialize, JXPathFilter jxPathFilter) throws ClassNotFoundException { Class clazz; + + // give chance to ClassFilter to filter out, if present + if (jxPathFilter != null && !jxPathFilter.exposeToXPath(className)) { + throw new ClassNotFoundException(className); + } + if (abbreviationMap.containsKey(className)) { String clsName = "[" + abbreviationMap.get(className); clazz = Class.forName(clsName, initialize, classLoader).getComponentType(); @@ -91,6 +100,38 @@ public static Class getClass(ClassLoader classLoader, String className, boolean return clazz; } + /** + * Returns the class represented by className using the + * classLoader. This implementation supports names like + * "java.lang.String[]" as well as "[Ljava.lang.String;". + * + * @param classLoader the class loader to use to load the class + * @param className the class name + * @param initialize whether the class must be initialized + * @return the class represented by className using the classLoader + * @throws ClassNotFoundException if the class is not found + */ + public static Class getClass(ClassLoader classLoader, String className, boolean initialize) + throws ClassNotFoundException { + return getClass(classLoader, className, initialize, null); + } + + /** + * Returns the (initialized) class represented by className + * using the classLoader. This implementation supports names + * like "java.lang.String[]" as well as + * "[Ljava.lang.String;". + * + * @param classLoader the class loader to use to load the class + * @param className the class name + * @param jxPathFilter the XPath filter + * @return the class represented by className using the classLoader + * @throws ClassNotFoundException if the class is not found + */ + public static Class getClass(ClassLoader classLoader, String className, JXPathFilter jxPathFilter) throws ClassNotFoundException { + return getClass(classLoader, className, true, jxPathFilter); + } + /** * Returns the (initialized) class represented by className * using the classLoader. This implementation supports names @@ -103,7 +144,7 @@ public static Class getClass(ClassLoader classLoader, String className, boolean * @throws ClassNotFoundException if the class is not found */ public static Class getClass(ClassLoader classLoader, String className) throws ClassNotFoundException { - return getClass(classLoader, className, true); + return getClass(classLoader, className, true, null); } /** @@ -117,7 +158,22 @@ public static Class getClass(ClassLoader classLoader, String className) throws C * @throws ClassNotFoundException if the class is not found */ public static Class getClass(String className) throws ClassNotFoundException { - return getClass(className, true); + return getClass(className, true, null); + } + + /** + * Returns the (initialized) class represented by className + * using the current thread's context class loader. This implementation + * supports names like "java.lang.String[]" as well as + * "[Ljava.lang.String;". + * + * @param className the class name + * @param jxPathFilter the XPath filter + * @return the class represented by className using the current thread's context class loader + * @throws ClassNotFoundException if the class is not found + */ + public static Class getClass(String className, JXPathFilter jxPathFilter) throws ClassNotFoundException { + return getClass(className, true, jxPathFilter); } /** @@ -132,17 +188,33 @@ public static Class getClass(String className) throws ClassNotFoundException { * @throws ClassNotFoundException if the class is not found */ public static Class getClass(String className, boolean initialize) throws ClassNotFoundException { + return getClass(className, initialize, null); + } + + /** + * Returns the class represented by className using the + * current thread's context class loader. This implementation supports + * names like "java.lang.String[]" as well as + * "[Ljava.lang.String;". + * + * @param className the class name + * @param initialize whether the class must be initialized + * @param jxPathFilter the XPath filter + * @return the class represented by className using the current thread's context class loader + * @throws ClassNotFoundException if the class is not found + */ + public static Class getClass(String className, boolean initialize, JXPathFilter jxPathFilter) throws ClassNotFoundException { ClassLoader contextCL = Thread.currentThread().getContextClassLoader(); ClassLoader currentCL = ClassLoaderUtil.class.getClassLoader(); if (contextCL != null) { try { - return getClass(contextCL, className, initialize); + return getClass(contextCL, className, initialize, jxPathFilter); } catch (ClassNotFoundException e) {//NOPMD // ignore this exception and try the current class loader. } } - return getClass(currentCL, className, initialize); + return getClass(currentCL, className, initialize, jxPathFilter); } /** diff --git a/src/test/java/org/apache/commons/jxpath/ri/compiler/ExtensionFunctionTest.java b/src/test/java/org/apache/commons/jxpath/ri/compiler/ExtensionFunctionTest.java index 7a222e4d4..7ec7f9419 100644 --- a/src/test/java/org/apache/commons/jxpath/ri/compiler/ExtensionFunctionTest.java +++ b/src/test/java/org/apache/commons/jxpath/ri/compiler/ExtensionFunctionTest.java @@ -16,23 +16,10 @@ */ package org.apache.commons.jxpath.ri.compiler; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Locale; - -import org.apache.commons.jxpath.ClassFunctions; -import org.apache.commons.jxpath.ExpressionContext; -import org.apache.commons.jxpath.Function; -import org.apache.commons.jxpath.FunctionLibrary; -import org.apache.commons.jxpath.Functions; -import org.apache.commons.jxpath.JXPathContext; -import org.apache.commons.jxpath.JXPathTestCase; -import org.apache.commons.jxpath.NodeSet; -import org.apache.commons.jxpath.PackageFunctions; -import org.apache.commons.jxpath.Pointer; -import org.apache.commons.jxpath.TestBean; -import org.apache.commons.jxpath.Variables; +import java.util.*; + +import org.apache.commons.jxpath.*; +import org.apache.commons.jxpath.ri.JXPathFilter; import org.apache.commons.jxpath.ri.model.NodePointer; import org.apache.commons.jxpath.util.JXPath11CompatibleTypeConverter; import org.apache.commons.jxpath.util.TypeConverter; @@ -49,6 +36,7 @@ public class ExtensionFunctionTest extends JXPathTestCase { private JXPathContext context; private TestBean testBean; private TypeConverter typeConverter; + JXPathFilter jxPathFilter = new JXPathFilter(); public void setUp() { if (context == null) { @@ -385,6 +373,139 @@ public void testBCNodeSetHack() { Boolean.TRUE); } + public void testClassFunctionsWithClassFilter() { + try { + Functions iFunctions = new ClassFunctions(TestFunctions3.class, "test3"); + + System.setProperty("jxpath.class.deny", "org.apache.commons.jxpath.ri.compiler.TestFunctions3"); + jxPathFilter.init(); + + Function classFunction = iFunctions.getFunction("test3", "testFunction3Method1", null, jxPathFilter); + classFunction = null; + throw new Exception("testClassFunctionsWithClassFilter() failed."); + } catch (Throwable t) { + assertTrue((t.getMessage().indexOf("Cannot invoke extension function test3:testFunction3Method1; org.apache.commons.jxpath.ri.compiler.TestFunctions3") > -1) + || (t.getMessage().indexOf("java.lang.ClassNotFoundException: org.apache.commons.jxpath.ri.compiler.TestFunctions3") > -1)); + } finally { + System.clearProperty("jxpath.class.deny"); + } + } + + public void testClassFunctionsWithoutClassFilter() { + Function classFunction = null; + try { + Functions iFunctions = new ClassFunctions(TestFunctions3.class, "test3"); + + System.clearProperty("jxpath.class.deny"); + jxPathFilter.init(); + + classFunction = iFunctions.getFunction("test3", "testFunction3Method1", null, jxPathFilter); + } catch (Throwable t) { + fail(t.getMessage()); + } finally { + System.clearProperty("jxpath.class.deny"); + assertTrue(classFunction != null); + classFunction = null; + } + } + + public void testPackageFunctionsWithClassFilter() { + try { + Functions iFunctions = new PackageFunctions("org.apache.commons.jxpath.ri.compiler.","jxpathtests"); + + System.setProperty("jxpath.class.deny", "org.apache.commons.jxpath.ri.compiler.TestFunctions3"); + jxPathFilter.init(); + + Function packageFunction = iFunctions.getFunction("jxpathtests", "TestFunctions3.testFunction3Method1", null, jxPathFilter); + packageFunction = null; + throw new Exception("testPackageFunctionsWithClassFilter() failed."); + } catch (Throwable t) { + assertTrue((t.getMessage().indexOf("Cannot invoke extension function jxpathtests:TestFunctions3.testFunction3Method1; org.apache.commons.jxpath.ri.compiler.TestFunctions3") > -1) + || (t.getMessage().indexOf("java.lang.ClassNotFoundException: org.apache.commons.jxpath.ri.compiler.TestFunctions3") > -1)); + } finally { + System.clearProperty("jxpath.class.deny"); + } + } + + public void testPackageFunctionsWithoutClassFilter() { + Function packageFunction = null; + try { + Functions iFunctions = new PackageFunctions("org.apache.commons.jxpath.ri.compiler.","jxpathtests"); + + System.clearProperty("jxpath.class.deny"); + jxPathFilter.init(); + + packageFunction = iFunctions.getFunction("jxpathtests", "TestFunctions3.testFunction3Method1", null, jxPathFilter); + } catch (Throwable t) { + fail(t.getMessage()); + } finally { + System.clearProperty("jxpath.class.deny"); + assertTrue(packageFunction != null); + packageFunction = null; + } + } + + public void testJXPathContextFunctionsWithClassFilter() { + String failedMethods = null; + try { + System.setProperty("jxpath.class.deny", "java.lang.Thread"); + jxPathFilter.init(); + + try { + context.iterate("java.lang.Thread.sleep(5000)"); + throw new Exception("testJXPathContextFunctionsWithClassFilter() failed for iterate()"); + } catch (Throwable t) { + if ((t.getMessage().indexOf("Cannot invoke extension function java.lang.Thread.sleep; java.lang.Thread") > -1) + || (t.getMessage().indexOf("java.lang.ClassNotFoundException: java.lang.Thread") > -1)) { + //success + } else { + failedMethods = "org.apache.commons.jxpath.JXPathContext.iterate()"; + } + } + + try { + context.selectSingleNode("java.lang.Thread.sleep(5000)"); + throw new Exception("testJXPathContextFunctionsWithClassFilter() failed for iterate()"); + } catch (Throwable t) { + if ((t.getMessage().indexOf("Cannot invoke extension function java.lang.Thread.sleep; java.lang.Thread") > -1) + || (t.getMessage().indexOf("java.lang.ClassNotFoundException: java.lang.Thread") > -1)) { + //success + } else { + failedMethods += ("".equals(failedMethods) ? "org.apache.commons.jxpath.JXPathContext.selectSingleNode()" : ", org.apache.commons.jxpath.JXPathContext.selectSingleNode()"); + } + } + + } catch (Throwable t) { + fail(t.getMessage()); + } finally { + System.clearProperty("jxpath.class.deny"); + if (failedMethods != null) { + fail("Problem exists in: " + failedMethods); + } + } + } + + public void testJXPathContextFunctionsWithoutClassFilter() { + try { + System.clearProperty("jxpath.class.deny"); + jxPathFilter.init(); + + long t = System.currentTimeMillis(); + context.iterate("java.lang.Thread.sleep(5000)"); + t = System.currentTimeMillis() - t; + assertTrue(t >= 5); + + t = System.currentTimeMillis(); + context.selectSingleNode("java.lang.Thread.sleep(5000)"); + t = System.currentTimeMillis() - t; + assertTrue(t >= 5); + } catch (Throwable t) { + fail(t.getMessage()); + } finally { + System.clearProperty("jxpath.class.deny"); + } + } + private static class Context implements ExpressionContext { private Object object; diff --git a/src/test/java/org/apache/commons/jxpath/ri/compiler/TestFunctions3.java b/src/test/java/org/apache/commons/jxpath/ri/compiler/TestFunctions3.java new file mode 100644 index 000000000..d8d0c92a6 --- /dev/null +++ b/src/test/java/org/apache/commons/jxpath/ri/compiler/TestFunctions3.java @@ -0,0 +1,55 @@ +/* + * 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.commons.jxpath.ri.compiler; + +import org.apache.commons.jxpath.Functions; +import org.apache.commons.jxpath.JXPathContext; + +import java.util.*; + +/** + * A test class with few methods, with different argument list + * + * @author bhmohanr-techie + * @version $Revision$ $Date$ + */ +public final class TestFunctions3 { + + static { + System.out.println("TestFunctions3: static block..."); + } + + public TestFunctions3() { + System.out.println("TestFunctions3: constructor..."); + } + + public static String testFunction3Method1() { + System.out.println("TestFunctions3: testFunction3Method1 method..."); + return "testFunction3Method1"; + } + + public String testFunction3Method2(String str) { + System.out.println("TestFunctions3: testFunction3Method2 method..." + str); + return "testFunction3Method2:" + str; + } + + public String testFunction3Method3(String str1, String str2) { + System.out.println("TestFunctions3: testFunction3Method3 method..." + str1 + ", " + str2); + return "testFunction3Method3:" + str1 + ":" + str2; + } + +} \ No newline at end of file