2 This file is part of libXMLRPC - a C library for xml-encoded function calls.
4 Author: Dan Libby (dan@libby.com)
8 /*-**********************************************************************
10 * - [SOAP-ENC:position] read sparse arrays (and write?) *
11 * - [SOAP-ENC:offset] read partially transmitted arrays (and write?) *
12 * - read "flattened" multi-dimensional arrays. (don't bother writing) *
15 * - does not read schema. thus only knows soap pre-defined types. *
16 * - references (probably) do not work. untested. *
17 * - does not expose SOAP-ENV:Header to application at all. *
18 * - does not use namespaces correctly, thus: *
19 * - namespaces are hard-coded in comparison tokens *
20 * - if a sender uses another namespace identifer, it will break *
21 ************************************************************************/
24 static const char rcsid[] = "#(@) $Id:";
27 #include "xmlrpc_win32.h"
31 #include "xml_to_soap.h"
34 /* list of tokens used in vocab */
35 #define TOKEN_ANY "xsd:ur-type"
36 #define TOKEN_ARRAY "SOAP-ENC:Array"
37 #define TOKEN_ARRAY_TYPE "SOAP-ENC:arrayType"
38 #define TOKEN_BASE64 "SOAP-ENC:base64"
39 #define TOKEN_BOOLEAN "xsd:boolean"
40 #define TOKEN_DATETIME "xsd:timeInstant"
41 #define TOKEN_DOUBLE "xsd:double"
42 #define TOKEN_FLOAT "xsd:float"
44 #define TOKEN_INT "xsd:int"
45 #define TOKEN_NULL "xsi:null"
46 #define TOKEN_STRING "xsd:string"
47 #define TOKEN_STRUCT "xsd:struct"
48 #define TOKEN_TYPE "xsi:type"
49 #define TOKEN_FAULT "SOAP-ENV:Fault"
50 #define TOKEN_MUSTUNDERSTAND "SOAP-ENV:mustUnderstand"
51 #define TOKEN_ACTOR "SOAP-ENV:actor"
52 #define TOKEN_ACTOR_NEXT "http://schemas.xmlsoap.org/soap/actor/next"
54 #define TOKEN_XMLRPC_FAULTCODE "faultCode"
55 #define TOKEN_XMLRPC_FAULTSTRING "faultString"
56 #define TOKEN_SOAP_FAULTCODE "faultcode"
57 #define TOKEN_SOAP_FAULTSTRING "faultstring"
58 #define TOKEN_SOAP_FAULTDETAILS "details"
59 #define TOKEN_SOAP_FAULTACTOR "actor"
62 /* determine if a string represents a soap type, as used in element names */
63 static inline int is_soap_type(const char* soap_type) {
64 return(strstr(soap_type, "SOAP-ENC:") || strstr(soap_type, "xsd:")) ? 1 : 0;
67 /* utility func to generate a new attribute. possibly should be in xml_element.c?? */
68 static xml_element_attr* new_attr(const char* key, const char* val) {
69 xml_element_attr* attr = malloc(sizeof(xml_element_attr));
71 attr->key = key ? strdup(key) : NULL;
72 attr->val = val ? strdup(val) : NULL;
84 /* parses soap arrayType attribute to generate an array_info structure.
85 * TODO: should deal with sparse, flattened, & multi-dimensional arrays
87 static struct array_info* parse_array_type_info(const char* array_type) {
88 struct array_info* ai = NULL;
90 ai = (struct array_info*)calloc(1, sizeof(struct array_info));
93 snprintf(buf, sizeof(buf), "%s", array_type);
98 strcpy(ai->kids_type, buf);
104 /* performs heuristics on an xmlrpc_vector_array to determine
105 * appropriate soap arrayType string.
107 static const char* get_array_soap_type(XMLRPC_VALUE node) {
108 XMLRPC_VALUE_TYPE_EASY type = xmlrpc_type_none;
110 XMLRPC_VALUE xIter = XMLRPC_VectorRewind(node);
112 const char* soapType = TOKEN_ANY;
114 type = XMLRPC_GetValueTypeEasy(xIter);
115 xIter = XMLRPC_VectorNext(node);
118 /* 50 seems like a decent # of loops. That will likely
119 * cover most cases. Any more and we start to sacrifice
122 if ( (XMLRPC_GetValueTypeEasy(xIter) != type) || loopCount >= 50) {
123 type = xmlrpc_type_none;
128 xIter = XMLRPC_VectorNext(node);
131 case xmlrpc_type_none:
132 soapType = TOKEN_ANY;
134 case xmlrpc_type_empty:
135 soapType = TOKEN_NULL;
137 case xmlrpc_type_int:
138 soapType = TOKEN_INT;
140 case xmlrpc_type_double:
141 soapType = TOKEN_DOUBLE;
143 case xmlrpc_type_boolean:
144 soapType = TOKEN_BOOLEAN;
146 case xmlrpc_type_string:
147 soapType = TOKEN_STRING;
149 case xmlrpc_type_base64:
150 soapType = TOKEN_BASE64;
152 case xmlrpc_type_datetime:
153 soapType = TOKEN_DATETIME;
155 case xmlrpc_type_struct:
156 soapType = TOKEN_STRUCT;
158 case xmlrpc_type_array:
159 soapType = TOKEN_ARRAY;
161 case xmlrpc_type_mixed:
162 soapType = TOKEN_STRUCT;
168 /* determines wether a node is a fault or not, and of which type:
170 * 1 = xmlrpc style fault
171 * 2 = soap style fault.
173 static inline int get_fault_type(XMLRPC_VALUE node) {
174 if (XMLRPC_VectorGetValueWithID(node, TOKEN_XMLRPC_FAULTCODE) &&
175 XMLRPC_VectorGetValueWithID(node, TOKEN_XMLRPC_FAULTSTRING)) {
178 else if (XMLRPC_VectorGetValueWithID(node, TOKEN_SOAP_FAULTCODE) &&
179 XMLRPC_VectorGetValueWithID(node, TOKEN_SOAP_FAULTSTRING)) {
185 /* input: an XMLRPC_VALUE representing a fault struct in xml-rpc style.
186 * output: an XMLRPC_VALUE representing a fault struct in soap style,
187 * with xmlrpc codes mapped to soap codes, and all other values preserved.
188 * note that the returned value is a completely new value, and must be freed.
189 * the input value is untouched.
191 static XMLRPC_VALUE gen_fault_xmlrpc(XMLRPC_VALUE node, xml_element* el_target) {
192 XMLRPC_VALUE xDup = XMLRPC_DupValueNew(node);
193 XMLRPC_VALUE xCode = XMLRPC_VectorGetValueWithID(xDup, TOKEN_XMLRPC_FAULTCODE);
194 XMLRPC_VALUE xStr = XMLRPC_VectorGetValueWithID(xDup, TOKEN_XMLRPC_FAULTSTRING);
196 XMLRPC_SetValueID(xCode, TOKEN_SOAP_FAULTCODE, 0);
197 XMLRPC_SetValueID(xStr, TOKEN_SOAP_FAULTSTRING, 0);
199 /* rough mapping of xmlrpc fault codes to soap codes */
200 switch (XMLRPC_GetValueInt(xCode)) {
201 case -32700: /* "parse error. not well formed", */
202 case -32701: /* "parse error. unsupported encoding" */
203 case -32702: /* "parse error. invalid character for encoding" */
204 case -32600: /* "server error. invalid xml-rpc. not conforming to spec." */
205 case -32601: /* "server error. requested method not found" */
206 case -32602: /* "server error. invalid method parameters" */
207 XMLRPC_SetValueString(xCode, "SOAP-ENV:Client", 0);
209 case -32603: /* "server error. internal xml-rpc error" */
210 case -32500: /* "application error" */
211 case -32400: /* "system error" */
212 case -32300: /* "transport error */
213 XMLRPC_SetValueString(xCode, "SOAP-ENV:Server", 0);
219 /* returns a new XMLRPC_VALUE representing a soap fault, comprised of a struct with four keys. */
220 static XMLRPC_VALUE gen_soap_fault(const char* fault_code, const char* fault_string,
221 const char* actor, const char* details) {
222 XMLRPC_VALUE xReturn = XMLRPC_CreateVector(TOKEN_FAULT, xmlrpc_vector_struct);
223 XMLRPC_AddValuesToVector(xReturn,
224 XMLRPC_CreateValueString(TOKEN_SOAP_FAULTCODE, fault_code, 0),
225 XMLRPC_CreateValueString(TOKEN_SOAP_FAULTSTRING, fault_string, 0),
226 XMLRPC_CreateValueString(TOKEN_SOAP_FAULTACTOR, actor, 0),
227 XMLRPC_CreateValueString(TOKEN_SOAP_FAULTDETAILS, details, 0),
232 /* translates xml soap dom to native data structures. recursive. */
233 XMLRPC_VALUE xml_element_to_SOAP_REQUEST_worker(XMLRPC_REQUEST request,
234 XMLRPC_VALUE xParent,
235 struct array_info* parent_array,
236 XMLRPC_VALUE xCurrent,
239 XMLRPC_REQUEST_TYPE rtype = xmlrpc_request_none;
241 /* no current element on first call */
243 xCurrent = XMLRPC_CreateValueEmpty();
246 /* increment recursion depth guage */
249 /* safety first. must have a valid element */
250 if (el && el->name) {
251 const char* id = NULL;
252 const char* type = NULL, *arrayType=NULL, *actor = NULL;
253 xml_element_attr* attr_iter = Q_Head(&el->attrs);
254 int b_must_understand = 0;
256 /* in soap, types may be specified in either element name -or- with xsi:type attribute. */
257 if (is_soap_type(el->name)) {
260 /* if our parent node, by definition a vector, is not an array, then
261 our element name must be our key identifier. */
262 else if (XMLRPC_GetVectorType(xParent) != xmlrpc_vector_array) {
264 if(!strcmp(id, "item")) {
268 /* iterate through element attributes, pick out useful stuff. */
271 if (!strcmp(attr_iter->key, TOKEN_TYPE)) {
272 type = attr_iter->val;
275 else if (!strcmp(attr_iter->key, TOKEN_ARRAY_TYPE)) {
276 arrayType = attr_iter->val;
278 /* must understand, sometimes present in headers. */
279 else if (!strcmp(attr_iter->key, TOKEN_MUSTUNDERSTAND)) {
280 b_must_understand = strchr(attr_iter->val, '1') ? 1 : 0;
282 /* actor, used in conjuction with must understand. */
283 else if (!strcmp(attr_iter->key, TOKEN_ACTOR)) {
284 actor = attr_iter->val;
286 attr_iter = Q_Next(&el->attrs);
289 /* check if caller says we must understand something in a header. */
290 if (b_must_understand) {
291 /* is must understand actually indended for us?
292 BUG: spec says we should also determine if actor is our URL, but
293 we do not have that information. */
294 if (!actor || !strcmp(actor, TOKEN_ACTOR_NEXT)) {
295 /* TODO: implement callbacks or other mechanism for applications
296 to "understand" these headers. For now, we just bail if we
297 get a mustUnderstand header intended for us. */
298 XMLRPC_RequestSetError(request,
299 gen_soap_fault("SOAP-ENV:MustUnderstand",
300 "SOAP Must Understand Error",
306 /* set id (key) if one was found. */
308 XMLRPC_SetValueID_Case(xCurrent, id, 0, xmlrpc_case_exact);
311 /* according to soap spec,
312 depth 1 = Envelope, 2 = Header, Body & Fault, 3 = methodcall or response. */
314 const char* methodname = el->name;
317 /* BUG: we determine request or response type using presence of "Response" in element name.
318 According to spec, this is only recommended, not required. Apparently, implementations
319 are supposed to know the type of action based on state, which strikes me as a bit lame.
320 Anyway, we don't have that state info, thus we use Response as a heuristic. */
323 strcasestr(el->name, "response") ? xmlrpc_request_response : xmlrpc_request_call;
325 strstr(el->name, "esponse") ? xmlrpc_request_response : xmlrpc_request_call;
327 XMLRPC_RequestSetRequestType(request, rtype);
329 /* Get methodname. strip xml namespace crap. */
330 p = strchr(el->name, ':');
334 if (rtype == xmlrpc_request_call) {
335 XMLRPC_RequestSetMethodName(request, methodname);
340 /* Next, we begin to convert actual values. if no children, then must be a scalar value. */
341 if (!Q_Size(&el->children)) {
342 if (!type && parent_array && parent_array->kids_type[0]) {
343 type = parent_array->kids_type;
345 if (!type || !strcmp(type, TOKEN_STRING)) {
346 XMLRPC_SetValueString(xCurrent, el->text.str, el->text.len);
348 else if (!strcmp(type, TOKEN_INT)) {
349 XMLRPC_SetValueInt(xCurrent, atoi(el->text.str));
351 else if (!strcmp(type, TOKEN_BOOLEAN)) {
352 XMLRPC_SetValueBoolean(xCurrent, atoi(el->text.str));
354 else if (!strcmp(type, TOKEN_DOUBLE) ||
355 !strcmp(type, TOKEN_FLOAT)) {
356 XMLRPC_SetValueDouble(xCurrent, atof(el->text.str));
358 else if (!strcmp(type, TOKEN_NULL)) {
359 /* already an empty val. do nothing. */
361 else if (!strcmp(type, TOKEN_DATETIME)) {
362 XMLRPC_SetValueDateTime_ISO8601(xCurrent, el->text.str);
364 else if (!strcmp(type, TOKEN_BASE64)) {
365 struct buffer_st buf;
366 base64_decode(&buf, el->text.str, el->text.len);
367 XMLRPC_SetValueBase64(xCurrent, buf.data, buf.offset);
371 /* Element has children, thus a vector, or "compound type" in soap-speak. */
373 struct array_info* ai = NULL;
374 xml_element* iter = (xml_element*)Q_Head(&el->children);
376 if (!type || !strcmp(type, TOKEN_STRUCT)) {
377 XMLRPC_SetIsVector(xCurrent, xmlrpc_vector_struct);
379 else if (!strcmp(type, TOKEN_ARRAY) || arrayType != NULL) {
380 /* determine magic associated with soap array type.
381 this is passed down as we recurse, so our children have access to the info. */
382 ai = parse_array_type_info(arrayType); // alloc'ed ai free'd below.
383 XMLRPC_SetIsVector(xCurrent, xmlrpc_vector_array);
386 /* mixed is probably closest thing we have to compound type. */
387 XMLRPC_SetIsVector(xCurrent, xmlrpc_vector_mixed);
389 /* Recurse, adding values as we go. Check for error during recursion
390 and if found, bail. this short-circuits us out of the recursion. */
391 while ( iter && !XMLRPC_RequestGetError(request) ) {
392 XMLRPC_VALUE xNext = NULL;
393 /* top level elements don't actually represent values, so we just pass the
394 current value along until we are deep enough. */
396 (rtype == xmlrpc_request_response && depth <= 3) ) {
397 xml_element_to_SOAP_REQUEST_worker(request, NULL, ai, xCurrent, iter, depth);
399 /* ready to do some actual de-serialization. create a new empty value and
400 pass that along to be init'd, then add it to our current vector. */
402 xNext = XMLRPC_CreateValueEmpty();
403 xml_element_to_SOAP_REQUEST_worker(request, xCurrent, ai, xNext, iter, depth);
404 XMLRPC_AddValueToVector(xCurrent, xNext);
406 iter = (xml_element*)Q_Next(&el->children);
417 /* Convert soap xml dom to XMLRPC_VALUE, sans request info. untested. */
418 XMLRPC_VALUE xml_element_to_SOAP_VALUE(xml_element* el)
420 return xml_element_to_SOAP_REQUEST_worker(NULL, NULL, NULL, NULL, el, 0);
423 /* Convert soap xml dom to XMLRPC_REQUEST */
424 XMLRPC_VALUE xml_element_to_SOAP_REQUEST(XMLRPC_REQUEST request, xml_element* el)
427 return XMLRPC_RequestSetData(request, xml_element_to_SOAP_REQUEST_worker(request, NULL, NULL, NULL, el, 0));
433 /* translates data structures to soap/xml. recursive */
434 xml_element* SOAP_to_xml_element_worker(XMLRPC_REQUEST request, XMLRPC_VALUE node) {
436 xml_element* elem_val = NULL;
438 int bFreeNode = 0; /* sometimes we may need to free 'node' variable */
440 XMLRPC_VALUE_TYPE_EASY type = XMLRPC_GetValueTypeEasy(node);
441 char* pName = NULL, *pAttrType = NULL;
443 /* create our return value element */
444 elem_val = xml_elem_new();
447 case xmlrpc_type_struct:
448 case xmlrpc_type_mixed:
449 case xmlrpc_type_array:
450 if (type == xmlrpc_type_array) {
451 /* array's are _very_ special in soap.
452 TODO: Should handle sparse/partial arrays here. */
454 /* determine soap array type. */
455 const char* type = get_array_soap_type(node);
456 xml_element_attr* attr_array_type = NULL;
458 /* specify array kids type and array size. */
459 snprintf(buf, sizeof(buf), "%s[%i]", type, XMLRPC_VectorSize(node));
460 attr_array_type = new_attr(TOKEN_ARRAY_TYPE, buf);
462 Q_PushTail(&elem_val->attrs, attr_array_type);
464 pAttrType = TOKEN_ARRAY;
466 /* check for fault, which is a rather special case.
467 (can't these people design anything consistent/simple/elegant?) */
468 else if (type == xmlrpc_type_struct) {
469 int fault_type = get_fault_type(node);
471 if (fault_type == 1) {
472 /* gen fault from xmlrpc style fault codes
473 notice that we get a new node, which must be freed herein. */
474 node = gen_fault_xmlrpc(node, elem_val);
482 /* recurse through sub-elements */
483 XMLRPC_VALUE xIter = XMLRPC_VectorRewind(node);
485 xml_element* next_el = SOAP_to_xml_element_worker(request, xIter);
487 Q_PushTail(&elem_val->children, next_el);
489 xIter = XMLRPC_VectorNext(node);
495 /* handle scalar types */
496 case xmlrpc_type_empty:
497 pAttrType = TOKEN_NULL;
499 case xmlrpc_type_string:
500 pAttrType = TOKEN_STRING;
501 simplestring_addn(&elem_val->text, XMLRPC_GetValueString(node), XMLRPC_GetValueStringLen(node));
503 case xmlrpc_type_int:
504 pAttrType = TOKEN_INT;
505 snprintf(buf, BUF_SIZE, "%i", XMLRPC_GetValueInt(node));
506 simplestring_add(&elem_val->text, buf);
508 case xmlrpc_type_boolean:
509 pAttrType = TOKEN_BOOLEAN;
510 snprintf(buf, BUF_SIZE, "%i", XMLRPC_GetValueBoolean(node));
511 simplestring_add(&elem_val->text, buf);
513 case xmlrpc_type_double:
514 pAttrType = TOKEN_DOUBLE;
515 snprintf(buf, BUF_SIZE, "%f", XMLRPC_GetValueDouble(node));
516 simplestring_add(&elem_val->text, buf);
518 case xmlrpc_type_datetime:
520 time_t tt = XMLRPC_GetValueDateTime(node);
521 struct tm *tm = localtime (&tt);
522 pAttrType = TOKEN_DATETIME;
523 if(strftime (buf, BUF_SIZE, "%Y-%m-%dT%H:%M:%SZ", tm)) {
524 simplestring_add(&elem_val->text, buf);
528 case xmlrpc_type_base64:
530 struct buffer_st buf;
531 pAttrType = TOKEN_BASE64;
532 base64_encode(&buf, XMLRPC_GetValueBase64(node), XMLRPC_GetValueStringLen(node));
533 simplestring_addn(&elem_val->text, buf.data, buf.offset );
542 /* determining element's name is a bit tricky, due to soap semantics. */
544 /* if the value's type is known... */
546 /* see if it has an id (key). If so, use that as name, and type as an attribute. */
547 pName = (char*)XMLRPC_GetValueID(node);
549 Q_PushTail(&elem_val->attrs, new_attr(TOKEN_TYPE, pAttrType));
552 /* otherwise, use the type as the name. */
557 /* if the value's type is not known... (a rare case?) */
559 /* see if it has an id (key). otherwise, default to generic "item" */
560 pName = (char*)XMLRPC_GetValueID(node);
566 elem_val->name = strdup(pName);
570 XMLRPC_CleanupValue(node);
576 /* convert XMLRPC_VALUE to soap xml dom. untested. */
577 xml_element* SOAP_VALUE_to_xml_element(XMLRPC_VALUE node) {
578 return SOAP_to_xml_element_worker(NULL, node);
581 /* convert XMLRPC_REQUEST to soap xml dom. */
582 xml_element* SOAP_REQUEST_to_xml_element(XMLRPC_REQUEST request) {
583 xml_element* root = xml_elem_new();
587 xml_element* body = xml_elem_new();
588 root->name = strdup("SOAP-ENV:Envelope");
590 /* silly namespace stuff */
591 Q_PushTail(&root->attrs, new_attr("xmlns:SOAP-ENV", "http://schemas.xmlsoap.org/soap/envelope/"));
592 Q_PushTail(&root->attrs, new_attr("xmlns:xsi", "http://www.w3.org/1999/XMLSchema-instance"));
593 Q_PushTail(&root->attrs, new_attr("xmlns:xsd", "http://www.w3.org/1999/XMLSchema"));
594 Q_PushTail(&root->attrs, new_attr("xmlns:SOAP-ENC", "http://schemas.xmlsoap.org/soap/encoding/"));
595 Q_PushTail(&root->attrs, new_attr("xmlns:si", "http://soapinterop.org/xsd"));
596 Q_PushTail(&root->attrs, new_attr("xmlns:ns6", "http://testuri.org"));
597 Q_PushTail(&root->attrs, new_attr("SOAP-ENV:encodingStyle", "http://schemas.xmlsoap.org/soap/encoding/"));
599 /* Q_PushHead(&root->attrs, new_attr("xmlns:ks", "http://kitchen.sink.org/soap/everything/under/sun"));
600 JUST KIDDING!! :-) ----> ------------------------------------------------- */
603 /* go ahead and serialize first... */
604 xml_element* el_serialized =
605 SOAP_to_xml_element_worker(request,
606 XMLRPC_RequestGetData(request));
608 /* check for fault, in which case, there is no intermediate element */
609 if (el_serialized && !strcmp(el_serialized->name, TOKEN_FAULT)) {
610 Q_PushTail(&body->children, el_serialized);
612 /* usual case: not a fault. Add Response element in between. */
614 xml_element* rpc = xml_elem_new();
617 const char* methodname = XMLRPC_RequestGetMethodName(request);
618 XMLRPC_REQUEST_TYPE rtype = XMLRPC_RequestGetRequestType(request);
620 /* if we are making a request, we want to use the methodname as is. */
621 if (rtype == xmlrpc_request_call) {
623 rpc->name = strdup(methodname);
626 /* if it's a response, we append "Response". Also, given xmlrpc-epi
627 API/architecture, it's likely that we don't have a methodname for
628 the response, so we have to check that. */
631 snprintf(buf, sizeof(buf), "%s%s",
632 methodname ? methodname : "",
635 rpc->name = strdup(buf);
638 /* add serialized data to method call/response.
639 add method call/response to body element */
642 if(Q_Size(&el_serialized->children) && rtype == xmlrpc_request_call) {
643 xml_element* iter = (xml_element*)Q_Head(&el_serialized->children);
645 Q_PushTail(&rpc->children, iter);
646 iter = (xml_element*)Q_Next(&el_serialized->children);
648 xml_elem_free_non_recurse(el_serialized);
651 Q_PushTail(&rpc->children, el_serialized);
655 Q_PushTail(&body->children, rpc);
659 TODO: fault here...? */
663 body->name = strdup("SOAP-ENV:Body");
664 Q_PushTail(&root->children, body);