helma-xmlrpc-introspection.diff - Java XML-RPC introspection v1.0 Patch Copyright 2001 Eric Kidd Original Source Copyright (c) 1999 Hannes Wallnoefer License ======= 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 name of the author may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS 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 AUTHOR OR 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. Summary ======= Get the original source from . This adds several new features to Hannes Wallnoefer's XML-RPC library for Java: * Full introspection support. * Compound handler names (e.g., 'imap.message.get_body'). * A security fix which prevents remote systems from accessing methods on Object ('notify', 'wait', 'hashCode', etc.) and other classes unless explicitly exported. Here's how to export and document a method named 'foo': 1) Declare 'foo' in the usual fashion. 2) Declare 'foo_help' a 'public static final String' and include some help text. 2) Declare 'foo_public' a 'public static final boolean' and initialize it to 'true'. This is a new requirement. To disable introspection on an XmlRpcServer, call: server.setIntrospectionAllowed(false) To apply this diff under Unix, cd to the top level of the source tree and type: cat helma-xmlrpc-introspection.diff | patch -p1 Share and enjoy! diff -uNr xmlrpc-java-old/src/helma/xmlrpc/IntrospectiveXmlRpcHandler.java xmlrpc-java/src/helma/xmlrpc/IntrospectiveXmlRpcHandler.java --- xmlrpc-java-old/src/helma/xmlrpc/IntrospectiveXmlRpcHandler.java Wed Dec 31 19:00:00 1969 +++ xmlrpc-java/src/helma/xmlrpc/IntrospectiveXmlRpcHandler.java Fri Apr 6 18:50:14 2001 @@ -0,0 +1,68 @@ +/* XmlRpcIntrospectiveHandler.java - Part of Java XML-RPC introspection. + * Copyright 2001 Eric Kidd + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS 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 AUTHOR OR 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. + */ + +package helma.xmlrpc; + +import java.util.Vector; + +/** + * The XML-RPC server uses this interface to perform introspection. If + * your handler class implements this interface, users will be able to + * automatically generate documentation and/or proxy classes from your + * server. + */ +public interface IntrospectiveXmlRpcHandler +{ + /** + * Return a vector of strings, listing the unique method names + * supported by this object. The handler name should not be + * included; this will be added automatically by the server. + * @return Unique method names supported by this object. + */ + public Vector listMethods () throws Exception; + + /** + * Return a vector of vectors of strings, containing method signatures + * of the form documented at the PHP + * introspection page. + * @param methodName The bare method name, not including the handler + * name. + * @return The allowable signatures for this method. + */ + public Vector methodSignature (String methodName) throws Exception; + + /** + * Return a help string for the specified message. This may + * contain HTML markup, but it's usually better to return a single, + * long line of ASCII text. + * @param methodName The bare method name, not including the handler + * name. + * @return A help string. + */ + public String methodHelp (String methodName) throws Exception; +} diff -uNr xmlrpc-java-old/src/helma/xmlrpc/XmlRpcServer.java xmlrpc-java/src/helma/xmlrpc/XmlRpcServer.java --- xmlrpc-java-old/src/helma/xmlrpc/XmlRpcServer.java Thu Oct 26 00:39:00 2000 +++ xmlrpc-java/src/helma/xmlrpc/XmlRpcServer.java Fri Apr 6 21:04:11 2001 @@ -21,13 +21,23 @@ */ Hashtable handlers; - + boolean allow_introspection; + /** * Construct a new XML-RPC server. You have to register handlers to make it * do something useful. */ public XmlRpcServer () { handlers = new Hashtable (); + allow_introspection = true; + addHandler("system", new SystemHandler(this)); + } + + /** + * Enable or disable introspection. + */ + public void setIntrospectionAllowed (boolean allow) { + allow_introspection = allow; } /** @@ -49,6 +59,59 @@ handlers.remove (handlername); } + /** A handler & method pair. */ + private static class MethodSpec { + Object handler; + String function; + + /** + * Create a new MethodSpec. + * @param handler A handler object. + * @param function The name of a Java method which (allegedly) + * appear on that object. + */ + public MethodSpec (Object handler, String function) { + this.handler = handler; + this.function = function; + } + + public Object getHandler () { return handler; } + public String getFunction () { return function; } + } + + /** Given a full method name, find the MethodSpec. */ + private MethodSpec findMethodSpec (String methodName) + throws Exception + { + Object handler = null; + String function = null; + + String handlerName = null; + int dot = methodName.lastIndexOf ("."); + if (dot > -1) { + handlerName = methodName.substring (0, dot); + handler = handlers.get (handlerName); + if (handler != null) + function = methodName.substring (dot+1); + } + if (handler == null) { + handler = handlers.get ("$default"); + function = methodName; + } + + if (handler == null) { + if (dot > -1) + throw new Exception ("RPC handler object \"" + handlerName + + "\" not found and no default handler " + + "registered."); + else + throw new Exception ("RPC handler object not found for \"" + + methodName + "\": no default handler " + + "registered."); + } + return new MethodSpec(handler, function); + } + /** * Parse the request and execute the handler method, if one is found. Returns the result as XML. * The calling Java code doesn't need to know whether the call was successful or not since this is all @@ -110,27 +173,10 @@ if (errorLevel > NONE) throw new Exception (errorMsg); - Object handler = null; - - String handlerName = null; - int dot = methodName.indexOf ("."); - if (dot > -1) { - handlerName = methodName.substring (0, dot); - handler = handlers.get (handlerName); - if (handler != null) - methodName = methodName.substring (dot+1); - } - - if (handler == null) { - handler = handlers.get ("$default"); - } - - if (handler == null) { - if (dot > -1) - throw new Exception ("RPC handler object \""+handlerName+"\" not found and no default handler registered."); - else - throw new Exception ("RPC handler object not found for \""+methodName+"\": no default handler registered."); - } + // Find the appropriate handler & the method name to call. + MethodSpec spec = findMethodSpec(methodName); + Object handler = spec.getHandler(); + methodName = spec.getFunction(); if (handler instanceof AuthenticatedXmlRpcHandler) outParam = ((AuthenticatedXmlRpcHandler) handler).execute (methodName, inParams, user, password); @@ -201,10 +247,142 @@ } // end of inner class Worker + /** Throw an error if introspection has been disabled. */ + private void introspection_check () throws Exception { + if (!allow_introspection) + throw new Exception("Introspection disabled for security."); + } + + /** Make sure the named object supports introspection. */ + private IntrospectiveXmlRpcHandler requireIntrospection (Object handler) + throws Exception + { + if (!(handler instanceof IntrospectiveXmlRpcHandler)) + throw new Exception ("The handler does not support introspection"); + return (IntrospectiveXmlRpcHandler) handler; + } + + /** + * Return a vector of strings, listing the unique method names + * supported by this server. + * @return Unique method names supported by this server. + */ + public Vector listMethods () throws Exception { + introspection_check(); + Vector result = new Vector(); + + // Merge the method names for all our handlers. + Enumeration handlerNames = handlers.keys(); + while (handlerNames.hasMoreElements()) { + String handlerName = (String) handlerNames.nextElement(); + Object handler = handlers.get(handlerName); + + // See if this handler supports introspection. + if (handler instanceof IntrospectiveXmlRpcHandler) { + IntrospectiveXmlRpcHandler ihandler = + (IntrospectiveXmlRpcHandler) handler; + + // Figure out what name prefix to use for this handler. + String namePrefix = ""; + if (handlerName != "$default") + namePrefix = handlerName + "."; + + // Copy all the method names into our handler. + Vector methods = ihandler.listMethods(); + for (int i = 0; i < methods.size(); i++) { + String method = (String) methods.elementAt(i); + result.addElement(namePrefix + method); + } + } + } + return result; + } + + /** + * Return a vector of vectors of strings, containing method signatures + * of the form documented at the PHP + * introspection page. + * @param methodName The full method name. + * @return The allowable signatures for this method. + */ + public Vector methodSignature (String methodName) throws Exception { + introspection_check(); + + // Find the appropriate handler & the method name to look up. + MethodSpec spec = findMethodSpec(methodName); + IntrospectiveXmlRpcHandler handler = + requireIntrospection(spec.getHandler()); + methodName = spec.getFunction(); + + // Ask the handler for the signature. + return handler.methodSignature(methodName); + } + + /** + * Return a help string for the specified message. This may + * contain HTML markup, but it's usually better to return a single, + * long line of ASCII text. + * @param methodName The full method name. + * @return A help string. + */ + public String methodHelp (String methodName) throws Exception { + introspection_check(); + + // Find the appropriate handler & the method name to look up. + MethodSpec spec = findMethodSpec(methodName); + IntrospectiveXmlRpcHandler handler = + requireIntrospection(spec.getHandler()); + methodName = spec.getFunction(); + + // Ask the handler for the help string. + return handler.methodHelp(methodName); + } + + /** + * This class gets installed as a handler to respond to queries for + * methods beginning with 'system.*'. Most of the time, it just forwards + * calls to the appropriate method on the server itself. + */ + private static class SystemHandler { + XmlRpcServer server; + + public SystemHandler (XmlRpcServer server) { + this.server = server; + } + + public static final boolean listMethods_public = true; + public static final String listMethods_help = + "Return an array of all available XML-RPC methods on this server."; + + public Vector listMethods () throws Exception { + return server.listMethods(); + } + + public static final boolean methodSignature_public = true; + public static final String methodSignature_help = + "Given the name of a method, return an array of legal " + + "signatures. Each signature is an array of strings. The first " + + "item of each signature is the return type, and any others " + + "items are parameter types."; + + public Vector methodSignature (String methodName) throws Exception { + return server.methodSignature(methodName); + } + + public static final boolean methodHelp_public = true; + public static final String methodHelp_help = + "Given the name of a method, return a help string."; + + public String methodHelp (String methodName) throws Exception { + return server.methodHelp(methodName); + } + } // SystemHandler } // XmlRpcServer -// This class uses Java Reflection to call methods matching an XML-RPC call -class Invoker implements XmlRpcHandler { +// This class uses Java Reflection to call methods matching an XML-RPC call. +// It can also use reflection to support XML-RPC introspection. +class Invoker implements XmlRpcHandler, IntrospectiveXmlRpcHandler { private Object invokeTarget; private Class targetClass; @@ -215,12 +393,37 @@ (Class) invokeTarget : invokeTarget.getClass(); if (XmlRpc.debug) System.err.println("Target object is " + targetClass); - } + } + + /** + * Should we expose the specified method to remote users? + * If we didn't perform this check, we would expose all of the default + * methods on Object, some of which are potentially dangerous. + */ + private boolean isPublicXmlRpcMethod (String methodName) { + boolean pub = false; + try { + String publicFieldName = methodName.concat("_public"); + Field publicField = targetClass.getField(publicFieldName); + pub = publicField.getBoolean(invokeTarget); + + } catch (Exception e) { + return false; + } + return pub; + } + /** Check to make sure the specified method is public. */ + private void checkPublicXmlRpcMethod (String methodName) + throws Exception + { + if (!isPublicXmlRpcMethod(methodName)) + throw new Exception("No public method found: " + methodName); + } // main method, sucht methode in object, wenn gefunden dann aufrufen. public Object execute (String methodName, Vector params) throws Exception { - + checkPublicXmlRpcMethod(methodName); // Array mit Classtype bilden, ObjectAry mit Values bilden Class[] argClasses = null; @@ -281,4 +484,100 @@ return returnValue; } + /** Return a vector of strings listing the unique method names. */ + public Vector listMethods () throws Exception { + Method [] methodObjects = targetClass.getMethods(); + + // Find the unique method names exported by this class. + Hashtable methodNameTable = new Hashtable(); + int end = methodObjects.length; + for (int i = 0; i < end; i++) { + Method methodObject = methodObjects[i]; + if (Modifier.isPublic(methodObject.getModifiers())) { + methodNameTable.put(methodObject.getName(), ""); + } + } + + // Build and return a vector. + Vector names = new Vector(); + Enumeration keys = methodNameTable.keys(); + while (keys.hasMoreElements()) { + String methodName = (String) keys.nextElement(); + + // Filter our private methods. + if (isPublicXmlRpcMethod(methodName)) + names.addElement(methodName); + } + return names; + } + + /** Find the introspection name for a type. */ + private static String findTypeName (Class type) + throws XmlRpcException + { + if (type == Integer.TYPE) + return "int"; + else if (type == String.class) + return "string"; + else if (type == Vector.class) + return "array"; + else if (type == Hashtable.class) + return "struct"; + else if (type == Boolean.TYPE) + return "boolean"; + else if (type == Double.TYPE) + return "double"; + else if (type == Date.class) + return "dateTime.iso8601"; + else if (type.isArray() && type.getComponentType() == Byte.TYPE) + return "base64"; + else + throw new XmlRpcException(0, "Unsupported parameter type: " + + type.toString()); + } + + /** Return a vector of vectors of strings containing method signatures. */ + public Vector methodSignature (String methodName) throws Exception { + checkPublicXmlRpcMethod(methodName); + Method [] methodObjects = targetClass.getMethods(); + Vector signature_set = new Vector(); + + // Find signatures for all matching methods. + for (int i = 0; i < methodObjects.length; i++) { + Method methodObject = methodObjects[i]; + if (methodObject.getName().equals(methodName) + && Modifier.isPublic(methodObject.getModifiers())) + { + Vector signature = new Vector(); + Class returnType = methodObject.getReturnType(); + Class [] parameterTypes = methodObject.getParameterTypes(); + signature.addElement(findTypeName(returnType)); + for (int j = 0; j < parameterTypes.length; j++) { + Class parameterType = parameterTypes[j]; + signature.addElement(findTypeName(parameterType)); + } + signature_set.addElement(signature); + } + } + + // Raise an error if we don't find any matching methods. + if (signature_set.size() == 0) + throw new XmlRpcException(0, "Unknown method: " + methodName); + + return signature_set; + } + + /** Return a help string for the specified message. */ + public String methodHelp (String methodName) throws Exception { + checkPublicXmlRpcMethod(methodName); + String help = ""; + try { + String helpFieldName = methodName.concat("_help"); + Field helpField = targetClass.getField(helpFieldName); + help = (String) helpField.get(invokeTarget); + } catch (Exception e) { + /* Use the default value for 'help'. */ + } + return help; + } }