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