Merge from trunk
[plcapi.git] / trunk / php / xmlrpc / libxmlrpc / xmlrpc_introspection.c
diff --git a/trunk/php/xmlrpc/libxmlrpc/xmlrpc_introspection.c b/trunk/php/xmlrpc/libxmlrpc/xmlrpc_introspection.c
new file mode 100644 (file)
index 0000000..589ff8e
--- /dev/null
@@ -0,0 +1,604 @@
+/*
+  This file is part of libXMLRPC - a C library for xml-encoded function calls.
+
+  Author: Dan Libby (dan@libby.com)
+  Epinions.com may be contacted at feedback@epinions-inc.com
+*/
+
+/*  
+  Copyright 2001 Epinions, Inc. 
+
+  Subject to the following 3 conditions, Epinions, Inc.  permits you, free 
+  of charge, to (a) use, copy, distribute, modify, perform and display this 
+  software and associated documentation files (the "Software"), and (b) 
+  permit others to whom the Software is furnished to do so as well.  
+
+  1) The above copyright notice and this permission notice shall be included 
+  without modification in all copies or substantial portions of the 
+  Software.  
+
+  2) THE SOFTWARE IS PROVIDED "AS IS", WITHOUT ANY WARRANTY OR CONDITION OF 
+  ANY KIND, EXPRESS, IMPLIED OR STATUTORY, INCLUDING WITHOUT LIMITATION ANY 
+  IMPLIED WARRANTIES OF ACCURACY, MERCHANTABILITY, FITNESS FOR A PARTICULAR 
+  PURPOSE OR NONINFRINGEMENT.  
+
+  3) IN NO EVENT SHALL EPINIONS, INC. BE LIABLE FOR ANY DIRECT, INDIRECT, 
+  SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES OR LOST PROFITS ARISING OUT 
+  OF OR IN CONNECTION WITH THE SOFTWARE (HOWEVER ARISING, INCLUDING 
+  NEGLIGENCE), EVEN IF EPINIONS, INC.  IS AWARE OF THE POSSIBILITY OF SUCH 
+  DAMAGES.    
+
+*/
+
+
+/****h* ABOUT/xmlrpc_introspection
+ * AUTHOR
+ *   Dan Libby, aka danda  (dan@libby.com)
+ * HISTORY
+ *   $Log: xmlrpc_introspection.c,v $
+ *   Revision 1.4  2003/12/16 21:00:21  sniper
+ *   Fix some compile warnings (patch by Joe Orton)
+ *
+ *   Revision 1.3  2002/07/05 04:43:53  danda
+ *   merged in updates from SF project.  bring php repository up to date with xmlrpc-epi version 0.51
+ *
+ *   Revision 1.9  2001/09/29 21:58:05  danda
+ *   adding cvs log to history section
+ *
+ *   4/10/2001 -- danda -- initial introspection support
+ * TODO
+ * NOTES
+ *******/
+
+
+#ifdef _WIN32
+#include "xmlrpc_win32.h"
+#endif
+#include "queue.h"
+#include "xmlrpc.h"
+#include "xmlrpc_private.h"
+#include "xmlrpc_introspection_private.h"
+#include <string.h>
+#include <stdlib.h>
+#include <stdarg.h>
+
+
+/* forward declarations for static (non public, non api) funcs */
+static XMLRPC_VALUE xi_system_describe_methods_cb(XMLRPC_SERVER server, XMLRPC_REQUEST input, void* userData);
+static XMLRPC_VALUE xi_system_list_methods_cb(XMLRPC_SERVER server, XMLRPC_REQUEST input, void* userData);
+static XMLRPC_VALUE xi_system_method_signature_cb(XMLRPC_SERVER server, XMLRPC_REQUEST input, void* userData);
+static XMLRPC_VALUE xi_system_method_help_cb(XMLRPC_SERVER server, XMLRPC_REQUEST input, void* userData);
+
+
+/*-**********************************
+* Introspection Callbacks (methods) *
+************************************/
+
+/* iterates through a list of structs and finds the one with key "name" matching
+ * needle.  slow, would benefit from a struct key hash.
+ */
+inline XMLRPC_VALUE find_named_value(XMLRPC_VALUE list, const char* needle) {
+   XMLRPC_VALUE xIter = XMLRPC_VectorRewind(list);
+   while(xIter) {
+      const char* name = XMLRPC_VectorGetStringWithID(xIter, xi_token_name);
+      if(name && !strcmp(name, needle)) {
+         return xIter;
+      }
+      xIter = XMLRPC_VectorNext(list);
+   }
+   return NULL;
+}
+
+
+/* iterates through docs callbacks and calls any that have not yet been called */
+static void check_docs_loaded(XMLRPC_SERVER server, void* userData) {
+   if(server) {
+      q_iter qi = Q_Iter_Head_F(&server->docslist);
+      while( qi ) {
+         doc_method* dm = Q_Iter_Get_F(qi);
+         if(dm && !dm->b_called) {
+            dm->method(server, userData);
+            dm->b_called = 1;
+         }
+         qi = Q_Iter_Next_F(qi);
+      }
+   }
+}
+
+
+/* utility function for xi_system_describe_methods_cb */
+inline void describe_method(XMLRPC_SERVER server, XMLRPC_VALUE vector, const char* method) {
+   if(method) {
+      server_method* sm = find_method(server, method);
+      if(sm) {
+         XMLRPC_AddValueToVector(vector, sm->desc);
+      }
+   }
+}
+
+
+
+/* system.describeMethods() callback */
+static XMLRPC_VALUE xi_system_describe_methods_cb(XMLRPC_SERVER server, XMLRPC_REQUEST input, void* userData) {
+   XMLRPC_VALUE xParams = XMLRPC_VectorRewind(XMLRPC_RequestGetData(input));
+   XMLRPC_VALUE xResponse = XMLRPC_CreateVector(NULL, xmlrpc_vector_struct);
+   XMLRPC_VALUE xMethodList = XMLRPC_CreateVector("methodList", xmlrpc_vector_array);
+   XMLRPC_VALUE xTypeList = NULL;
+   int bAll = 1;
+
+   /* lazy loading of introspection data */
+   check_docs_loaded(server, userData);
+
+   xTypeList = XMLRPC_VectorGetValueWithID(server->xIntrospection, "typeList");
+
+   XMLRPC_AddValueToVector(xResponse, xTypeList);
+   XMLRPC_AddValueToVector(xResponse, xMethodList);
+
+   /* check if we have any param */
+   if(xParams) {
+      /* check if string or vector (1 or n) */
+      XMLRPC_VALUE_TYPE type = XMLRPC_GetValueType(xParams);
+      if(type == xmlrpc_string) {
+         /* just one.  spit it out. */
+         describe_method(server, xMethodList, XMLRPC_GetValueString(xParams));
+         bAll = 0;
+      }
+      else if(type == xmlrpc_vector) {
+         /* multiple.  spit all out */
+         XMLRPC_VALUE xIter = XMLRPC_VectorRewind(xParams);
+         while(xIter) {
+            describe_method(server, xMethodList, XMLRPC_GetValueString(xIter));
+            xIter = XMLRPC_VectorNext(xParams);
+         }
+         bAll = 0;
+      }
+   }
+
+   /* otherwise, default to sending all methods */
+   if(bAll) {
+      q_iter qi = Q_Iter_Head_F(&server->methodlist);
+      while( qi ) {
+         server_method* sm = Q_Iter_Get_F(qi);
+         if(sm) {
+            XMLRPC_AddValueToVector(xMethodList, sm->desc);
+         }
+         qi = Q_Iter_Next_F(qi);
+      }
+   }
+   
+   return xResponse;
+}
+
+/* this complies with system.listMethods as defined at http://xmlrpc.usefulinc.com/doc/reserved.html */
+static XMLRPC_VALUE xi_system_list_methods_cb(XMLRPC_SERVER server, XMLRPC_REQUEST input, void* userData) {
+   XMLRPC_VALUE xResponse = XMLRPC_CreateVector(NULL, xmlrpc_vector_array);
+
+   q_iter qi = Q_Iter_Head_F(&server->methodlist);
+   while( qi ) {
+      server_method* sm = Q_Iter_Get_F(qi);
+      if(sm) {
+         XMLRPC_VectorAppendString(xResponse, 0, sm->name, 0);
+      }
+      qi = Q_Iter_Next_F(qi);
+   }
+   return xResponse;
+}
+
+/* this complies with system.methodSignature as defined at 
+ * http://xmlrpc.usefulinc.com/doc/sysmethodsig.html 
+ */
+static XMLRPC_VALUE xi_system_method_signature_cb(XMLRPC_SERVER server, XMLRPC_REQUEST input, void* userData) {
+   const char* method = XMLRPC_GetValueString(XMLRPC_VectorRewind(XMLRPC_RequestGetData(input)));
+   XMLRPC_VALUE xResponse = NULL;
+
+   /* lazy loading of introspection data */
+   check_docs_loaded(server, userData);
+
+   if(method) {
+      server_method* sm = find_method(server, method);
+      if(sm && sm->desc) {
+         XMLRPC_VALUE xTypesArray = XMLRPC_CreateVector(NULL, xmlrpc_vector_array);
+         XMLRPC_VALUE xIter, xParams, xSig, xSigIter;
+         const char* type;
+
+         /* array of possible signatures.  */
+         xResponse = XMLRPC_CreateVector(NULL, xmlrpc_vector_array);
+
+         /* find first signature */
+         xSig = XMLRPC_VectorGetValueWithID(sm->desc, xi_token_signatures);
+         xSigIter = XMLRPC_VectorRewind( xSig );
+
+         /* iterate through sigs */
+         while(xSigIter) {
+            /* first type is the return value */
+            type = XMLRPC_VectorGetStringWithID(XMLRPC_VectorRewind(
+                                                 XMLRPC_VectorGetValueWithID(xSigIter, xi_token_returns)), 
+                                                xi_token_type);
+            XMLRPC_AddValueToVector(xTypesArray, 
+                                    XMLRPC_CreateValueString(NULL, 
+                                                             type ? type : type_to_str(xmlrpc_none, 0), 
+                                    0));
+
+            /* the rest are parameters */
+            xParams = XMLRPC_VectorGetValueWithID(xSigIter, xi_token_params);
+            xIter = XMLRPC_VectorRewind(xParams);
+
+            /* iter through params, adding to types array */
+            while(xIter) {
+               XMLRPC_AddValueToVector(xTypesArray,
+                                       XMLRPC_CreateValueString(NULL, 
+                                                                XMLRPC_VectorGetStringWithID(xIter, xi_token_type),
+                                                                0));
+               xIter = XMLRPC_VectorNext(xParams);
+            }
+
+            /* add types for this signature */
+            XMLRPC_AddValueToVector(xResponse, xTypesArray);
+
+            xSigIter = XMLRPC_VectorNext( xSig );
+         }
+      }
+   }
+
+   return xResponse;
+}
+
+/* this complies with system.methodHelp as defined at 
+ * http://xmlrpc.usefulinc.com/doc/sysmethhelp.html 
+ */
+static XMLRPC_VALUE xi_system_method_help_cb(XMLRPC_SERVER server, XMLRPC_REQUEST input, void* userData) {
+   const char* method = XMLRPC_GetValueString(XMLRPC_VectorRewind(XMLRPC_RequestGetData(input)));
+   XMLRPC_VALUE xResponse = NULL;
+
+   /* lazy loading of introspection data */
+   check_docs_loaded(server, userData);
+
+   if(method) {
+      server_method* sm = find_method(server, method);
+      if(sm && sm->desc) {
+         const char* help = XMLRPC_VectorGetStringWithID(sm->desc, xi_token_purpose);
+
+         /* returns a documentation string, or empty string */
+         xResponse = XMLRPC_CreateValueString(NULL, help ? help : xi_token_empty, 0);
+      }
+   }
+
+   return xResponse;
+}
+
+/*-**************************************
+* End Introspection Callbacks (methods) *
+****************************************/
+
+
+/*-************************
+* Introspection Utilities *
+**************************/
+
+/* performs registration of introspection methods */
+void xi_register_system_methods(XMLRPC_SERVER server) {
+   XMLRPC_ServerRegisterMethod(server, xi_token_system_list_methods, xi_system_list_methods_cb);
+   XMLRPC_ServerRegisterMethod(server, xi_token_system_method_help, xi_system_method_help_cb);
+   XMLRPC_ServerRegisterMethod(server, xi_token_system_method_signature, xi_system_method_signature_cb);
+   XMLRPC_ServerRegisterMethod(server, xi_token_system_describe_methods, xi_system_describe_methods_cb);
+}
+
+/* describe a value (param, return, type) */
+static XMLRPC_VALUE describeValue_worker(const char* type, const char* id, const char* desc, int optional, const char* default_val, XMLRPC_VALUE sub_params) {
+   XMLRPC_VALUE xParam = NULL;
+   if(id || desc) {
+      xParam = XMLRPC_CreateVector(NULL, xmlrpc_vector_struct);
+      XMLRPC_VectorAppendString(xParam, xi_token_name, id, 0);
+      XMLRPC_VectorAppendString(xParam, xi_token_type, type, 0);
+      XMLRPC_VectorAppendString(xParam, xi_token_description, desc, 0);
+      if(optional != 2) {
+         XMLRPC_VectorAppendInt(xParam, xi_token_optional, optional);
+      }
+      if(optional == 1 && default_val) {
+         XMLRPC_VectorAppendString(xParam, xi_token_default, default_val, 0);
+      }
+      XMLRPC_AddValueToVector(xParam, sub_params);
+   }
+   return xParam;
+}
+
+
+/* convert an xml tree conforming to spec <url tbd> to  XMLRPC_VALUE
+ * suitable for use with XMLRPC_ServerAddIntrospectionData
+ */
+XMLRPC_VALUE xml_element_to_method_description(xml_element* el, XMLRPC_ERROR err) {
+   XMLRPC_VALUE xReturn = NULL;
+
+   if(el->name) {
+      const char* name = NULL;
+      const char* type = NULL;
+      const char* basetype = NULL;
+      const char* desc = NULL;
+      const char* def = NULL;
+      int optional = 0;
+      xml_element_attr* attr_iter = Q_Head(&el->attrs);
+
+      /* grab element attributes up front to save redundant while loops */
+      while(attr_iter) {
+         if(!strcmp(attr_iter->key, "name")) {
+            name = attr_iter->val;
+         }
+         else if(!strcmp(attr_iter->key, "type")) {
+            type = attr_iter->val;
+         }
+         else if(!strcmp(attr_iter->key, "basetype")) {
+            basetype = attr_iter->val;
+         }
+         else if(!strcmp(attr_iter->key, "desc")) {
+            desc = attr_iter->val;
+         }
+         else if(!strcmp(attr_iter->key, "optional")) {
+            if(attr_iter->val && !strcmp(attr_iter->val, "yes")) {
+               optional = 1;
+            }
+         }
+         else if(!strcmp(attr_iter->key, "default")) {
+            def = attr_iter->val;
+         }
+         attr_iter = Q_Next(&el->attrs);
+      }
+
+      /* value and typeDescription behave about the same */
+      if(!strcmp(el->name, "value") || !strcmp(el->name, "typeDescription")) {
+         XMLRPC_VALUE xSubList = NULL;
+         const char* ptype = !strcmp(el->name, "value") ? type : basetype;
+         if(ptype) {
+            if(Q_Size(&el->children) &&
+               (!strcmp(ptype, "array") || !strcmp(ptype, "struct") || !strcmp(ptype, "mixed"))) {
+               xSubList = XMLRPC_CreateVector("member", xmlrpc_vector_array);
+
+               if(xSubList) {
+                  xml_element* elem_iter = Q_Head(&el->children);
+                  while(elem_iter) {
+                     XMLRPC_AddValueToVector(xSubList, 
+                                             xml_element_to_method_description(elem_iter, err));
+                     elem_iter = Q_Next(&el->children);
+                  }
+               }
+            }
+            xReturn = describeValue_worker(ptype, name, (desc ? desc : (xSubList ? NULL : el->text.str)), optional, def, xSubList);
+         }
+      }
+
+      /* these three kids are about equivalent */
+      else if(!strcmp(el->name, "params") || 
+              !strcmp(el->name, "returns") || 
+              !strcmp(el->name, "signature")) {
+         if(Q_Size(&el->children)) {
+            xml_element* elem_iter = Q_Head(&el->children);
+            xReturn = XMLRPC_CreateVector(!strcmp(el->name, "signature") ? NULL : el->name, xmlrpc_vector_struct);
+
+
+            while(elem_iter) {
+               XMLRPC_AddValueToVector(xReturn, 
+                                       xml_element_to_method_description(elem_iter, err));
+               elem_iter = Q_Next(&el->children);
+            }
+         }
+      }
+
+
+      else if(!strcmp(el->name, "methodDescription")) {
+         xml_element* elem_iter = Q_Head(&el->children);
+         xReturn = XMLRPC_CreateVector(NULL, xmlrpc_vector_struct);
+
+         XMLRPC_VectorAppendString(xReturn, xi_token_name, name, 0);
+
+         while(elem_iter) {
+            XMLRPC_AddValueToVector(xReturn, 
+                                    xml_element_to_method_description(elem_iter, err));
+            elem_iter = Q_Next(&el->children);
+         }
+      }
+
+      /* items are slightly special */
+      else if(!strcmp(el->name, "item")) {
+         xReturn = XMLRPC_CreateValueString(name, el->text.str, el->text.len);
+      }
+
+      /* sure.  we'll let any ol element with children through */
+      else if(Q_Size(&el->children)) {
+         xml_element* elem_iter = Q_Head(&el->children);
+         xReturn = XMLRPC_CreateVector(el->name, xmlrpc_vector_mixed);
+
+         while(elem_iter) {
+            XMLRPC_AddValueToVector(xReturn, 
+                                    xml_element_to_method_description(elem_iter, err));
+            elem_iter = Q_Next(&el->children);
+         }
+      }
+
+      /* or anything at all really, so long as its got some text. 
+       * no reason being all snotty about a spec, right? 
+       */
+      else if(el->name && el->text.len) {
+         xReturn = XMLRPC_CreateValueString(el->name, el->text.str, el->text.len);
+      }
+   }
+
+   return xReturn;
+}
+
+/*-****************************
+* End Introspection Utilities *
+******************************/
+
+
+
+/*-******************
+* Introspection API *
+********************/
+
+
+/****f* VALUE/XMLRPC_IntrospectionCreateDescription
+ * NAME
+ *   XMLRPC_IntrospectionCreateDescription
+ * SYNOPSIS
+ *   XMLRPC_VALUE XMLRPC_IntrospectionCreateDescription(const char* xml, XMLRPC_ERROR err)
+ * FUNCTION
+ *   converts raw xml describing types and methods into an
+ *   XMLRPC_VALUE suitable for use with XMLRPC_ServerAddIntrospectionData()
+ * INPUTS
+ *   xml - xml data conforming to introspection spec at <url tbd>
+ *   err - optional pointer to error struct. filled in if error occurs and not NULL.
+ * RESULT
+ *   XMLRPC_VALUE - newly created value, or NULL if fatal error.
+ * BUGS
+ *   Currently does little or no validation of xml.
+ *   Only parse errors are currently reported in err, not structural errors.
+ * SEE ALSO
+ *   XMLRPC_ServerAddIntrospectionData ()
+ * SOURCE
+ */
+XMLRPC_VALUE XMLRPC_IntrospectionCreateDescription(const char* xml, XMLRPC_ERROR err) {
+   XMLRPC_VALUE xReturn = NULL;
+   xml_element* root = xml_elem_parse_buf(xml, 0, 0, err ? &err->xml_elem_error : NULL);
+
+   if(root) {
+      xReturn = xml_element_to_method_description(root, err);
+
+      xml_elem_free(root);
+   }
+
+   return xReturn;
+
+}
+/*******/
+
+
+/****f* SERVER/XMLRPC_ServerAddIntrospectionData
+ * NAME
+ *   XMLRPC_ServerAddIntrospectionData
+ * SYNOPSIS
+ *   int XMLRPC_ServerAddIntrospectionData(XMLRPC_SERVER server, XMLRPC_VALUE desc)
+ * FUNCTION
+ *   updates server with additional introspection data
+ * INPUTS
+ *   server - target server
+ *   desc - introspection data, should be a struct generated by 
+ *          XMLRPC_IntrospectionCreateDescription ()
+ * RESULT
+ *   int - 1 if success, else 0
+ * NOTES
+ *  - function will fail if neither typeList nor methodList key is present in struct.
+ *  - if method or type already exists, it will be replaced.
+ *  - desc is never freed by the server.  caller is responsible for cleanup.
+ * BUGS
+ *   - horribly slow lookups. prime candidate for hash improvements.
+ *   - uglier and more complex than I like to see for API functions.
+ * SEE ALSO
+ *   XMLRPC_ServerAddIntrospectionData ()
+ *   XMLRPC_ServerRegisterIntrospectionCallback ()
+ *   XMLRPC_CleanupValue ()
+ * SOURCE
+ */
+int XMLRPC_ServerAddIntrospectionData(XMLRPC_SERVER server, XMLRPC_VALUE desc) {
+   int bSuccess = 0;
+   if(server && desc) {
+      XMLRPC_VALUE xNewTypes = XMLRPC_VectorGetValueWithID(desc, "typeList");
+      XMLRPC_VALUE xNewMethods = XMLRPC_VectorGetValueWithID(desc, "methodList");
+      XMLRPC_VALUE xServerTypes = XMLRPC_VectorGetValueWithID(server->xIntrospection, "typeList");
+
+      if(xNewMethods) {
+         XMLRPC_VALUE xMethod = XMLRPC_VectorRewind(xNewMethods);
+
+         while(xMethod) {
+            const char* name = XMLRPC_VectorGetStringWithID(xMethod, xi_token_name);
+            server_method* sm = find_method(server, name);
+
+            if(sm) {
+               if(sm->desc) {
+                  XMLRPC_CleanupValue(sm->desc);
+               }
+               sm->desc = XMLRPC_CopyValue(xMethod);
+               bSuccess = 1;
+            }
+
+            xMethod = XMLRPC_VectorNext(xNewMethods);
+         }
+      }
+      if(xNewTypes) {
+         if(!xServerTypes) {
+            if(!server->xIntrospection) {
+               server->xIntrospection = XMLRPC_CreateVector(NULL, xmlrpc_vector_struct);
+            }
+
+            XMLRPC_AddValueToVector(server->xIntrospection, xNewTypes);
+            bSuccess = 1;
+         }
+         else {
+            XMLRPC_VALUE xIter = XMLRPC_VectorRewind(xNewTypes);
+            while(xIter) {
+               /* get rid of old values */
+               XMLRPC_VALUE xPrev = find_named_value(xServerTypes, XMLRPC_VectorGetStringWithID(xIter, xi_token_name));
+               if(xPrev) {
+                  XMLRPC_VectorRemoveValue(xServerTypes, xPrev);
+               }
+               XMLRPC_AddValueToVector(xServerTypes, xIter);
+               bSuccess = 1;
+               xIter = XMLRPC_VectorNext(xNewTypes);
+            }
+         }
+      }
+   }
+   return bSuccess;
+}
+/*******/
+
+
+/****f* SERVER/XMLRPC_ServerRegisterIntrospectionCallback
+ * NAME
+ *   XMLRPC_ServerRegisterIntrospectionCallback
+ * SYNOPSIS
+ *   int XMLRPC_ServerRegisterIntrospectionCallback(XMLRPC_SERVER server, XMLRPC_IntrospectionCallback cb)
+ * FUNCTION
+ *   registers a callback for lazy generation of introspection data
+ * INPUTS
+ *   server - target server
+ *   cb - callback that will generate introspection data
+ * RESULT
+ *   int - 1 if success, else 0
+ * NOTES
+ *   parsing xml and generating introspection data is fairly expensive, thus a
+ *   server may wish to wait until this data is actually requested before generating
+ *   it. Any number of callbacks may be registered at any time.  A given callback
+ *   will only ever be called once, the first time an introspection request is
+ *   processed after the time of callback registration.
+ * SEE ALSO
+ *   XMLRPC_ServerAddIntrospectionData ()
+ *   XMLRPC_IntrospectionCreateDescription ()
+ * SOURCE
+ */
+int XMLRPC_ServerRegisterIntrospectionCallback(XMLRPC_SERVER server, XMLRPC_IntrospectionCallback cb) {
+   int bSuccess = 0;
+   if(server && cb) {
+
+      doc_method* dm = calloc(1, sizeof(doc_method));
+      
+      if(dm) {
+         dm->method = cb;
+         dm->b_called = 0;
+
+         if(Q_PushTail(&server->docslist, dm)) {
+            bSuccess = 1;
+         }
+         else {
+            my_free(dm);
+         }
+      }
+   }
+   return 0;
+}
+/*******/
+
+/*-**********************
+* End Introspection API *
+************************/
+
+
+