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 #include "queue.h"
55 #include "xmlrpc.h"
56 #include "xmlrpc_private.h"
57 #include "xmlrpc_introspection_private.h"
58 #include <string.h>
59 #include <stdlib.h>
60 #include <stdarg.h>
61 
62 
63 /* forward declarations for static (non public, non api) funcs */
64 static XMLRPC_VALUE xi_system_describe_methods_cb(XMLRPC_SERVER server, XMLRPC_REQUEST input, void* userData);
65 static XMLRPC_VALUE xi_system_list_methods_cb(XMLRPC_SERVER server, XMLRPC_REQUEST input, void* userData);
66 static XMLRPC_VALUE xi_system_method_signature_cb(XMLRPC_SERVER server, XMLRPC_REQUEST input, void* userData);
67 static XMLRPC_VALUE xi_system_method_help_cb(XMLRPC_SERVER server, XMLRPC_REQUEST input, void* userData);
68 
69 
70 /*-**********************************
71 * Introspection Callbacks (methods) *
72 ************************************/
73 
74 /* iterates through a list of structs and finds the one with key "name" matching
75  * needle.  slow, would benefit from a struct key hash.
76  */
find_named_value(XMLRPC_VALUE list,const char * needle)77 static inline XMLRPC_VALUE find_named_value(XMLRPC_VALUE list, const char* needle) {
78    XMLRPC_VALUE xIter = XMLRPC_VectorRewind(list);
79    while(xIter) {
80       const char* name = XMLRPC_VectorGetStringWithID(xIter, xi_token_name);
81       if(name && !strcmp(name, needle)) {
82          return xIter;
83       }
84       xIter = XMLRPC_VectorNext(list);
85    }
86    return NULL;
87 }
88 
89 
90 /* iterates through docs callbacks and calls any that have not yet been called */
check_docs_loaded(XMLRPC_SERVER server,void * userData)91 static void check_docs_loaded(XMLRPC_SERVER server, void* userData) {
92    if(server) {
93       q_iter qi = Q_Iter_Head_F(&server->docslist);
94       while( qi ) {
95          doc_method* dm = Q_Iter_Get_F(qi);
96          if(dm && !dm->b_called) {
97             dm->method(server, userData);
98             dm->b_called = 1;
99          }
100          qi = Q_Iter_Next_F(qi);
101       }
102    }
103 }
104 
105 
106 /* utility function for xi_system_describe_methods_cb */
describe_method(XMLRPC_SERVER server,XMLRPC_VALUE vector,const char * method)107 static inline void describe_method(XMLRPC_SERVER server, XMLRPC_VALUE vector, const char* method) {
108    if(method) {
109       server_method* sm = find_method(server, method);
110       if(sm) {
111          XMLRPC_AddValueToVector(vector, sm->desc);
112       }
113    }
114 }
115 
116 
117 
118 /* system.describeMethods() callback */
xi_system_describe_methods_cb(XMLRPC_SERVER server,XMLRPC_REQUEST input,void * userData)119 static XMLRPC_VALUE xi_system_describe_methods_cb(XMLRPC_SERVER server, XMLRPC_REQUEST input, void* userData) {
120    XMLRPC_VALUE xParams = XMLRPC_VectorRewind(XMLRPC_RequestGetData(input));
121    XMLRPC_VALUE xResponse = XMLRPC_CreateVector(NULL, xmlrpc_vector_struct);
122    XMLRPC_VALUE xMethodList = XMLRPC_CreateVector("methodList", xmlrpc_vector_array);
123    XMLRPC_VALUE xTypeList = NULL;
124    int bAll = 1;
125 
126    /* lazy loading of introspection data */
127    check_docs_loaded(server, userData);
128 
129    xTypeList = XMLRPC_VectorGetValueWithID(server->xIntrospection, "typeList");
130 
131    XMLRPC_AddValueToVector(xResponse, xTypeList);
132    XMLRPC_AddValueToVector(xResponse, xMethodList);
133 
134    /* check if we have any param */
135    if(xParams) {
136       /* check if string or vector (1 or n) */
137       XMLRPC_VALUE_TYPE type = XMLRPC_GetValueType(xParams);
138       if(type == xmlrpc_string) {
139          /* just one.  spit it out. */
140          describe_method(server, xMethodList, XMLRPC_GetValueString(xParams));
141          bAll = 0;
142       }
143       else if(type == xmlrpc_vector) {
144          /* multiple.  spit all out */
145          XMLRPC_VALUE xIter = XMLRPC_VectorRewind(xParams);
146          while(xIter) {
147             describe_method(server, xMethodList, XMLRPC_GetValueString(xIter));
148             xIter = XMLRPC_VectorNext(xParams);
149          }
150          bAll = 0;
151       }
152    }
153 
154    /* otherwise, default to sending all methods */
155    if(bAll) {
156       q_iter qi = Q_Iter_Head_F(&server->methodlist);
157       while( qi ) {
158          server_method* sm = Q_Iter_Get_F(qi);
159          if(sm) {
160             XMLRPC_AddValueToVector(xMethodList, sm->desc);
161          }
162          qi = Q_Iter_Next_F(qi);
163       }
164    }
165 
166    return xResponse;
167 }
168 
169 /* 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)170 static XMLRPC_VALUE xi_system_list_methods_cb(XMLRPC_SERVER server, XMLRPC_REQUEST input, void* userData) {
171    XMLRPC_VALUE xResponse = XMLRPC_CreateVector(NULL, xmlrpc_vector_array);
172 
173    q_iter qi = Q_Iter_Head_F(&server->methodlist);
174    while( qi ) {
175       server_method* sm = Q_Iter_Get_F(qi);
176       if(sm) {
177          XMLRPC_VectorAppendString(xResponse, 0, sm->name, 0);
178       }
179       qi = Q_Iter_Next_F(qi);
180    }
181    return xResponse;
182 }
183 
184 /* this complies with system.methodSignature as defined at
185  * http://xmlrpc.usefulinc.com/doc/sysmethodsig.html
186  */
xi_system_method_signature_cb(XMLRPC_SERVER server,XMLRPC_REQUEST input,void * userData)187 static XMLRPC_VALUE xi_system_method_signature_cb(XMLRPC_SERVER server, XMLRPC_REQUEST input, void* userData) {
188    const char* method = XMLRPC_GetValueString(XMLRPC_VectorRewind(XMLRPC_RequestGetData(input)));
189    XMLRPC_VALUE xResponse = NULL;
190 
191    /* lazy loading of introspection data */
192    check_docs_loaded(server, userData);
193 
194    if(method) {
195       server_method* sm = find_method(server, method);
196       if(sm && sm->desc) {
197          XMLRPC_VALUE xTypesArray = XMLRPC_CreateVector(NULL, xmlrpc_vector_array);
198          XMLRPC_VALUE xIter, xParams, xSig, xSigIter;
199          const char* type;
200 
201          /* array of possible signatures.  */
202          xResponse = XMLRPC_CreateVector(NULL, xmlrpc_vector_array);
203 
204          /* find first signature */
205          xSig = XMLRPC_VectorGetValueWithID(sm->desc, xi_token_signatures);
206          xSigIter = XMLRPC_VectorRewind( xSig );
207 
208          /* iterate through sigs */
209          while(xSigIter) {
210             /* first type is the return value */
211             type = XMLRPC_VectorGetStringWithID(XMLRPC_VectorRewind(
212                                                  XMLRPC_VectorGetValueWithID(xSigIter, xi_token_returns)),
213                                                 xi_token_type);
214             XMLRPC_AddValueToVector(xTypesArray,
215                                     XMLRPC_CreateValueString(NULL,
216                                                              type ? type : type_to_str(xmlrpc_none, 0),
217                                     0));
218 
219             /* the rest are parameters */
220             xParams = XMLRPC_VectorGetValueWithID(xSigIter, xi_token_params);
221             xIter = XMLRPC_VectorRewind(xParams);
222 
223             /* iter through params, adding to types array */
224             while(xIter) {
225                XMLRPC_AddValueToVector(xTypesArray,
226                                        XMLRPC_CreateValueString(NULL,
227                                                                 XMLRPC_VectorGetStringWithID(xIter, xi_token_type),
228                                                                 0));
229                xIter = XMLRPC_VectorNext(xParams);
230             }
231 
232             /* add types for this signature */
233             XMLRPC_AddValueToVector(xResponse, xTypesArray);
234 
235             xSigIter = XMLRPC_VectorNext( xSig );
236          }
237       }
238    }
239 
240    return xResponse;
241 }
242 
243 /* this complies with system.methodHelp as defined at
244  * http://xmlrpc.usefulinc.com/doc/sysmethhelp.html
245  */
xi_system_method_help_cb(XMLRPC_SERVER server,XMLRPC_REQUEST input,void * userData)246 static XMLRPC_VALUE xi_system_method_help_cb(XMLRPC_SERVER server, XMLRPC_REQUEST input, void* userData) {
247    const char* method = XMLRPC_GetValueString(XMLRPC_VectorRewind(XMLRPC_RequestGetData(input)));
248    XMLRPC_VALUE xResponse = NULL;
249 
250    /* lazy loading of introspection data */
251    check_docs_loaded(server, userData);
252 
253    if(method) {
254       server_method* sm = find_method(server, method);
255       if(sm && sm->desc) {
256          const char* help = XMLRPC_VectorGetStringWithID(sm->desc, xi_token_purpose);
257 
258          /* returns a documentation string, or empty string */
259          xResponse = XMLRPC_CreateValueString(NULL, help ? help : xi_token_empty, 0);
260       }
261    }
262 
263    return xResponse;
264 }
265 
266 /*-**************************************
267 * End Introspection Callbacks (methods) *
268 ****************************************/
269 
270 
271 /*-************************
272 * Introspection Utilities *
273 **************************/
274 
275 /* performs registration of introspection methods */
xi_register_system_methods(XMLRPC_SERVER server)276 void xi_register_system_methods(XMLRPC_SERVER server) {
277    XMLRPC_ServerRegisterMethod(server, xi_token_system_list_methods, xi_system_list_methods_cb);
278    XMLRPC_ServerRegisterMethod(server, xi_token_system_method_help, xi_system_method_help_cb);
279    XMLRPC_ServerRegisterMethod(server, xi_token_system_method_signature, xi_system_method_signature_cb);
280    XMLRPC_ServerRegisterMethod(server, xi_token_system_describe_methods, xi_system_describe_methods_cb);
281 }
282 
283 /* 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)284 static XMLRPC_VALUE describeValue_worker(const char* type, const char* id, const char* desc, int optional, const char* default_val, XMLRPC_VALUE sub_params) {
285    XMLRPC_VALUE xParam = NULL;
286    if(id || desc) {
287       xParam = XMLRPC_CreateVector(NULL, xmlrpc_vector_struct);
288       XMLRPC_VectorAppendString(xParam, xi_token_name, id, 0);
289       XMLRPC_VectorAppendString(xParam, xi_token_type, type, 0);
290       XMLRPC_VectorAppendString(xParam, xi_token_description, desc, 0);
291       if(optional != 2) {
292          XMLRPC_VectorAppendInt(xParam, xi_token_optional, optional);
293       }
294       if(optional == 1 && default_val) {
295          XMLRPC_VectorAppendString(xParam, xi_token_default, default_val, 0);
296       }
297       XMLRPC_AddValueToVector(xParam, sub_params);
298    }
299    return xParam;
300 }
301 
302 
303 /* convert an xml tree conforming to spec <url tbd> to  XMLRPC_VALUE
304  * suitable for use with XMLRPC_ServerAddIntrospectionData
305  */
xml_element_to_method_description(xml_element * el,XMLRPC_ERROR err)306 XMLRPC_VALUE xml_element_to_method_description(xml_element* el, XMLRPC_ERROR err) {
307    XMLRPC_VALUE xReturn = NULL;
308 
309    if(el->name) {
310       const char* name = NULL;
311       const char* type = NULL;
312       const char* basetype = NULL;
313       const char* desc = NULL;
314       const char* def = NULL;
315       int optional = 0;
316       xml_element_attr* attr_iter = Q_Head(&el->attrs);
317 
318       /* grab element attributes up front to save redundant while loops */
319       while(attr_iter) {
320          if(!strcmp(attr_iter->key, "name")) {
321             name = attr_iter->val;
322          }
323          else if(!strcmp(attr_iter->key, "type")) {
324             type = attr_iter->val;
325          }
326          else if(!strcmp(attr_iter->key, "basetype")) {
327             basetype = attr_iter->val;
328          }
329          else if(!strcmp(attr_iter->key, "desc")) {
330             desc = attr_iter->val;
331          }
332          else if(!strcmp(attr_iter->key, "optional")) {
333             if(attr_iter->val && !strcmp(attr_iter->val, "yes")) {
334                optional = 1;
335             }
336          }
337          else if(!strcmp(attr_iter->key, "default")) {
338             def = attr_iter->val;
339          }
340          attr_iter = Q_Next(&el->attrs);
341       }
342 
343       /* value and typeDescription behave about the same */
344       if(!strcmp(el->name, "value") || !strcmp(el->name, "typeDescription")) {
345          XMLRPC_VALUE xSubList = NULL;
346          const char* ptype = !strcmp(el->name, "value") ? type : basetype;
347          if(ptype) {
348             if(Q_Size(&el->children) &&
349                (!strcmp(ptype, "array") || !strcmp(ptype, "struct") || !strcmp(ptype, "mixed"))) {
350                xSubList = XMLRPC_CreateVector("member", xmlrpc_vector_array);
351 
352                if(xSubList) {
353                   xml_element* elem_iter = Q_Head(&el->children);
354                   while(elem_iter) {
355                      XMLRPC_AddValueToVector(xSubList,
356                                              xml_element_to_method_description(elem_iter, err));
357                      elem_iter = Q_Next(&el->children);
358                   }
359                }
360             }
361             xReturn = describeValue_worker(ptype, name, (desc ? desc : (xSubList ? NULL : el->text.str)), optional, def, xSubList);
362          }
363       }
364 
365       /* these three kids are about equivalent */
366       else if(!strcmp(el->name, "params") ||
367               !strcmp(el->name, "returns") ||
368               !strcmp(el->name, "signature")) {
369          if(Q_Size(&el->children)) {
370             xml_element* elem_iter = Q_Head(&el->children);
371             xReturn = XMLRPC_CreateVector(!strcmp(el->name, "signature") ? NULL : el->name, xmlrpc_vector_struct);
372 
373 
374             while(elem_iter) {
375                XMLRPC_AddValueToVector(xReturn,
376                                        xml_element_to_method_description(elem_iter, err));
377                elem_iter = Q_Next(&el->children);
378             }
379          }
380       }
381 
382 
383       else if(!strcmp(el->name, "methodDescription")) {
384          xml_element* elem_iter = Q_Head(&el->children);
385          xReturn = XMLRPC_CreateVector(NULL, xmlrpc_vector_struct);
386 
387          XMLRPC_VectorAppendString(xReturn, xi_token_name, name, 0);
388 
389          while(elem_iter) {
390             XMLRPC_AddValueToVector(xReturn,
391                                     xml_element_to_method_description(elem_iter, err));
392             elem_iter = Q_Next(&el->children);
393          }
394       }
395 
396       /* items are slightly special */
397       else if(!strcmp(el->name, "item")) {
398          xReturn = XMLRPC_CreateValueString(name, el->text.str, el->text.len);
399       }
400 
401       /* sure.  we'll let any ol element with children through */
402       else if(Q_Size(&el->children)) {
403          xml_element* elem_iter = Q_Head(&el->children);
404          xReturn = XMLRPC_CreateVector(el->name, xmlrpc_vector_mixed);
405 
406          while(elem_iter) {
407             XMLRPC_AddValueToVector(xReturn,
408                                     xml_element_to_method_description(elem_iter, err));
409             elem_iter = Q_Next(&el->children);
410          }
411       }
412 
413       /* or anything at all really, so long as its got some text.
414        * no reason being all snotty about a spec, right?
415        */
416       else if(el->name && el->text.len) {
417          xReturn = XMLRPC_CreateValueString(el->name, el->text.str, el->text.len);
418       }
419    }
420 
421    return xReturn;
422 }
423 
424 /*-****************************
425 * End Introspection Utilities *
426 ******************************/
427 
428 
429 
430 /*-******************
431 * Introspection API *
432 ********************/
433 
434 
435 /****f* VALUE/XMLRPC_IntrospectionCreateDescription
436  * NAME
437  *   XMLRPC_IntrospectionCreateDescription
438  * SYNOPSIS
439  *   XMLRPC_VALUE XMLRPC_IntrospectionCreateDescription(const char* xml, XMLRPC_ERROR err)
440  * FUNCTION
441  *   converts raw xml describing types and methods into an
442  *   XMLRPC_VALUE suitable for use with XMLRPC_ServerAddIntrospectionData()
443  * INPUTS
444  *   xml - xml data conforming to introspection spec at <url tbd>
445  *   err - optional pointer to error struct. filled in if error occurs and not NULL.
446  * RESULT
447  *   XMLRPC_VALUE - newly created value, or NULL if fatal error.
448  * BUGS
449  *   Currently does little or no validation of xml.
450  *   Only parse errors are currently reported in err, not structural errors.
451  * SEE ALSO
452  *   XMLRPC_ServerAddIntrospectionData ()
453  * SOURCE
454  */
XMLRPC_IntrospectionCreateDescription(const char * xml,XMLRPC_ERROR err)455 XMLRPC_VALUE XMLRPC_IntrospectionCreateDescription(const char* xml, XMLRPC_ERROR err) {
456    XMLRPC_VALUE xReturn = NULL;
457    xml_element* root = xml_elem_parse_buf(xml, 0, 0, err ? &err->xml_elem_error : NULL);
458 
459    if(root) {
460       xReturn = xml_element_to_method_description(root, err);
461 
462       xml_elem_free(root);
463    }
464 
465    return xReturn;
466 
467 }
468 /*******/
469 
470 
471 /****f* SERVER/XMLRPC_ServerAddIntrospectionData
472  * NAME
473  *   XMLRPC_ServerAddIntrospectionData
474  * SYNOPSIS
475  *   int XMLRPC_ServerAddIntrospectionData(XMLRPC_SERVER server, XMLRPC_VALUE desc)
476  * FUNCTION
477  *   updates server with additional introspection data
478  * INPUTS
479  *   server - target server
480  *   desc - introspection data, should be a struct generated by
481  *          XMLRPC_IntrospectionCreateDescription ()
482  * RESULT
483  *   int - 1 if success, else 0
484  * NOTES
485  *  - function will fail if neither typeList nor methodList key is present in struct.
486  *  - if method or type already exists, it will be replaced.
487  *  - desc is never freed by the server.  caller is responsible for cleanup.
488  * BUGS
489  *   - horribly slow lookups. prime candidate for hash improvements.
490  *   - uglier and more complex than I like to see for API functions.
491  * SEE ALSO
492  *   XMLRPC_ServerAddIntrospectionData ()
493  *   XMLRPC_ServerRegisterIntrospectionCallback ()
494  *   XMLRPC_CleanupValue ()
495  * SOURCE
496  */
XMLRPC_ServerAddIntrospectionData(XMLRPC_SERVER server,XMLRPC_VALUE desc)497 int XMLRPC_ServerAddIntrospectionData(XMLRPC_SERVER server, XMLRPC_VALUE desc) {
498    int bSuccess = 0;
499    if(server && desc) {
500       XMLRPC_VALUE xNewTypes = XMLRPC_VectorGetValueWithID(desc, "typeList");
501       XMLRPC_VALUE xNewMethods = XMLRPC_VectorGetValueWithID(desc, "methodList");
502       XMLRPC_VALUE xServerTypes = XMLRPC_VectorGetValueWithID(server->xIntrospection, "typeList");
503 
504       if(xNewMethods) {
505          XMLRPC_VALUE xMethod = XMLRPC_VectorRewind(xNewMethods);
506 
507          while(xMethod) {
508             const char* name = XMLRPC_VectorGetStringWithID(xMethod, xi_token_name);
509             server_method* sm = find_method(server, name);
510 
511             if(sm) {
512                if(sm->desc) {
513                   XMLRPC_CleanupValue(sm->desc);
514                }
515                sm->desc = XMLRPC_CopyValue(xMethod);
516                bSuccess = 1;
517             }
518 
519             xMethod = XMLRPC_VectorNext(xNewMethods);
520          }
521       }
522       if(xNewTypes) {
523          if(!xServerTypes) {
524             if(!server->xIntrospection) {
525                server->xIntrospection = XMLRPC_CreateVector(NULL, xmlrpc_vector_struct);
526             }
527 
528             XMLRPC_AddValueToVector(server->xIntrospection, xNewTypes);
529             bSuccess = 1;
530          }
531          else {
532             XMLRPC_VALUE xIter = XMLRPC_VectorRewind(xNewTypes);
533             while(xIter) {
534                /* get rid of old values */
535                XMLRPC_VALUE xPrev = find_named_value(xServerTypes, XMLRPC_VectorGetStringWithID(xIter, xi_token_name));
536                if(xPrev) {
537                   XMLRPC_VectorRemoveValue(xServerTypes, xPrev);
538                }
539                XMLRPC_AddValueToVector(xServerTypes, xIter);
540                bSuccess = 1;
541                xIter = XMLRPC_VectorNext(xNewTypes);
542             }
543          }
544       }
545    }
546    return bSuccess;
547 }
548 /*******/
549 
550 
551 /****f* SERVER/XMLRPC_ServerRegisterIntrospectionCallback
552  * NAME
553  *   XMLRPC_ServerRegisterIntrospectionCallback
554  * SYNOPSIS
555  *   int XMLRPC_ServerRegisterIntrospectionCallback(XMLRPC_SERVER server, XMLRPC_IntrospectionCallback cb)
556  * FUNCTION
557  *   registers a callback for lazy generation of introspection data
558  * INPUTS
559  *   server - target server
560  *   cb - callback that will generate introspection data
561  * RESULT
562  *   int - 1 if success, else 0
563  * NOTES
564  *   parsing xml and generating introspection data is fairly expensive, thus a
565  *   server may wish to wait until this data is actually requested before generating
566  *   it. Any number of callbacks may be registered at any time.  A given callback
567  *   will only ever be called once, the first time an introspection request is
568  *   processed after the time of callback registration.
569  * SEE ALSO
570  *   XMLRPC_ServerAddIntrospectionData ()
571  *   XMLRPC_IntrospectionCreateDescription ()
572  * SOURCE
573  */
XMLRPC_ServerRegisterIntrospectionCallback(XMLRPC_SERVER server,XMLRPC_IntrospectionCallback cb)574 int XMLRPC_ServerRegisterIntrospectionCallback(XMLRPC_SERVER server, XMLRPC_IntrospectionCallback cb) {
575    int bSuccess = 0;
576    if(server && cb) {
577 
578       doc_method* dm = ecalloc(1, sizeof(doc_method));
579 
580       if(dm) {
581          dm->method = cb;
582          dm->b_called = 0;
583 
584          if(Q_PushTail(&server->docslist, dm)) {
585             bSuccess = 1;
586          }
587          else {
588             my_free(dm);
589          }
590       }
591    }
592    return 0;
593 }
594 /*******/
595 
596 /*-**********************
597 * End Introspection API *
598 ************************/
599