soap(): use xmlNewChild() instead of xmlNewNode() and xmlAddChild()
[libisds.git] / src / soap.c
blob32c8efec5888c0b012918dce038223a2efda1df9
1 #define _XOPEN_SOURCE 500 /* strdup from string.h */
2 #include "isds_priv.h"
3 #include "soap.h"
4 #include "utils.h"
5 #include <stdlib.h>
6 #include <string.h>
8 /* Private structure for write_body() call back */
9 struct soap_body {
10 void *data;
11 size_t length;
15 /* CURL call back function called when chunk of HTTP reponse body is available.
16 * @buffer points to new data
17 * @size * @nmemb is length of the chunk in bytes. Zero means empty body.
18 * @userp is private structure.
19 * Must reuturn the length of the chunk, otherwise CURL will signal
20 * CURL_WRITE_ERROR. */
21 static size_t write_body(void *buffer, size_t size, size_t nmemb, void *userp) {
22 struct soap_body *body = (struct soap_body *) userp;
23 void *new_data;
25 /* FIXME: Check for (size * nmemb + body->lengt) !> SIZE_T_MAX.
26 * Precompute the product then. */
28 if (!body) return 0; /* This should never happen */
29 if (0 == (size * nmemb)) return 0; /* Empty body */
31 new_data = realloc(body->data, body->length + size * nmemb);
32 if (!new_data) return 0;
34 memcpy(new_data + body->length, buffer, size * nmemb);
36 body->data = new_data;
37 body->length += size * nmemb;
39 return (size * nmemb);
43 /* Do HTTP request.
44 * @context holds the base URL,
45 * @url is a (CGI) file of SOAP URL,
46 * @request is body for POST request
47 * @request_length is length of @request in bytes
48 * @reponse is automatically reallocated() buffer to fit HTTP response with
49 * @response_length (does not need to match allocatef memory exactly). You must
50 * free() the @response.
51 * @mime_type is automatically allocated MIME type send by server (*NULL if not
52 * sent). Set NULL if you don't care.
53 * @charset is charset of the body signaled by server. The same constrains
54 * like on @mime_type apply.
55 * In case of error, the response memory, MIME type, charset and lenght will be
56 * deallocated and zerod automatically. Thus be sure they are preallocated or
57 * they points to NULL.
58 * Side effect: message buffer */
59 static isds_error http(struct isds_ctx *context, const char *url,
60 const void *request, const size_t request_length,
61 void **response, size_t *response_length,
62 char **mime_type, char**charset) {
64 CURLcode curl_err;
65 isds_error err = IE_SUCCESS;
66 struct soap_body body;
67 char *content_type;
68 struct curl_slist *headers = NULL;
71 if (!context) return IE_INVALID_CONTEXT;
72 if (!url) return IE_INVAL;
73 if (request_length > 0 && !request) return IE_INVAL;
74 if (!response || !response_length) return IE_INVAL;
76 /* Set the body here to allow deallocataion in leave block */
77 body.data = *response;
78 body.length = 0;
80 /* Set Request-URI */
81 curl_err = curl_easy_setopt(context->curl, CURLOPT_URL, url);
82 if (!curl_err && context->username) {
83 curl_err = curl_easy_setopt(context->curl, CURLOPT_USERNAME,
84 context->username);
87 /* Set credentials */
88 if (!curl_err && context->password) {
89 curl_err = curl_easy_setopt(context->curl, CURLOPT_PASSWORD,
90 context->password);
92 if (!curl_err) {
93 curl_err = curl_easy_setopt(context->curl, CURLOPT_FAILONERROR, 1);
96 /* Set get-response function */
97 if (!curl_err) {
98 curl_err = curl_easy_setopt(context->curl, CURLOPT_WRITEFUNCTION,
99 write_body);
101 if (!curl_err) {
102 curl_err = curl_easy_setopt(context->curl, CURLOPT_WRITEDATA, &body);
105 /* Set MIME types and user agent identification */
106 if (!curl_err) {
107 headers = curl_slist_append(headers, "Accept: application/soap+xml");
108 if (!headers) {
109 err = IE_NOMEM;
110 goto leave;
112 headers = curl_slist_append(headers, "Content-Type: application/soap+xml");
113 if (!headers) {
114 err = IE_NOMEM;
115 goto leave;
117 curl_err = curl_easy_setopt(context->curl, CURLOPT_HTTPHEADER, headers);
119 if (!curl_err) {
120 /* TODO: Present library version, curl etc. in User-Agent */
121 curl_err = curl_easy_setopt(context->curl, CURLOPT_USERAGENT, "libisds");
124 /* Set POST request body */
125 if (!curl_err) {
126 curl_err = curl_easy_setopt(context->curl, CURLOPT_POST, 1);
128 if (!curl_err) {
129 curl_err = curl_easy_setopt(context->curl, CURLOPT_POSTFIELDS, request);
131 if (!curl_err) {
132 curl_err = curl_easy_setopt(context->curl, CURLOPT_POSTFIELDSIZE,
133 request_length);
136 /* Check for errors so far */
137 if (curl_err) {
138 isds_log_message(context, curl_easy_strerror(curl_err));
139 err = IE_NETWORK;
140 goto leave;
143 /* Do the request */
144 curl_err = curl_easy_perform(context->curl);
146 if (!curl_err)
147 curl_err = curl_easy_getinfo(context->curl, CURLINFO_CONTENT_TYPE,
148 &content_type);
150 if (curl_err) {
151 isds_log_message(context, url);
152 isds_append_message(context, _(": "));
153 isds_append_message(context, curl_easy_strerror(curl_err));
154 err = IE_NETWORK;
155 goto leave;
158 /* Extract MIME type and charset */
159 if (content_type) {
160 char *sep;
161 size_t offset;
163 sep = strchr(content_type, ';');
164 if (sep) offset = (size_t) (sep - content_type);
165 else offset = strlen(content_type);
167 if (mime_type) {
168 *mime_type = malloc(offset + 1);
169 if (!*mime_type) {
170 err = IE_NOMEM;
171 goto leave;
173 memcpy(*mime_type, content_type, offset);
174 (*mime_type)[offset] = '\0';
177 if (charset) {
178 if (!sep) {
179 *charset = NULL;
180 } else {
181 sep = strstr(sep, "charset=");
182 if (!sep) {
183 *charset = NULL;
184 } else {
185 *charset = strdup(sep + 8);
186 if (!*charset) {
187 err = IE_NOMEM;
188 goto leave;
195 leave:
196 free(headers);
198 if (err) {
199 free(body.data);
200 body.data = NULL;
201 body.length = 0;
203 if (mime_type) {
204 free(*mime_type);
205 *mime_type = NULL;
207 if (charset) {
208 free(*charset);
209 *charset = NULL;
212 curl_easy_cleanup(context->curl);
213 context->curl = NULL;
216 *response = body.data;
217 *response_length = body.length;
219 return err;
223 /* Do SOAP request.
224 * @context holds the base URL,
225 * @file is a (CGI) file of SOAP URL,
226 * @request is XML node set with SOAP request body.
227 * @file must be NULL, @request should be NULL rather than empty, if they should
228 * not be signaled in the SOAP request.
229 * @reponse is automatically allocated() node set with SOAP response body.
230 * You must xmlFreeNodeList() it. This is literal body, empty (NULL), one node
231 * or more nodes can be returned.
232 * In case of error the response will be deallocated automatically.
233 * Side effect: message buffer */
234 _hidden isds_error soap(struct isds_ctx *context, const char *file,
235 const xmlNodePtr request, xmlNodePtr *response) {
237 isds_error err = IE_SUCCESS;
238 char *url = NULL;
239 char *mime_type = NULL;
240 xmlBufferPtr http_request = NULL;
241 xmlSaveCtxtPtr save_ctx = NULL;
242 xmlDocPtr request_soap_doc = NULL;
243 xmlNodePtr request_soap_envelope = NULL, request_soap_body = NULL;
244 xmlNsPtr soap_ns = NULL;
245 void *http_response = NULL;
246 size_t response_length = 0;
247 xmlDocPtr response_soap_doc = NULL;
248 xmlXPathContextPtr xpath_ctx = NULL;
249 xmlXPathObjectPtr response_soap_headers = NULL, response_soap_body = NULL;
252 if (!context) return IE_INVALID_CONTEXT;
253 if (!response) return IE_INVAL;
255 xmlFreeNodeList(*response);
256 *response = NULL;
258 url = astrcat(context->url, file);
259 if (!url) return IE_NOMEM;
261 /* Build SOAP request envelope */
262 request_soap_doc = xmlNewDoc(BAD_CAST "1.0");
263 if (!request_soap_doc) {
264 isds_log_message(context, _("Could not build SOAP request document"));
265 err = IE_ERROR;
266 goto leave;
268 request_soap_envelope = xmlNewNode(NULL, BAD_CAST "Envelope");
269 if (!request_soap_envelope) {
270 isds_log_message(context, _("Could not build SOAP request envelope"));
271 err = IE_ERROR;
272 goto leave;
274 xmlDocSetRootElement(request_soap_doc, request_soap_envelope);
275 /* Only this way we get namespace definition as @xmlns:soap,
276 * otherwise we get namespace prefix without definition */
277 soap_ns = xmlNewNs(request_soap_envelope, BAD_CAST SOAP_NS, NULL);
278 if(!soap_ns) {
279 isds_log_message(context, _("Could not create SOAP name space"));
280 err = IE_ERROR;
281 goto leave;
283 xmlSetNs(request_soap_envelope, soap_ns);
284 request_soap_body = xmlNewChild(request_soap_envelope, NULL,
285 BAD_CAST "Body", NULL);
286 if (!request_soap_body) {
287 isds_log_message(context, _("Could not add Body to SOAP request envelope"));
288 err = IE_ERROR;
289 goto leave;
292 /* Append request XML node set to SOAP body if request is not empty */
293 /* XXX: Copy of request must be used, otherwise xmlFreeDoc(request_soap_doc)
294 * would destroy this outer structure. */
295 if (request) {
296 xmlNodePtr request_copy = xmlCopyNodeList(request);
297 if (!request_copy) {
298 isds_log_message(context,
299 _("Could not copy request content"));
300 err = IE_ERROR;
301 goto leave;
303 if (!xmlAddChildList(request_soap_body, request_copy)) {
304 xmlFreeNodeList(request_copy);
305 isds_log_message(context,
306 _("Could not add request content to SOAP request envelope"));
307 err = IE_ERROR;
308 goto leave;
313 /* Serialize the SOAP request into HTTP request body */
314 http_request = xmlBufferCreate();
315 if (!http_request) {
316 isds_log_message(context,
317 _("Could not create xmlBuffer for HTTP request body"));
318 err = IE_ERROR;
319 goto leave;
321 /* Last argument 1 means format the XML tree. This is pretty but it breaks
322 * digital signatures probably because ISDS abadoned XMLSec */
323 save_ctx = xmlSaveToBuffer(http_request, "UTF-8", 1);
324 if (!save_ctx) {
325 isds_log_message(context,
326 _("Could not create XML serializer"));
327 err = IE_ERROR;
328 goto leave;
330 /* XXX: According LibXML documentation, this function does not return
331 * meaningfull value yet */
332 xmlSaveDoc(save_ctx, request_soap_doc);
333 if (-1 == xmlSaveFlush(save_ctx)) {
334 isds_log_message(context,
335 _("Could not serialize SOAP request to HTTP request bddy"));
336 err = IE_ERROR;
337 goto leave;
341 err = http(context, url, http_request->content, http_request->use,
342 &http_response, &response_length,
343 &mime_type, NULL);
345 /* TODO: HTTP binding for SOAP prescribes non-200 HTTP return codes
346 * to be processes too. See SOAP Part 2, Table 16. */
348 if (err) {
349 goto leave;
352 /* Check for Content-Type: application/soap+xml */
353 if (mime_type && strcmp(mime_type, "application/soap+xml")
354 && strcmp(mime_type, "application/xml")
355 && strcmp(mime_type, "text/xml")) {
356 isds_log_message(context, url);
357 isds_append_message(context, _(": bad MIME type sent by server: "));
358 isds_append_message(context, mime_type);
359 err = IE_SOAP;
360 goto leave;
363 /* TODO: Convert returned body into XML default encoding */
365 /* Parse the HTTP body as XML */
366 response_soap_doc = xmlParseMemory(http_response, response_length);
367 if (!response_soap_doc) {
368 err = IE_XML;
369 goto leave;
372 xpath_ctx = xmlXPathNewContext(response_soap_doc);
373 if (!xpath_ctx) {
374 err = IE_ERROR;
375 goto leave;
378 if (register_namespaces(xpath_ctx)) {
379 err = IE_ERROR;
380 goto leave;
383 /* Check for SOAP requirements */
384 response_soap_headers = xmlXPathEvalExpression(
385 BAD_CAST "/soap:Envelope/soap:Header/"
386 "*[@soap:mustUnderstand/text() = true()]", xpath_ctx);
387 if (!response_soap_headers) {
388 err = IE_ERROR;
389 goto leave;
391 if (response_soap_headers->nodesetval) {
392 isds_log_message(context, "SOAP response requires unsupported feature");
393 /* TODO: log the headers
394 * xmlChar *fragment = NULL;
395 * fragment = xmlXPathCastNodeSetToSting(response_soap_headers->nodesetval);*/
396 err = IE_NOTSUP;
397 goto leave;
400 response_soap_body = xmlXPathEvalExpression(
401 BAD_CAST "/soap:Envelope/soap:Body", xpath_ctx);
402 if (!response_soap_body) {
403 err = IE_ERROR;
404 goto leave;
406 if (!(response_soap_body->nodesetval)) {
407 isds_log_message(context, "SOAP response does not contain Body element");
408 err = IE_SOAP;
409 goto leave;
411 if (response_soap_body->nodesetval->nodeNr > 1) {
412 isds_log_message(context, "SOAP body has more than Body element");
413 err = IE_SOAP;
414 goto leave;
418 /* Extract XML Tree with ISDS response from SOAP envelope and return it */
419 *response = xmlDocCopyNodeList(response_soap_doc,
420 response_soap_body->nodesetval->nodeTab[0]);
421 if (!*response) {
422 err = IE_NOMEM;
423 goto leave;
428 leave:
429 if (err) {
430 xmlFreeNodeList(*response);
431 *response = NULL;
434 xmlXPathFreeObject(response_soap_body);
435 xmlXPathFreeObject(response_soap_headers);
436 xmlXPathFreeContext(xpath_ctx);
437 xmlFreeDoc(response_soap_doc);
438 free(mime_type);
439 free(http_response);
440 xmlFreeDoc(request_soap_doc); /* recursive, frees request_body, soap_ns*/
441 xmlBufferFree(http_request);
442 xmlSaveClose(save_ctx);
443 free(url);
445 return err;
449 /* LibXML functions:
451 * void xmlInitParser(void)
452 * Initialization function for the XML parser. This is not reentrant. Call
453 * once before processing in case of use in multithreaded programs.
455 * int xmlInitParserCtxt(xmlParserCtxtPtr ctxt)
456 * Initialize a parser context
458 * xmlDocPtr xmlCtxtReadDoc(xmlParserCtxtPtr ctxt, const xmlChar * cur,
459 * const * char URL, const char * encoding, int options);
460 * Parse in-memory NULL-terminated document @cur.
462 * xmlDocPtr xmlParseMemory(const char * buffer, int size)
463 * Parse an XML in-memory block and build a tree.
465 * xmlParserCtxtPtr xmlCreateMemoryParserCtxt(const char * buffer, int
466 * size);
467 * Create a parser context for an XML in-memory document.
469 * xmlParserCtxtPtr xmlCreateDocParserCtxt(const xmlChar * cur)
470 * Creates a parser context for an XML in-memory document.
472 * xmlDocPtr xmlCtxtReadMemory(xmlParserCtxtPtr ctxt,
473 * const char * buffer, int size, const char * URL, const char * encoding,
474 * int options)
475 * Parse an XML in-memory document and build a tree. This reuses the existing
476 * @ctxt parser context.
478 * void xmlCleanupParser(void)
479 * Cleanup function for the XML library. It tries to reclaim all parsing
480 * related glob document related memory. Calling this function should not
481 * prevent reusing the libr finished using the library or XML document built
482 * with it.
484 * void xmlClearParserCtxt(xmlParserCtxtPtr ctxt)
485 * Clear (release owned resources) and reinitialize a parser context.
487 * void xmlCtxtReset(xmlParserCtxtPtr ctxt)
488 * Reset a parser context
490 * void xmlFreeParserCtxt(xmlParserCtxtPtr ctxt)
491 * Free all the memory used by a parser context. However the parsed document
492 * in ctxt->myDoc is not freed.
494 * void xmlFreeDoc(xmlDocPtr cur)
495 * Free up all the structures used by a document, tree included.