diff options
Diffstat (limited to 'ext/xmlrpc/libxmlrpc/xmlrpc_introspection.c')
-rw-r--r-- | ext/xmlrpc/libxmlrpc/xmlrpc_introspection.c | 591 |
1 files changed, 591 insertions, 0 deletions
diff --git a/ext/xmlrpc/libxmlrpc/xmlrpc_introspection.c b/ext/xmlrpc/libxmlrpc/xmlrpc_introspection.c new file mode 100644 index 0000000000..5b15d4b071 --- /dev/null +++ b/ext/xmlrpc/libxmlrpc/xmlrpc_introspection.c @@ -0,0 +1,591 @@ +/* + 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 + * 4/10/2001 -- danda -- initial introspection support + * TODO + * NOTES + *******/ + + +#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 * +************************/ + + + |