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