2 This file is part of libXMLRPC - a C library for xml-encoded function calls.
4 Author: Dan Libby (dan@libby.com)
5 Epinions.com may be contacted at feedback@epinions-inc.com
9 Copyright 2001 Epinions, Inc.
11 Subject to the following 3 conditions, Epinions, Inc. permits you, free
12 of charge, to (a) use, copy, distribute, modify, perform and display this
13 software and associated documentation files (the "Software"), and (b)
14 permit others to whom the Software is furnished to do so as well.
16 1) The above copyright notice and this permission notice shall be included
17 without modification in all copies or substantial portions of the
20 2) THE SOFTWARE IS PROVIDED "AS IS", WITHOUT ANY WARRANTY OR CONDITION OF
21 ANY KIND, EXPRESS, IMPLIED OR STATUTORY, INCLUDING WITHOUT LIMITATION ANY
22 IMPLIED WARRANTIES OF ACCURACY, MERCHANTABILITY, FITNESS FOR A PARTICULAR
23 PURPOSE OR NONINFRINGEMENT.
25 3) IN NO EVENT SHALL EPINIONS, INC. BE LIABLE FOR ANY DIRECT, INDIRECT,
26 SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES OR LOST PROFITS ARISING OUT
27 OF OR IN CONNECTION WITH THE SOFTWARE (HOWEVER ARISING, INCLUDING
28 NEGLIGENCE), EVEN IF EPINIONS, INC. IS AWARE OF THE POSSIBILITY OF SUCH
34 /****h* ABOUT/xmlrpc_introspection
36 * Dan Libby, aka danda (dan@libby.com)
38 * $Log: xmlrpc_introspection.c,v $
39 * Revision 1.4 2003/12/16 21:00:21 sniper
40 * Fix some compile warnings (patch by Joe Orton)
42 * Revision 1.3 2002/07/05 04:43:53 danda
43 * merged in updates from SF project. bring php repository up to date with xmlrpc-epi version 0.51
45 * Revision 1.9 2001/09/29 21:58:05 danda
46 * adding cvs log to history section
48 * 4/10/2001 -- danda -- initial introspection support
55 #include "xmlrpc_win32.h"
59 #include "xmlrpc_private.h"
60 #include "xmlrpc_introspection_private.h"
66 /* forward declarations for static (non public, non api) funcs */
67 static XMLRPC_VALUE xi_system_describe_methods_cb(XMLRPC_SERVER server, XMLRPC_REQUEST input, void* userData);
68 static XMLRPC_VALUE xi_system_list_methods_cb(XMLRPC_SERVER server, XMLRPC_REQUEST input, void* userData);
69 static XMLRPC_VALUE xi_system_method_signature_cb(XMLRPC_SERVER server, XMLRPC_REQUEST input, void* userData);
70 static XMLRPC_VALUE xi_system_method_help_cb(XMLRPC_SERVER server, XMLRPC_REQUEST input, void* userData);
73 /*-**********************************
74 * Introspection Callbacks (methods) *
75 ************************************/
77 /* iterates through a list of structs and finds the one with key "name" matching
78 * needle. slow, would benefit from a struct key hash.
80 inline XMLRPC_VALUE find_named_value(XMLRPC_VALUE list, const char* needle) {
81 XMLRPC_VALUE xIter = XMLRPC_VectorRewind(list);
83 const char* name = XMLRPC_VectorGetStringWithID(xIter, xi_token_name);
84 if(name && !strcmp(name, needle)) {
87 xIter = XMLRPC_VectorNext(list);
93 /* iterates through docs callbacks and calls any that have not yet been called */
94 static void check_docs_loaded(XMLRPC_SERVER server, void* userData) {
96 q_iter qi = Q_Iter_Head_F(&server->docslist);
98 doc_method* dm = Q_Iter_Get_F(qi);
99 if(dm && !dm->b_called) {
100 dm->method(server, userData);
103 qi = Q_Iter_Next_F(qi);
109 /* utility function for xi_system_describe_methods_cb */
110 inline void describe_method(XMLRPC_SERVER server, XMLRPC_VALUE vector, const char* method) {
112 server_method* sm = find_method(server, method);
114 XMLRPC_AddValueToVector(vector, sm->desc);
121 /* system.describeMethods() callback */
122 static XMLRPC_VALUE xi_system_describe_methods_cb(XMLRPC_SERVER server, XMLRPC_REQUEST input, void* userData) {
123 XMLRPC_VALUE xParams = XMLRPC_VectorRewind(XMLRPC_RequestGetData(input));
124 XMLRPC_VALUE xResponse = XMLRPC_CreateVector(NULL, xmlrpc_vector_struct);
125 XMLRPC_VALUE xMethodList = XMLRPC_CreateVector("methodList", xmlrpc_vector_array);
126 XMLRPC_VALUE xTypeList = NULL;
129 /* lazy loading of introspection data */
130 check_docs_loaded(server, userData);
132 xTypeList = XMLRPC_VectorGetValueWithID(server->xIntrospection, "typeList");
134 XMLRPC_AddValueToVector(xResponse, xTypeList);
135 XMLRPC_AddValueToVector(xResponse, xMethodList);
137 /* check if we have any param */
139 /* check if string or vector (1 or n) */
140 XMLRPC_VALUE_TYPE type = XMLRPC_GetValueType(xParams);
141 if(type == xmlrpc_string) {
142 /* just one. spit it out. */
143 describe_method(server, xMethodList, XMLRPC_GetValueString(xParams));
146 else if(type == xmlrpc_vector) {
147 /* multiple. spit all out */
148 XMLRPC_VALUE xIter = XMLRPC_VectorRewind(xParams);
150 describe_method(server, xMethodList, XMLRPC_GetValueString(xIter));
151 xIter = XMLRPC_VectorNext(xParams);
157 /* otherwise, default to sending all methods */
159 q_iter qi = Q_Iter_Head_F(&server->methodlist);
161 server_method* sm = Q_Iter_Get_F(qi);
163 XMLRPC_AddValueToVector(xMethodList, sm->desc);
165 qi = Q_Iter_Next_F(qi);
172 /* this complies with system.listMethods as defined at http://xmlrpc.usefulinc.com/doc/reserved.html */
173 static XMLRPC_VALUE xi_system_list_methods_cb(XMLRPC_SERVER server, XMLRPC_REQUEST input, void* userData) {
174 XMLRPC_VALUE xResponse = XMLRPC_CreateVector(NULL, xmlrpc_vector_array);
176 q_iter qi = Q_Iter_Head_F(&server->methodlist);
178 server_method* sm = Q_Iter_Get_F(qi);
180 XMLRPC_VectorAppendString(xResponse, 0, sm->name, 0);
182 qi = Q_Iter_Next_F(qi);
187 /* this complies with system.methodSignature as defined at
188 * http://xmlrpc.usefulinc.com/doc/sysmethodsig.html
190 static XMLRPC_VALUE xi_system_method_signature_cb(XMLRPC_SERVER server, XMLRPC_REQUEST input, void* userData) {
191 const char* method = XMLRPC_GetValueString(XMLRPC_VectorRewind(XMLRPC_RequestGetData(input)));
192 XMLRPC_VALUE xResponse = NULL;
194 /* lazy loading of introspection data */
195 check_docs_loaded(server, userData);
198 server_method* sm = find_method(server, method);
200 XMLRPC_VALUE xTypesArray = XMLRPC_CreateVector(NULL, xmlrpc_vector_array);
201 XMLRPC_VALUE xIter, xParams, xSig, xSigIter;
204 /* array of possible signatures. */
205 xResponse = XMLRPC_CreateVector(NULL, xmlrpc_vector_array);
207 /* find first signature */
208 xSig = XMLRPC_VectorGetValueWithID(sm->desc, xi_token_signatures);
209 xSigIter = XMLRPC_VectorRewind( xSig );
211 /* iterate through sigs */
213 /* first type is the return value */
214 type = XMLRPC_VectorGetStringWithID(XMLRPC_VectorRewind(
215 XMLRPC_VectorGetValueWithID(xSigIter, xi_token_returns)),
217 XMLRPC_AddValueToVector(xTypesArray,
218 XMLRPC_CreateValueString(NULL,
219 type ? type : type_to_str(xmlrpc_none, 0),
222 /* the rest are parameters */
223 xParams = XMLRPC_VectorGetValueWithID(xSigIter, xi_token_params);
224 xIter = XMLRPC_VectorRewind(xParams);
226 /* iter through params, adding to types array */
228 XMLRPC_AddValueToVector(xTypesArray,
229 XMLRPC_CreateValueString(NULL,
230 XMLRPC_VectorGetStringWithID(xIter, xi_token_type),
232 xIter = XMLRPC_VectorNext(xParams);
235 /* add types for this signature */
236 XMLRPC_AddValueToVector(xResponse, xTypesArray);
238 xSigIter = XMLRPC_VectorNext( xSig );
246 /* this complies with system.methodHelp as defined at
247 * http://xmlrpc.usefulinc.com/doc/sysmethhelp.html
249 static XMLRPC_VALUE xi_system_method_help_cb(XMLRPC_SERVER server, XMLRPC_REQUEST input, void* userData) {
250 const char* method = XMLRPC_GetValueString(XMLRPC_VectorRewind(XMLRPC_RequestGetData(input)));
251 XMLRPC_VALUE xResponse = NULL;
253 /* lazy loading of introspection data */
254 check_docs_loaded(server, userData);
257 server_method* sm = find_method(server, method);
259 const char* help = XMLRPC_VectorGetStringWithID(sm->desc, xi_token_purpose);
261 /* returns a documentation string, or empty string */
262 xResponse = XMLRPC_CreateValueString(NULL, help ? help : xi_token_empty, 0);
269 /*-**************************************
270 * End Introspection Callbacks (methods) *
271 ****************************************/
274 /*-************************
275 * Introspection Utilities *
276 **************************/
278 /* performs registration of introspection methods */
279 void xi_register_system_methods(XMLRPC_SERVER server) {
280 XMLRPC_ServerRegisterMethod(server, xi_token_system_list_methods, xi_system_list_methods_cb);
281 XMLRPC_ServerRegisterMethod(server, xi_token_system_method_help, xi_system_method_help_cb);
282 XMLRPC_ServerRegisterMethod(server, xi_token_system_method_signature, xi_system_method_signature_cb);
283 XMLRPC_ServerRegisterMethod(server, xi_token_system_describe_methods, xi_system_describe_methods_cb);
286 /* describe a value (param, return, type) */
287 static XMLRPC_VALUE describeValue_worker(const char* type, const char* id, const char* desc, int optional, const char* default_val, XMLRPC_VALUE sub_params) {
288 XMLRPC_VALUE xParam = NULL;
290 xParam = XMLRPC_CreateVector(NULL, xmlrpc_vector_struct);
291 XMLRPC_VectorAppendString(xParam, xi_token_name, id, 0);
292 XMLRPC_VectorAppendString(xParam, xi_token_type, type, 0);
293 XMLRPC_VectorAppendString(xParam, xi_token_description, desc, 0);
295 XMLRPC_VectorAppendInt(xParam, xi_token_optional, optional);
297 if(optional == 1 && default_val) {
298 XMLRPC_VectorAppendString(xParam, xi_token_default, default_val, 0);
300 XMLRPC_AddValueToVector(xParam, sub_params);
306 /* convert an xml tree conforming to spec <url tbd> to XMLRPC_VALUE
307 * suitable for use with XMLRPC_ServerAddIntrospectionData
309 XMLRPC_VALUE xml_element_to_method_description(xml_element* el, XMLRPC_ERROR err) {
310 XMLRPC_VALUE xReturn = NULL;
313 const char* name = NULL;
314 const char* type = NULL;
315 const char* basetype = NULL;
316 const char* desc = NULL;
317 const char* def = NULL;
319 xml_element_attr* attr_iter = Q_Head(&el->attrs);
321 /* grab element attributes up front to save redundant while loops */
323 if(!strcmp(attr_iter->key, "name")) {
324 name = attr_iter->val;
326 else if(!strcmp(attr_iter->key, "type")) {
327 type = attr_iter->val;
329 else if(!strcmp(attr_iter->key, "basetype")) {
330 basetype = attr_iter->val;
332 else if(!strcmp(attr_iter->key, "desc")) {
333 desc = attr_iter->val;
335 else if(!strcmp(attr_iter->key, "optional")) {
336 if(attr_iter->val && !strcmp(attr_iter->val, "yes")) {
340 else if(!strcmp(attr_iter->key, "default")) {
341 def = attr_iter->val;
343 attr_iter = Q_Next(&el->attrs);
346 /* value and typeDescription behave about the same */
347 if(!strcmp(el->name, "value") || !strcmp(el->name, "typeDescription")) {
348 XMLRPC_VALUE xSubList = NULL;
349 const char* ptype = !strcmp(el->name, "value") ? type : basetype;
351 if(Q_Size(&el->children) &&
352 (!strcmp(ptype, "array") || !strcmp(ptype, "struct") || !strcmp(ptype, "mixed"))) {
353 xSubList = XMLRPC_CreateVector("member", xmlrpc_vector_array);
356 xml_element* elem_iter = Q_Head(&el->children);
358 XMLRPC_AddValueToVector(xSubList,
359 xml_element_to_method_description(elem_iter, err));
360 elem_iter = Q_Next(&el->children);
364 xReturn = describeValue_worker(ptype, name, (desc ? desc : (xSubList ? NULL : el->text.str)), optional, def, xSubList);
368 /* these three kids are about equivalent */
369 else if(!strcmp(el->name, "params") ||
370 !strcmp(el->name, "returns") ||
371 !strcmp(el->name, "signature")) {
372 if(Q_Size(&el->children)) {
373 xml_element* elem_iter = Q_Head(&el->children);
374 xReturn = XMLRPC_CreateVector(!strcmp(el->name, "signature") ? NULL : el->name, xmlrpc_vector_struct);
378 XMLRPC_AddValueToVector(xReturn,
379 xml_element_to_method_description(elem_iter, err));
380 elem_iter = Q_Next(&el->children);
386 else if(!strcmp(el->name, "methodDescription")) {
387 xml_element* elem_iter = Q_Head(&el->children);
388 xReturn = XMLRPC_CreateVector(NULL, xmlrpc_vector_struct);
390 XMLRPC_VectorAppendString(xReturn, xi_token_name, name, 0);
393 XMLRPC_AddValueToVector(xReturn,
394 xml_element_to_method_description(elem_iter, err));
395 elem_iter = Q_Next(&el->children);
399 /* items are slightly special */
400 else if(!strcmp(el->name, "item")) {
401 xReturn = XMLRPC_CreateValueString(name, el->text.str, el->text.len);
404 /* sure. we'll let any ol element with children through */
405 else if(Q_Size(&el->children)) {
406 xml_element* elem_iter = Q_Head(&el->children);
407 xReturn = XMLRPC_CreateVector(el->name, xmlrpc_vector_mixed);
410 XMLRPC_AddValueToVector(xReturn,
411 xml_element_to_method_description(elem_iter, err));
412 elem_iter = Q_Next(&el->children);
416 /* or anything at all really, so long as its got some text.
417 * no reason being all snotty about a spec, right?
419 else if(el->name && el->text.len) {
420 xReturn = XMLRPC_CreateValueString(el->name, el->text.str, el->text.len);
427 /*-****************************
428 * End Introspection Utilities *
429 ******************************/
433 /*-******************
434 * Introspection API *
435 ********************/
438 /****f* VALUE/XMLRPC_IntrospectionCreateDescription
440 * XMLRPC_IntrospectionCreateDescription
442 * XMLRPC_VALUE XMLRPC_IntrospectionCreateDescription(const char* xml, XMLRPC_ERROR err)
444 * converts raw xml describing types and methods into an
445 * XMLRPC_VALUE suitable for use with XMLRPC_ServerAddIntrospectionData()
447 * xml - xml data conforming to introspection spec at <url tbd>
448 * err - optional pointer to error struct. filled in if error occurs and not NULL.
450 * XMLRPC_VALUE - newly created value, or NULL if fatal error.
452 * Currently does little or no validation of xml.
453 * Only parse errors are currently reported in err, not structural errors.
455 * XMLRPC_ServerAddIntrospectionData ()
458 XMLRPC_VALUE XMLRPC_IntrospectionCreateDescription(const char* xml, XMLRPC_ERROR err) {
459 XMLRPC_VALUE xReturn = NULL;
460 xml_element* root = xml_elem_parse_buf(xml, 0, 0, err ? &err->xml_elem_error : NULL);
463 xReturn = xml_element_to_method_description(root, err);
474 /****f* SERVER/XMLRPC_ServerAddIntrospectionData
476 * XMLRPC_ServerAddIntrospectionData
478 * int XMLRPC_ServerAddIntrospectionData(XMLRPC_SERVER server, XMLRPC_VALUE desc)
480 * updates server with additional introspection data
482 * server - target server
483 * desc - introspection data, should be a struct generated by
484 * XMLRPC_IntrospectionCreateDescription ()
486 * int - 1 if success, else 0
488 * - function will fail if neither typeList nor methodList key is present in struct.
489 * - if method or type already exists, it will be replaced.
490 * - desc is never freed by the server. caller is responsible for cleanup.
492 * - horribly slow lookups. prime candidate for hash improvements.
493 * - uglier and more complex than I like to see for API functions.
495 * XMLRPC_ServerAddIntrospectionData ()
496 * XMLRPC_ServerRegisterIntrospectionCallback ()
497 * XMLRPC_CleanupValue ()
500 int XMLRPC_ServerAddIntrospectionData(XMLRPC_SERVER server, XMLRPC_VALUE desc) {
503 XMLRPC_VALUE xNewTypes = XMLRPC_VectorGetValueWithID(desc, "typeList");
504 XMLRPC_VALUE xNewMethods = XMLRPC_VectorGetValueWithID(desc, "methodList");
505 XMLRPC_VALUE xServerTypes = XMLRPC_VectorGetValueWithID(server->xIntrospection, "typeList");
508 XMLRPC_VALUE xMethod = XMLRPC_VectorRewind(xNewMethods);
511 const char* name = XMLRPC_VectorGetStringWithID(xMethod, xi_token_name);
512 server_method* sm = find_method(server, name);
516 XMLRPC_CleanupValue(sm->desc);
518 sm->desc = XMLRPC_CopyValue(xMethod);
522 xMethod = XMLRPC_VectorNext(xNewMethods);
527 if(!server->xIntrospection) {
528 server->xIntrospection = XMLRPC_CreateVector(NULL, xmlrpc_vector_struct);
531 XMLRPC_AddValueToVector(server->xIntrospection, xNewTypes);
535 XMLRPC_VALUE xIter = XMLRPC_VectorRewind(xNewTypes);
537 /* get rid of old values */
538 XMLRPC_VALUE xPrev = find_named_value(xServerTypes, XMLRPC_VectorGetStringWithID(xIter, xi_token_name));
540 XMLRPC_VectorRemoveValue(xServerTypes, xPrev);
542 XMLRPC_AddValueToVector(xServerTypes, xIter);
544 xIter = XMLRPC_VectorNext(xNewTypes);
554 /****f* SERVER/XMLRPC_ServerRegisterIntrospectionCallback
556 * XMLRPC_ServerRegisterIntrospectionCallback
558 * int XMLRPC_ServerRegisterIntrospectionCallback(XMLRPC_SERVER server, XMLRPC_IntrospectionCallback cb)
560 * registers a callback for lazy generation of introspection data
562 * server - target server
563 * cb - callback that will generate introspection data
565 * int - 1 if success, else 0
567 * parsing xml and generating introspection data is fairly expensive, thus a
568 * server may wish to wait until this data is actually requested before generating
569 * it. Any number of callbacks may be registered at any time. A given callback
570 * will only ever be called once, the first time an introspection request is
571 * processed after the time of callback registration.
573 * XMLRPC_ServerAddIntrospectionData ()
574 * XMLRPC_IntrospectionCreateDescription ()
577 int XMLRPC_ServerRegisterIntrospectionCallback(XMLRPC_SERVER server, XMLRPC_IntrospectionCallback cb) {
581 doc_method* dm = calloc(1, sizeof(doc_method));
587 if(Q_PushTail(&server->docslist, dm)) {
599 /*-**********************
600 * End Introspection API *
601 ************************/