1 /*
2   This file is part of libXMLRPC - a C library for xml-encoded function calls.
3 
4   Author: Dan Libby (dan@libby.com)
5   Epinions.com may be contacted at feedback@epinions-inc.com
6 */
7 
8 /*
9   Copyright 2001 Epinions, Inc.
10 
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.
15 
16   1) The above copyright notice and this permission notice shall be included
17   without modification in all copies or substantial portions of the
18   Software.
19 
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.
24 
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
29   DAMAGES.
30 
31 */
32 
33 
34 /****h* ABOUT/xmlrpc_introspection
35  * AUTHOR
36  *   Dan Libby, aka danda  (dan@libby.com)
37  * HISTORY
38  *   $Log$
39  *   Revision 1.4  2003/12/16 21:00:21  sniper
40  *   Fix some compile warnings (patch by Joe Orton)
41  *
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
44  *
45  *   Revision 1.9  2001/09/29 21:58:05  danda
46  *   adding cvs log to history section
47  *
48  *   4/10/2001 -- danda -- initial introspection support
49  * TODO
50  * NOTES
51  *******/
52 
53 
54 #ifdef _WIN32
55 #include "xmlrpc_win32.h"
56 #endif
57 #include "queue.h"
58 #include "xmlrpc.h"
59 #include "xmlrpc_private.h"
60 #include "xmlrpc_introspection_private.h"
61 #include <string.h>
62 #include <stdlib.h>
63 #include <stdarg.h>
64 
65 
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);
71 
72 
73 /*-**********************************
74 * Introspection Callbacks (methods) *
75 ************************************/
76 
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.
79  */
find_named_value(XMLRPC_VALUE list,const char * needle)80 static inline XMLRPC_VALUE find_named_value(XMLRPC_VALUE list, const char* needle) {
81    XMLRPC_VALUE xIter = XMLRPC_VectorRewind(list);
82    while(xIter) {
83       const char* name = XMLRPC_VectorGetStringWithID(xIter, xi_token_name);
84       if(name && !strcmp(name, needle)) {
85          return xIter;
86       }
87       xIter = XMLRPC_VectorNext(list);
88    }
89    return NULL;
90 }
91 
92 
93 /* iterates through docs callbacks and calls any that have not yet been called */
check_docs_loaded(XMLRPC_SERVER server,void * userData)94 static void check_docs_loaded(XMLRPC_SERVER server, void* userData) {
95    if(server) {
96       q_iter qi = Q_Iter_Head_F(&server->docslist);
97       while( qi ) {
98          doc_method* dm = Q_Iter_Get_F(qi);
99          if(dm && !dm->b_called) {
100             dm->method(server, userData);
101             dm->b_called = 1;
102          }
103          qi = Q_Iter_Next_F(qi);
104       }
105    }
106 }
107 
108 
109 /* utility function for xi_system_describe_methods_cb */
describe_method(XMLRPC_SERVER server,XMLRPC_VALUE vector,const char * method)110 static inline void describe_method(XMLRPC_SERVER server, XMLRPC_VALUE vector, const char* method) {
111    if(method) {
112       server_method* sm = find_method(server, method);
113       if(sm) {
114          XMLRPC_AddValueToVector(vector, sm->desc);
115       }
116    }
117 }
118 
119 
120 
121 /* system.describeMethods() callback */
xi_system_describe_methods_cb(XMLRPC_SERVER server,XMLRPC_REQUEST input,void * userData)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;
127    int bAll = 1;
128 
129    /* lazy loading of introspection data */
130    check_docs_loaded(server, userData);
131 
132    xTypeList = XMLRPC_VectorGetValueWithID(server->xIntrospection, "typeList");
133 
134    XMLRPC_AddValueToVector(xResponse, xTypeList);
135    XMLRPC_AddValueToVector(xResponse, xMethodList);
136 
137    /* check if we have any param */
138    if(xParams) {
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));
144          bAll = 0;
145       }
146       else if(type == xmlrpc_vector) {
147          /* multiple.  spit all out */
148          XMLRPC_VALUE xIter = XMLRPC_VectorRewind(xParams);
149          while(xIter) {
150             describe_method(server, xMethodList, XMLRPC_GetValueString(xIter));
151             xIter = XMLRPC_VectorNext(xParams);
152          }
153          bAll = 0;
154       }
155    }
156 
157    /* otherwise, default to sending all methods */
158    if(bAll) {
159       q_iter qi = Q_Iter_Head_F(&server->methodlist);
160       while( qi ) {
161          server_method* sm = Q_Iter_Get_F(qi);
162          if(sm) {
163             XMLRPC_AddValueToVector(xMethodList, sm->desc);
164          }
165          qi = Q_Iter_Next_F(qi);
166       }
167    }
168 
169    return xResponse;
170 }
171 
172 /* this complies with system.listMethods as defined at http://xmlrpc.usefulinc.com/doc/reserved.html */
xi_system_list_methods_cb(XMLRPC_SERVER server,XMLRPC_REQUEST input,void * userData)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);
175 
176    q_iter qi = Q_Iter_Head_F(&server->methodlist);
177    while( qi ) {
178       server_method* sm = Q_Iter_Get_F(qi);
179       if(sm) {
180          XMLRPC_VectorAppendString(xResponse, 0, sm->name, 0);
181       }
182       qi = Q_Iter_Next_F(qi);
183    }
184    return xResponse;
185 }
186 
187 /* this complies with system.methodSignature as defined at
188  * http://xmlrpc.usefulinc.com/doc/sysmethodsig.html
189  */
xi_system_method_signature_cb(XMLRPC_SERVER server,XMLRPC_REQUEST input,void * userData)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;
193 
194    /* lazy loading of introspection data */
195    check_docs_loaded(server, userData);
196 
197    if(method) {
198       server_method* sm = find_method(server, method);
199       if(sm && sm->desc) {
200          XMLRPC_VALUE xTypesArray = XMLRPC_CreateVector(NULL, xmlrpc_vector_array);
201          XMLRPC_VALUE xIter, xParams, xSig, xSigIter;
202          const char* type;
203 
204          /* array of possible signatures.  */
205          xResponse = XMLRPC_CreateVector(NULL, xmlrpc_vector_array);
206 
207          /* find first signature */
208          xSig = XMLRPC_VectorGetValueWithID(sm->desc, xi_token_signatures);
209          xSigIter = XMLRPC_VectorRewind( xSig );
210 
211          /* iterate through sigs */
212          while(xSigIter) {
213             /* first type is the return value */
214             type = XMLRPC_VectorGetStringWithID(XMLRPC_VectorRewind(
215                                                  XMLRPC_VectorGetValueWithID(xSigIter, xi_token_returns)),
216                                                 xi_token_type);
217             XMLRPC_AddValueToVector(xTypesArray,
218                                     XMLRPC_CreateValueString(NULL,
219                                                              type ? type : type_to_str(xmlrpc_none, 0),
220                                     0));
221 
222             /* the rest are parameters */
223             xParams = XMLRPC_VectorGetValueWithID(xSigIter, xi_token_params);
224             xIter = XMLRPC_VectorRewind(xParams);
225 
226             /* iter through params, adding to types array */
227             while(xIter) {
228                XMLRPC_AddValueToVector(xTypesArray,
229                                        XMLRPC_CreateValueString(NULL,
230                                                                 XMLRPC_VectorGetStringWithID(xIter, xi_token_type),
231                                                                 0));
232                xIter = XMLRPC_VectorNext(xParams);
233             }
234 
235             /* add types for this signature */
236             XMLRPC_AddValueToVector(xResponse, xTypesArray);
237 
238             xSigIter = XMLRPC_VectorNext( xSig );
239          }
240       }
241    }
242 
243    return xResponse;
244 }
245 
246 /* this complies with system.methodHelp as defined at
247  * http://xmlrpc.usefulinc.com/doc/sysmethhelp.html
248  */
xi_system_method_help_cb(XMLRPC_SERVER server,XMLRPC_REQUEST input,void * userData)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;
252 
253    /* lazy loading of introspection data */
254    check_docs_loaded(server, userData);
255 
256    if(method) {
257       server_method* sm = find_method(server, method);
258       if(sm && sm->desc) {
259          const char* help = XMLRPC_VectorGetStringWithID(sm->desc, xi_token_purpose);
260 
261          /* returns a documentation string, or empty string */
262          xResponse = XMLRPC_CreateValueString(NULL, help ? help : xi_token_empty, 0);
263       }
264    }
265 
266    return xResponse;
267 }
268 
269 /*-**************************************
270 * End Introspection Callbacks (methods) *
271 ****************************************/
272 
273 
274 /*-************************
275 * Introspection Utilities *
276 **************************/
277 
278 /* performs registration of introspection methods */
xi_register_system_methods(XMLRPC_SERVER server)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);
284 }
285 
286 /* describe a value (param, return, type) */
describeValue_worker(const char * type,const char * id,const char * desc,int optional,const char * default_val,XMLRPC_VALUE sub_params)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;
289    if(id || desc) {
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);
294       if(optional != 2) {
295          XMLRPC_VectorAppendInt(xParam, xi_token_optional, optional);
296       }
297       if(optional == 1 && default_val) {
298          XMLRPC_VectorAppendString(xParam, xi_token_default, default_val, 0);
299       }
300       XMLRPC_AddValueToVector(xParam, sub_params);
301    }
302    return xParam;
303 }
304 
305 
306 /* convert an xml tree conforming to spec <url tbd> to  XMLRPC_VALUE
307  * suitable for use with XMLRPC_ServerAddIntrospectionData
308  */
xml_element_to_method_description(xml_element * el,XMLRPC_ERROR err)309 XMLRPC_VALUE xml_element_to_method_description(xml_element* el, XMLRPC_ERROR err) {
310    XMLRPC_VALUE xReturn = NULL;
311 
312    if(el->name) {
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;
318       int optional = 0;
319       xml_element_attr* attr_iter = Q_Head(&el->attrs);
320 
321       /* grab element attributes up front to save redundant while loops */
322       while(attr_iter) {
323          if(!strcmp(attr_iter->key, "name")) {
324             name = attr_iter->val;
325          }
326          else if(!strcmp(attr_iter->key, "type")) {
327             type = attr_iter->val;
328          }
329          else if(!strcmp(attr_iter->key, "basetype")) {
330             basetype = attr_iter->val;
331          }
332          else if(!strcmp(attr_iter->key, "desc")) {
333             desc = attr_iter->val;
334          }
335          else if(!strcmp(attr_iter->key, "optional")) {
336             if(attr_iter->val && !strcmp(attr_iter->val, "yes")) {
337                optional = 1;
338             }
339          }
340          else if(!strcmp(attr_iter->key, "default")) {
341             def = attr_iter->val;
342          }
343          attr_iter = Q_Next(&el->attrs);
344       }
345 
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;
350          if(ptype) {
351             if(Q_Size(&el->children) &&
352                (!strcmp(ptype, "array") || !strcmp(ptype, "struct") || !strcmp(ptype, "mixed"))) {
353                xSubList = XMLRPC_CreateVector("member", xmlrpc_vector_array);
354 
355                if(xSubList) {
356                   xml_element* elem_iter = Q_Head(&el->children);
357                   while(elem_iter) {
358                      XMLRPC_AddValueToVector(xSubList,
359                                              xml_element_to_method_description(elem_iter, err));
360                      elem_iter = Q_Next(&el->children);
361                   }
362                }
363             }
364             xReturn = describeValue_worker(ptype, name, (desc ? desc : (xSubList ? NULL : el->text.str)), optional, def, xSubList);
365          }
366       }
367 
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);
375 
376 
377             while(elem_iter) {
378                XMLRPC_AddValueToVector(xReturn,
379                                        xml_element_to_method_description(elem_iter, err));
380                elem_iter = Q_Next(&el->children);
381             }
382          }
383       }
384 
385 
386       else if(!strcmp(el->name, "methodDescription")) {
387          xml_element* elem_iter = Q_Head(&el->children);
388          xReturn = XMLRPC_CreateVector(NULL, xmlrpc_vector_struct);
389 
390          XMLRPC_VectorAppendString(xReturn, xi_token_name, name, 0);
391 
392          while(elem_iter) {
393             XMLRPC_AddValueToVector(xReturn,
394                                     xml_element_to_method_description(elem_iter, err));
395             elem_iter = Q_Next(&el->children);
396          }
397       }
398 
399       /* items are slightly special */
400       else if(!strcmp(el->name, "item")) {
401          xReturn = XMLRPC_CreateValueString(name, el->text.str, el->text.len);
402       }
403 
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);
408 
409          while(elem_iter) {
410             XMLRPC_AddValueToVector(xReturn,
411                                     xml_element_to_method_description(elem_iter, err));
412             elem_iter = Q_Next(&el->children);
413          }
414       }
415 
416       /* or anything at all really, so long as its got some text.
417        * no reason being all snotty about a spec, right?
418        */
419       else if(el->name && el->text.len) {
420          xReturn = XMLRPC_CreateValueString(el->name, el->text.str, el->text.len);
421       }
422    }
423 
424    return xReturn;
425 }
426 
427 /*-****************************
428 * End Introspection Utilities *
429 ******************************/
430 
431 
432 
433 /*-******************
434 * Introspection API *
435 ********************/
436 
437 
438 /****f* VALUE/XMLRPC_IntrospectionCreateDescription
439  * NAME
440  *   XMLRPC_IntrospectionCreateDescription
441  * SYNOPSIS
442  *   XMLRPC_VALUE XMLRPC_IntrospectionCreateDescription(const char* xml, XMLRPC_ERROR err)
443  * FUNCTION
444  *   converts raw xml describing types and methods into an
445  *   XMLRPC_VALUE suitable for use with XMLRPC_ServerAddIntrospectionData()
446  * INPUTS
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.
449  * RESULT
450  *   XMLRPC_VALUE - newly created value, or NULL if fatal error.
451  * BUGS
452  *   Currently does little or no validation of xml.
453  *   Only parse errors are currently reported in err, not structural errors.
454  * SEE ALSO
455  *   XMLRPC_ServerAddIntrospectionData ()
456  * SOURCE
457  */
XMLRPC_IntrospectionCreateDescription(const char * xml,XMLRPC_ERROR err)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);
461 
462    if(root) {
463       xReturn = xml_element_to_method_description(root, err);
464 
465       xml_elem_free(root);
466    }
467 
468    return xReturn;
469 
470 }
471 /*******/
472 
473 
474 /****f* SERVER/XMLRPC_ServerAddIntrospectionData
475  * NAME
476  *   XMLRPC_ServerAddIntrospectionData
477  * SYNOPSIS
478  *   int XMLRPC_ServerAddIntrospectionData(XMLRPC_SERVER server, XMLRPC_VALUE desc)
479  * FUNCTION
480  *   updates server with additional introspection data
481  * INPUTS
482  *   server - target server
483  *   desc - introspection data, should be a struct generated by
484  *          XMLRPC_IntrospectionCreateDescription ()
485  * RESULT
486  *   int - 1 if success, else 0
487  * NOTES
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.
491  * BUGS
492  *   - horribly slow lookups. prime candidate for hash improvements.
493  *   - uglier and more complex than I like to see for API functions.
494  * SEE ALSO
495  *   XMLRPC_ServerAddIntrospectionData ()
496  *   XMLRPC_ServerRegisterIntrospectionCallback ()
497  *   XMLRPC_CleanupValue ()
498  * SOURCE
499  */
XMLRPC_ServerAddIntrospectionData(XMLRPC_SERVER server,XMLRPC_VALUE desc)500 int XMLRPC_ServerAddIntrospectionData(XMLRPC_SERVER server, XMLRPC_VALUE desc) {
501    int bSuccess = 0;
502    if(server && 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");
506 
507       if(xNewMethods) {
508          XMLRPC_VALUE xMethod = XMLRPC_VectorRewind(xNewMethods);
509 
510          while(xMethod) {
511             const char* name = XMLRPC_VectorGetStringWithID(xMethod, xi_token_name);
512             server_method* sm = find_method(server, name);
513 
514             if(sm) {
515                if(sm->desc) {
516                   XMLRPC_CleanupValue(sm->desc);
517                }
518                sm->desc = XMLRPC_CopyValue(xMethod);
519                bSuccess = 1;
520             }
521 
522             xMethod = XMLRPC_VectorNext(xNewMethods);
523          }
524       }
525       if(xNewTypes) {
526          if(!xServerTypes) {
527             if(!server->xIntrospection) {
528                server->xIntrospection = XMLRPC_CreateVector(NULL, xmlrpc_vector_struct);
529             }
530 
531             XMLRPC_AddValueToVector(server->xIntrospection, xNewTypes);
532             bSuccess = 1;
533          }
534          else {
535             XMLRPC_VALUE xIter = XMLRPC_VectorRewind(xNewTypes);
536             while(xIter) {
537                /* get rid of old values */
538                XMLRPC_VALUE xPrev = find_named_value(xServerTypes, XMLRPC_VectorGetStringWithID(xIter, xi_token_name));
539                if(xPrev) {
540                   XMLRPC_VectorRemoveValue(xServerTypes, xPrev);
541                }
542                XMLRPC_AddValueToVector(xServerTypes, xIter);
543                bSuccess = 1;
544                xIter = XMLRPC_VectorNext(xNewTypes);
545             }
546          }
547       }
548    }
549    return bSuccess;
550 }
551 /*******/
552 
553 
554 /****f* SERVER/XMLRPC_ServerRegisterIntrospectionCallback
555  * NAME
556  *   XMLRPC_ServerRegisterIntrospectionCallback
557  * SYNOPSIS
558  *   int XMLRPC_ServerRegisterIntrospectionCallback(XMLRPC_SERVER server, XMLRPC_IntrospectionCallback cb)
559  * FUNCTION
560  *   registers a callback for lazy generation of introspection data
561  * INPUTS
562  *   server - target server
563  *   cb - callback that will generate introspection data
564  * RESULT
565  *   int - 1 if success, else 0
566  * NOTES
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.
572  * SEE ALSO
573  *   XMLRPC_ServerAddIntrospectionData ()
574  *   XMLRPC_IntrospectionCreateDescription ()
575  * SOURCE
576  */
XMLRPC_ServerRegisterIntrospectionCallback(XMLRPC_SERVER server,XMLRPC_IntrospectionCallback cb)577 int XMLRPC_ServerRegisterIntrospectionCallback(XMLRPC_SERVER server, XMLRPC_IntrospectionCallback cb) {
578    int bSuccess = 0;
579    if(server && cb) {
580 
581       doc_method* dm = calloc(1, sizeof(doc_method));
582 
583       if(dm) {
584          dm->method = cb;
585          dm->b_called = 0;
586 
587          if(Q_PushTail(&server->docslist, dm)) {
588             bSuccess = 1;
589          }
590          else {
591             my_free(dm);
592          }
593       }
594    }
595    return 0;
596 }
597 /*******/
598 
599 /*-**********************
600 * End Introspection API *
601 ************************/
602 
603 
604 
605