test: http: respons writer skeleton
[libisds.git] / src / soap.c
blob3d759a25bdaf6010fa030e166142c03c3b239bef
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 /* Close connection to server and destroy CURL handle associated
16 * with @context */
17 _hidden isds_error _isds_close_connection(struct isds_ctx *context) {
18 if (!context) return IE_INVALID_CONTEXT;
20 if (context->curl) {
21 curl_easy_cleanup(context->curl);
22 context->curl = NULL;
23 isds_log(ILF_HTTP, ILL_DEBUG, _("Connection to server %s closed\n"),
24 context->url);
25 return IE_SUCCESS;
26 } else {
27 return IE_CONNECTION_CLOSED;
32 /* CURL call back function called when chunk of HTTP response body is available.
33 * @buffer points to new data
34 * @size * @nmemb is length of the chunk in bytes. Zero means empty body.
35 * @userp is private structure.
36 * Must return the length of the chunk, otherwise CURL will signal
37 * CURL_WRITE_ERROR. */
38 static size_t write_body(void *buffer, size_t size, size_t nmemb, void *userp) {
39 struct soap_body *body = (struct soap_body *) userp;
40 void *new_data;
42 /* FIXME: Check for (size * nmemb + body->lengt) !> SIZE_T_MAX.
43 * Precompute the product then. */
45 if (!body) return 0; /* This should never happen */
46 if (0 == (size * nmemb)) return 0; /* Empty body */
48 new_data = realloc(body->data, body->length + size * nmemb);
49 if (!new_data) return 0;
51 memcpy(new_data + body->length, buffer, size * nmemb);
53 body->data = new_data;
54 body->length += size * nmemb;
56 return (size * nmemb);
60 /* CURL progress callback proxy to rearrange arguments.
61 * @curl_data is session context */
62 static int progress_proxy(void *curl_data, double download_total,
63 double download_current, double upload_total, double upload_current) {
64 struct isds_ctx *context = (struct isds_ctx *) curl_data;
65 int abort = 0;
67 if (context && context->progress_callback) {
68 abort = context->progress_callback(
69 upload_total, upload_current,
70 download_total, download_current,
71 context->progress_callback_data);
72 if (abort) {
73 isds_log(ILF_HTTP, ILL_INFO,
74 _("Application aborted HTTP transfer"));
78 return abort;
82 /* CURL call back function called when curl has something to log.
83 * @curl is cURL context
84 * @type is cURL log facility
85 * @buffer points to log data, XXX: not zero-terminated
86 * @size is length of log data
87 * @userp is private structure.
88 * Must return 0. */
89 static int log_curl(CURL *curl, curl_infotype type, char *buffer, size_t size,
90 void *userp) {
91 if (!buffer || 0 == size) return 0;
92 if (type == CURLINFO_TEXT || type == CURLINFO_HEADER_IN ||
93 type == CURLINFO_HEADER_OUT)
94 isds_log(ILF_HTTP, ILL_DEBUG, "%*s", size, buffer);
95 return 0;
99 /* Do HTTP request.
100 * @context holds the base URL,
101 * @url is a (CGI) file of SOAP URL,
102 * @request is body for POST request
103 * @request_length is length of @request in bytes
104 * @reponse is automatically reallocated() buffer to fit HTTP response with
105 * @response_length (does not need to match allocated memory exactly). You must
106 * free() the @response.
107 * @mime_type is automatically allocated MIME type send by server (*NULL if not
108 * sent). Set NULL if you don't care.
109 * @charset is charset of the body signaled by server. The same constrains
110 * like on @mime_type apply.
111 * @http_code is final HTTP code returned by server. This can be 200, 401, 500
112 * or any other one. Pass NULL if you don't interest.
113 * In case of error, the response memory, MIME type, charset and length will be
114 * deallocated and zeroed automatically. Thus be sure they are preallocated or
115 * they points to NULL.
116 * Be ware that successful return value does not mean the HTTP request has
117 * been accepted by the server. You must consult @http_code. OTOH, failure
118 * return value means the request could not been sent (e.g. SSL error).
119 * Side effect: message buffer */
120 static isds_error http(struct isds_ctx *context, const char *url,
121 const void *request, const size_t request_length,
122 void **response, size_t *response_length,
123 char **mime_type, char **charset, long *http_code) {
125 CURLcode curl_err;
126 isds_error err = IE_SUCCESS;
127 struct soap_body body;
128 char *content_type;
129 struct curl_slist *headers = NULL;
132 if (!context) return IE_INVALID_CONTEXT;
133 if (!url) return IE_INVAL;
134 if (request_length > 0 && !request) return IE_INVAL;
135 if (!response || !response_length) return IE_INVAL;
137 /* Set the body here to allow deallocation in leave block */
138 body.data = *response;
139 body.length = 0;
141 /* Set Request-URI */
142 curl_err = curl_easy_setopt(context->curl, CURLOPT_URL, url);
144 /* Set TLS options */
145 if (!curl_err && context->tls_verify_server) {
146 if (!*context->tls_verify_server)
147 isds_log(ILF_SEC, ILL_WARNING,
148 _("Disabling server identity verification. "
149 "That was your decision.\n"));
150 curl_err = curl_easy_setopt(context->curl, CURLOPT_SSL_VERIFYPEER,
151 (*context->tls_verify_server)? 1L : 0L);
152 if (!curl_err) {
153 curl_err = curl_easy_setopt(context->curl, CURLOPT_SSL_VERIFYHOST,
154 (*context->tls_verify_server)? 2L : 0L);
157 if (!curl_err && context->tls_ca_file) {
158 isds_log(ILF_SEC, ILL_INFO,
159 _("CA certificates will be searched in `%s' file since now\n"),
160 context->tls_ca_file);
161 curl_err = curl_easy_setopt(context->curl, CURLOPT_CAINFO,
162 context->tls_ca_file);
164 if (!curl_err && context->tls_ca_dir) {
165 isds_log(ILF_SEC, ILL_INFO,
166 _("CA certificates will be searched in `%s' directory "
167 "since now\n"), context->tls_ca_dir);
168 curl_err = curl_easy_setopt(context->curl, CURLOPT_CAPATH,
169 context->tls_ca_dir);
171 if (!curl_err && context->tls_crl_file) {
172 #if HAVE_DECL_CURLOPT_CRLFILE /* Since curl-7.19.0 */
173 isds_log(ILF_SEC, ILL_INFO,
174 _("CRLs will be searched in `%s' file since now\n"),
175 context->tls_crl_file);
176 curl_err = curl_easy_setopt(context->curl, CURLOPT_CRLFILE,
177 context->tls_crl_file);
178 #else
179 isds_log(ILF_SEC, ILL_WARNING,
180 _("Your curl library cannot pass certificate revocation "
181 "list to cryptographic library.\n"
182 "Make sure cryptographic library default setting "
183 "delivers proper CRLs,\n"
184 "or upgrade curl.\n"));
185 #endif /* not HAVE_DECL_CURLOPT_CRLFILE */
189 /* Set credentials */
190 #if HAVE_DECL_CURLOPT_USERNAME /* Since curl-7.19.1 */
191 if (!curl_err && context->username) {
192 curl_err = curl_easy_setopt(context->curl, CURLOPT_USERNAME,
193 context->username);
195 if (!curl_err && context->password) {
196 curl_err = curl_easy_setopt(context->curl, CURLOPT_PASSWORD,
197 context->password);
199 #else
200 if (!curl_err && (context->username || context->password)) {
201 char *userpwd =
202 _isds_astrcat3(context->username, ":", context->password);
203 if (!userpwd) {
204 isds_log_message(context, _("Could not pass credentials to CURL"));
205 err = IE_NOMEM;
206 goto leave;
208 curl_err = curl_easy_setopt(context->curl, CURLOPT_USERPWD, userpwd);
209 free(userpwd);
211 #endif /* not HAVE_DECL_CURLOPT_USERNAME */
213 /* Set PKI credentials */
214 if (!curl_err && (context->pki_credentials)) {
215 if (context->pki_credentials->engine) {
216 /* Select SSL engine */
217 isds_log(ILF_SEC, ILL_INFO,
218 _("Cryptographic engine `%s' will be used for "
219 "key or certificate\n"),
220 context->pki_credentials->engine);
221 curl_err = curl_easy_setopt(context->curl, CURLOPT_SSLENGINE,
222 context->pki_credentials->engine);
225 if (!curl_err) {
226 /* Select certificate format */
227 #if HAVE_DECL_CURLOPT_SSLCERTTYPE /* since curl-7.9.3 */
228 if (context->pki_credentials->certificate_format ==
229 PKI_FORMAT_ENG) {
230 /* XXX: It's valid to have certificate in engine without name.
231 * Engines can select certificate according private key and
232 * vice versa. */
233 if (context->pki_credentials->certificate)
234 isds_log(ILF_SEC, ILL_INFO, _("Client `%s' certificate "
235 "will be read from `%s' engine\n"),
236 context->pki_credentials->certificate,
237 context->pki_credentials->engine);
238 else
239 isds_log(ILF_SEC, ILL_INFO, _("Client certificate "
240 "will be read from `%s' engine\n"),
241 context->pki_credentials->engine);
242 curl_err = curl_easy_setopt(context->curl, CURLOPT_SSLCERTTYPE,
243 "ENG");
244 } else if (context->pki_credentials->certificate) {
245 isds_log(ILF_SEC, ILL_INFO, _("Client %s certificate "
246 "will be read from `%s' file\n"),
247 (context->pki_credentials->certificate_format ==
248 PKI_FORMAT_DER) ? _("DER") : _("PEM"),
249 context->pki_credentials->certificate);
250 curl_err = curl_easy_setopt(context->curl, CURLOPT_SSLCERTTYPE,
251 (context->pki_credentials->certificate_format ==
252 PKI_FORMAT_DER) ? "DER" : "PEM");
254 #else
255 if ((context->pki_credentials->certificate_format ==
256 PKI_FORMAT_ENG ||
257 context->pki_credentials->certificate))
258 isds_log(ILF_SEC, ILL_WARNING,
259 _("Your curl library cannot distinguish certificate "
260 "formats. Make sure your cryptographic library\n"
261 "understands your certificate file by default, "
262 "or upgrade curl.\n"));
263 #endif /* not HAVE_DECL_CURLOPT_SSLCERTTYPE */
266 if (!curl_err && context->pki_credentials->certificate) {
267 /* Select certificate */
268 if (!curl_err)
269 curl_err = curl_easy_setopt(context->curl, CURLOPT_SSLCERT,
270 context->pki_credentials->certificate);
273 if (!curl_err) {
274 /* Select key format */
275 if (context->pki_credentials->key_format == PKI_FORMAT_ENG) {
276 if (context->pki_credentials->key)
277 isds_log(ILF_SEC, ILL_INFO, _("Client private key `%s' "
278 "from `%s' engine will be used\n"),
279 context->pki_credentials->key,
280 context->pki_credentials->engine);
281 else
282 isds_log(ILF_SEC, ILL_INFO, _("Client private key "
283 "from `%s' engine will be used\n"),
284 context->pki_credentials->engine);
285 curl_err = curl_easy_setopt(context->curl, CURLOPT_SSLKEYTYPE,
286 "ENG");
287 } else if (context->pki_credentials->key) {
288 isds_log(ILF_SEC, ILL_INFO, _("Client %s private key will be "
289 "read from `%s' file\n"),
290 (context->pki_credentials->key_format ==
291 PKI_FORMAT_DER) ? _("DER") : _("PEM"),
292 context->pki_credentials->key);
293 curl_err = curl_easy_setopt(context->curl, CURLOPT_SSLKEYTYPE,
294 (context->pki_credentials->key_format ==
295 PKI_FORMAT_DER) ? "DER" : "PEM");
298 if (!curl_err)
299 /* Select key */
300 curl_err = curl_easy_setopt(context->curl, CURLOPT_SSLKEY,
301 context->pki_credentials->key);
303 if (!curl_err) {
304 /* Pass key pass-phrase */
305 #if HAVE_DECL_CURLOPT_KEYPASSWD /* since curl-7.16.5 */
306 curl_err = curl_easy_setopt(context->curl,
307 CURLOPT_KEYPASSWD,
308 context->pki_credentials->passphrase);
309 #elif HAVE_DECL_CURLOPT_SSLKEYPASSWD /* up to curl-7.16.4 */
310 curl_err = curl_easy_setopt(context->curl,
311 CURLOPT_SSLKEYPASSWD,
312 context->pki_credentials->passphrase);
313 #else /* up to curl-7.9.2 */
314 curl_err = curl_easy_setopt(context->curl,
315 CURLOPT_SSLCERTPASSWD,
316 context->pki_credentials->passphrase);
317 #endif
322 /* Set timeout */
323 if (!curl_err) {
324 curl_err = curl_easy_setopt(context->curl, CURLOPT_NOSIGNAL, 1);
326 if (!curl_err && context->timeout) {
327 #if HAVE_DECL_CURLOPT_TIMEOUT_MS /* Since curl-7.16.2 */
328 curl_err = curl_easy_setopt(context->curl, CURLOPT_TIMEOUT_MS,
329 context->timeout);
330 #else
331 curl_err = curl_easy_setopt(context->curl, CURLOPT_TIMEOUT,
332 context->timeout / 1000);
333 #endif /* not HAVE_DECL_CURLOPT_TIMEOUT_MS */
336 /* Register callback */
337 if (context->progress_callback) {
338 if (!curl_err) {
339 curl_err = curl_easy_setopt(context->curl, CURLOPT_NOPROGRESS, 0);
341 if (!curl_err) {
342 curl_err = curl_easy_setopt(context->curl,
343 CURLOPT_PROGRESSFUNCTION, progress_proxy);
345 if (!curl_err) {
346 curl_err = curl_easy_setopt(context->curl, CURLOPT_PROGRESSDATA,
347 context);
351 /* Set other CURL features */
352 if (!curl_err) {
353 curl_err = curl_easy_setopt(context->curl, CURLOPT_FAILONERROR, 0);
356 /* Set get-response function */
357 if (!curl_err) {
358 curl_err = curl_easy_setopt(context->curl, CURLOPT_WRITEFUNCTION,
359 write_body);
361 if (!curl_err) {
362 curl_err = curl_easy_setopt(context->curl, CURLOPT_WRITEDATA, &body);
365 /* Set MIME types and headers requires by SOAP 1.1.
366 * SOAP 1.1 requires text/xml, SOAP 1.2 requires application/soap+xml */
367 if (!curl_err) {
368 headers = curl_slist_append(headers,
369 "Accept: application/soap+xml,application/xml,text/xml");
370 if (!headers) {
371 err = IE_NOMEM;
372 goto leave;
374 headers = curl_slist_append(headers, "Content-Type: text/xml");
375 if (!headers) {
376 err = IE_NOMEM;
377 goto leave;
379 headers = curl_slist_append(headers, "SOAPAction: ");
380 if (!headers) {
381 err = IE_NOMEM;
382 goto leave;
384 curl_err = curl_easy_setopt(context->curl, CURLOPT_HTTPHEADER, headers);
386 if (!curl_err) {
387 /* Set user agent identification */
388 curl_err = curl_easy_setopt(context->curl, CURLOPT_USERAGENT,
389 "libisds/" PACKAGE_VERSION);
392 /* Set POST request body */
393 if (!curl_err) {
394 curl_err = curl_easy_setopt(context->curl, CURLOPT_POST, 1);
396 if (!curl_err) {
397 curl_err = curl_easy_setopt(context->curl, CURLOPT_POSTFIELDS, request);
399 if (!curl_err) {
400 curl_err = curl_easy_setopt(context->curl, CURLOPT_POSTFIELDSIZE,
401 request_length);
404 /* Check for errors so far */
405 if (curl_err) {
406 isds_log_message(context, curl_easy_strerror(curl_err));
407 err = IE_NETWORK;
408 goto leave;
411 isds_log(ILF_HTTP, ILL_DEBUG, _("Sending POST request to %s\n"), url);
412 isds_log(ILF_HTTP, ILL_DEBUG,
413 _("POST body length: %zu, content follows:\n"), request_length);
414 isds_log(ILF_HTTP, ILL_DEBUG, "%.*s\n", request_length, request);
415 isds_log(ILF_HTTP, ILL_DEBUG, _("End of POST body\n"));
416 if ((log_facilities & ILF_HTTP) && (log_level >= ILL_DEBUG) ) {
417 curl_easy_setopt(context->curl, CURLOPT_VERBOSE, 1);
418 curl_easy_setopt(context->curl, CURLOPT_DEBUGFUNCTION, log_curl);
419 } else {
420 curl_easy_setopt(context->curl, CURLOPT_VERBOSE, 0);
421 curl_easy_setopt(context->curl, CURLOPT_DEBUGFUNCTION, NULL);
425 /* Do the request */
426 curl_err = curl_easy_perform(context->curl);
428 if (!curl_err)
429 curl_err = curl_easy_getinfo(context->curl, CURLINFO_CONTENT_TYPE,
430 &content_type);
432 if (curl_err) {
433 /* TODO: CURL is not internationalized yet. Collect CURL messages for
434 * I18N. */
435 isds_printf_message(context,
436 _("%s: %s"), url, _(curl_easy_strerror(curl_err)));
437 if (curl_err == CURLE_ABORTED_BY_CALLBACK)
438 err = IE_ABORTED;
439 else
440 err = IE_NETWORK;
441 goto leave;
444 isds_log(ILF_HTTP, ILL_DEBUG, _("Final response to %s received\n"), url);
445 isds_log(ILF_HTTP, ILL_DEBUG,
446 _("Response body length: %zu, content follows:\n"),
447 body.length);
448 isds_log(ILF_HTTP, ILL_DEBUG, "%.*s\n", body.length, body.data);
449 isds_log(ILF_HTTP, ILL_DEBUG, _("End of response body\n"));
452 /* Extract MIME type and charset */
453 if (content_type) {
454 char *sep;
455 size_t offset;
457 sep = strchr(content_type, ';');
458 if (sep) offset = (size_t) (sep - content_type);
459 else offset = strlen(content_type);
461 if (mime_type) {
462 *mime_type = malloc(offset + 1);
463 if (!*mime_type) {
464 err = IE_NOMEM;
465 goto leave;
467 memcpy(*mime_type, content_type, offset);
468 (*mime_type)[offset] = '\0';
471 if (charset) {
472 if (!sep) {
473 *charset = NULL;
474 } else {
475 sep = strstr(sep, "charset=");
476 if (!sep) {
477 *charset = NULL;
478 } else {
479 *charset = strdup(sep + 8);
480 if (!*charset) {
481 err = IE_NOMEM;
482 goto leave;
489 /* Get HTTP response code */
490 if (http_code) {
491 curl_err = curl_easy_getinfo(context->curl,
492 CURLINFO_RESPONSE_CODE, http_code);
493 if (curl_err) {
494 err = IE_ERROR;
495 goto leave;
499 leave:
500 curl_slist_free_all(headers);
502 if (err) {
503 free(body.data);
504 body.data = NULL;
505 body.length = 0;
507 if (mime_type) {
508 free(*mime_type);
509 *mime_type = NULL;
511 if (charset) {
512 free(*charset);
513 *charset = NULL;
516 if (err != IE_ABORTED) _isds_close_connection(context);
519 *response = body.data;
520 *response_length = body.length;
522 return err;
526 /* Do SOAP request.
527 * @context holds the base URL,
528 * @file is a (CGI) file of SOAP URL,
529 * @request is XML node set with SOAP request body.
530 * @file must be NULL, @request should be NULL rather than empty, if they should
531 * not be signaled in the SOAP request.
532 * @reponse is automatically allocated() node set with SOAP response body.
533 * You must xmlFreeNodeList() it. This is literal body, empty (NULL), one node
534 * or more nodes can be returned.
535 * @raw_response is automatically allocated bit stream with response body. Use
536 * NULL if you don't care
537 * @raw_response_length is size of @raw_response in bytes
538 * In case of error the response will be deallocated automatically.
539 * Side effect: message buffer */
540 _hidden isds_error _isds_soap(struct isds_ctx *context, const char *file,
541 const xmlNodePtr request, xmlNodePtr *response,
542 void **raw_response, size_t *raw_response_length) {
544 isds_error err = IE_SUCCESS;
545 char *url = NULL;
546 char *mime_type = NULL;
547 long http_code = 0;
548 xmlBufferPtr http_request = NULL;
549 xmlSaveCtxtPtr save_ctx = NULL;
550 xmlDocPtr request_soap_doc = NULL;
551 xmlNodePtr request_soap_envelope = NULL, request_soap_body = NULL;
552 xmlNsPtr soap_ns = NULL;
553 void *http_response = NULL;
554 size_t response_length = 0;
555 xmlDocPtr response_soap_doc = NULL;
556 xmlNodePtr response_root = NULL;
557 xmlXPathContextPtr xpath_ctx = NULL;
558 xmlXPathObjectPtr response_soap_headers = NULL, response_soap_body = NULL,
559 response_soap_fault = NULL;
562 if (!context) return IE_INVALID_CONTEXT;
563 if (!response) return IE_INVAL;
564 if (!raw_response_length && raw_response) return IE_INVAL;
566 xmlFreeNodeList(*response);
567 *response = NULL;
568 if (raw_response) *raw_response = NULL;
570 url = _isds_astrcat(context->url, file);
571 if (!url) return IE_NOMEM;
573 /* Build SOAP request envelope */
574 request_soap_doc = xmlNewDoc(BAD_CAST "1.0");
575 if (!request_soap_doc) {
576 isds_log_message(context, _("Could not build SOAP request document"));
577 err = IE_ERROR;
578 goto leave;
580 request_soap_envelope = xmlNewNode(NULL, BAD_CAST "Envelope");
581 if (!request_soap_envelope) {
582 isds_log_message(context, _("Could not build SOAP request envelope"));
583 err = IE_ERROR;
584 goto leave;
586 xmlDocSetRootElement(request_soap_doc, request_soap_envelope);
587 /* Only this way we get namespace definition as @xmlns:soap,
588 * otherwise we get namespace prefix without definition */
589 soap_ns = xmlNewNs(request_soap_envelope, BAD_CAST SOAP_NS, NULL);
590 if(!soap_ns) {
591 isds_log_message(context, _("Could not create SOAP name space"));
592 err = IE_ERROR;
593 goto leave;
595 xmlSetNs(request_soap_envelope, soap_ns);
596 request_soap_body = xmlNewChild(request_soap_envelope, NULL,
597 BAD_CAST "Body", NULL);
598 if (!request_soap_body) {
599 isds_log_message(context,
600 _("Could not add Body to SOAP request envelope"));
601 err = IE_ERROR;
602 goto leave;
605 /* Append request XML node set to SOAP body if request is not empty */
606 /* XXX: Copy of request must be used, otherwise xmlFreeDoc(request_soap_doc)
607 * would destroy this outer structure. */
608 if (request) {
609 xmlNodePtr request_copy = xmlCopyNodeList(request);
610 if (!request_copy) {
611 isds_log_message(context,
612 _("Could not copy request content"));
613 err = IE_ERROR;
614 goto leave;
616 if (!xmlAddChildList(request_soap_body, request_copy)) {
617 xmlFreeNodeList(request_copy);
618 isds_log_message(context,
619 _("Could not add request content to SOAP "
620 "request envelope"));
621 err = IE_ERROR;
622 goto leave;
627 /* Serialize the SOAP request into HTTP request body */
628 http_request = xmlBufferCreate();
629 if (!http_request) {
630 isds_log_message(context,
631 _("Could not create xmlBuffer for HTTP request body"));
632 err = IE_ERROR;
633 goto leave;
635 /* Last argument 1 means format the XML tree. This is pretty but it breaks
636 * XML document transport as it adds text nodes (indentiation) between
637 * elements. */
638 save_ctx = xmlSaveToBuffer(http_request, "UTF-8", 0);
639 if (!save_ctx) {
640 isds_log_message(context,
641 _("Could not create XML serializer"));
642 err = IE_ERROR;
643 goto leave;
645 /* XXX: According LibXML documentation, this function does not return
646 * meaningful value yet */
647 xmlSaveDoc(save_ctx, request_soap_doc);
648 if (-1 == xmlSaveFlush(save_ctx)) {
649 isds_log_message(context,
650 _("Could not serialize SOAP request to HTTP request body"));
651 err = IE_ERROR;
652 goto leave;
655 isds_log(ILF_SOAP, ILL_DEBUG,
656 _("SOAP request to sent to %s:\n%.*s\nEnd of SOAP request\n"),
657 url, http_request->use, http_request->content);
659 err = http(context, url, http_request->content, http_request->use,
660 &http_response, &response_length,
661 &mime_type, NULL, &http_code);
663 /* TODO: HTTP binding for SOAP prescribes non-200 HTTP return codes
664 * to be processes too. */
666 if (err) {
667 goto leave;
670 /* Check for HTTP return code */
671 isds_log(ILF_SOAP, ILL_DEBUG, _("Server returned %ld HTTP code\n"),
672 http_code);
673 switch (http_code) {
674 /* XXX: We must see which code is used for not permitted ISDS
675 * operation like downloading message without proper user
676 * permissions. In that cat we should keep connection opened. */
677 case 302:
678 err = IE_HTTP;
679 isds_printf_message(context,
680 _("Code 302: Server redirects on <%s>. "
681 "Redirection is forbidden in stateless mode."), url);
682 goto leave;
683 break;
684 case 401:
685 err = IE_NOT_LOGGED_IN;
686 isds_log_message(context, _("Authentication failed"));
687 goto leave;
688 break;
689 case 404:
690 err = IE_HTTP;
691 isds_printf_message(context,
692 _("Code 404: Document (%s) not found on server"), url);
693 goto leave;
694 break;
695 /* 500 should return standard SOAP message */
698 /* Check for Content-Type: text/xml.
699 * Do it after HTTP code check because 401 Unauthorized returns HTML web
700 * page for browsers. */
701 if (mime_type && strcmp(mime_type, "text/xml")
702 && strcmp(mime_type, "application/soap+xml")
703 && strcmp(mime_type, "application/xml")) {
704 char *mime_type_locale = _isds_utf82locale(mime_type);
705 isds_printf_message(context,
706 _("%s: bad MIME type sent by server: %s"), url,
707 mime_type_locale);
708 free(mime_type_locale);
709 err = IE_SOAP;
710 goto leave;
713 /* TODO: Convert returned body into XML default encoding */
715 /* Parse the HTTP body as XML */
716 response_soap_doc = xmlParseMemory(http_response, response_length);
717 if (!response_soap_doc) {
718 err = IE_XML;
719 goto leave;
722 xpath_ctx = xmlXPathNewContext(response_soap_doc);
723 if (!xpath_ctx) {
724 err = IE_ERROR;
725 goto leave;
728 if (_isds_register_namespaces(xpath_ctx, MESSAGE_NS_UNSIGNED)) {
729 err = IE_ERROR;
730 goto leave;
733 isds_log(ILF_SOAP, ILL_DEBUG,
734 _("SOAP response received:\n%.*s\nEnd of SOAP response\n"),
735 response_length, http_response);
738 /* Check for SOAP version */
739 response_root = xmlDocGetRootElement(response_soap_doc);
740 if (!response_root) {
741 isds_log_message(context, "SOAP response has no root element");
742 err = IE_SOAP;
743 goto leave;
745 if (xmlStrcmp(response_root->name, BAD_CAST "Envelope") ||
746 xmlStrcmp(response_root->ns->href, BAD_CAST SOAP_NS)) {
747 isds_log_message(context, "SOAP response is not SOAP 1.1 document");
748 err = IE_SOAP;
749 goto leave;
752 /* Check for SOAP Headers */
753 response_soap_headers = xmlXPathEvalExpression(
754 BAD_CAST "/soap:Envelope/soap:Header/"
755 "*[@soap:mustUnderstand/text() = true()]", xpath_ctx);
756 if (!response_soap_headers) {
757 err = IE_ERROR;
758 goto leave;
760 if (!xmlXPathNodeSetIsEmpty(response_soap_headers->nodesetval)) {
761 isds_log_message(context,
762 _("SOAP response requires unsupported feature"));
763 /* TODO: log the headers
764 * xmlChar *fragment = NULL;
765 * fragment = xmlXPathCastNodeSetToSting(response_soap_headers->nodesetval);*/
766 err = IE_NOTSUP;
767 goto leave;
770 /* Get SOAP Body */
771 response_soap_body = xmlXPathEvalExpression(
772 BAD_CAST "/soap:Envelope/soap:Body", xpath_ctx);
773 if (!response_soap_body) {
774 err = IE_ERROR;
775 goto leave;
777 if (xmlXPathNodeSetIsEmpty(response_soap_body->nodesetval)) {
778 isds_log_message(context,
779 _("SOAP response does not contain SOAP Body element"));
780 err = IE_SOAP;
781 goto leave;
783 if (response_soap_body->nodesetval->nodeNr > 1) {
784 isds_log_message(context,
785 _("SOAP response has more than one Body element"));
786 err = IE_SOAP;
787 goto leave;
790 /* Check for SOAP Fault */
791 response_soap_fault = xmlXPathEvalExpression(
792 BAD_CAST "/soap:Envelope/soap:Body/soap:Fault", xpath_ctx);
793 if (!response_soap_fault) {
794 err = IE_ERROR;
795 goto leave;
797 if (!xmlXPathNodeSetIsEmpty(response_soap_fault->nodesetval)) {
798 /* Server signals Fault. Gather error message and croak. */
799 /* XXX: Only first message is passed */
800 char *message = NULL, *message_locale = NULL;
801 xpath_ctx->node = response_soap_fault->nodesetval->nodeTab[0];
802 xmlXPathFreeObject(response_soap_fault);
803 /* XXX: faultstring and faultcode are in no name space according
804 * ISDS specification */
805 /* First more verbose faultstring */
806 response_soap_fault = xmlXPathEvalExpression(
807 BAD_CAST "faultstring[1]/text()", xpath_ctx);
808 if (response_soap_fault &&
809 !xmlXPathNodeSetIsEmpty(response_soap_fault->nodesetval)) {
810 message = (char *)
811 xmlXPathCastNodeSetToString(response_soap_fault->nodesetval);
812 message_locale = _isds_utf82locale(message);
814 /* If not available, try shorter faultcode */
815 if (!message_locale) {
816 free(message);
817 xmlXPathFreeObject(response_soap_fault);
818 response_soap_fault = xmlXPathEvalExpression(
819 BAD_CAST "faultcode[1]/text()", xpath_ctx);
820 if (response_soap_fault &&
821 !xmlXPathNodeSetIsEmpty(response_soap_fault->nodesetval)) {
822 message = (char *)
823 xmlXPathCastNodeSetToString(
824 response_soap_fault->nodesetval);
825 message_locale = _isds_utf82locale(message);
829 /* Croak */
830 if (message_locale)
831 isds_printf_message(context, _("SOAP response signals Fault: %s"),
832 message_locale);
833 else
834 isds_log_message(context, _("SOAP response signals Fault"));
836 free(message_locale);
837 free(message);
839 err = IE_SOAP;
840 goto leave;
844 /* Extract XML Tree with ISDS response from SOAP envelope and return it.
845 * XXX: response_soap_body is Body, we need children which may not exist
846 * (i.e. empty Body). */
847 /* TODO: Destroy SOAP response but Body children. This is more memory
848 * friendly than copying (potentially) fat body */
849 if (response_soap_body->nodesetval->nodeTab[0]->children) {
850 *response = xmlDocCopyNodeList(response_soap_doc,
851 response_soap_body->nodesetval->nodeTab[0]->children);
852 if (!*response) {
853 err = IE_NOMEM;
854 goto leave;
856 } else *response = NULL;
858 /* Save raw response */
859 if (raw_response) {
860 *raw_response = http_response;
861 *raw_response_length = response_length;
862 http_response = NULL;
866 leave:
867 if (err) {
868 xmlFreeNodeList(*response);
869 *response = NULL;
872 xmlXPathFreeObject(response_soap_fault);
873 xmlXPathFreeObject(response_soap_body);
874 xmlXPathFreeObject(response_soap_headers);
875 xmlXPathFreeContext(xpath_ctx);
876 xmlFreeDoc(response_soap_doc);
877 free(mime_type);
878 free(http_response);
879 xmlSaveClose(save_ctx);
880 xmlBufferFree(http_request);
881 xmlFreeDoc(request_soap_doc); /* recursive, frees request_body, soap_ns*/
882 free(url);
884 return err;
888 /* LibXML functions:
890 * void xmlInitParser(void)
891 * Initialization function for the XML parser. This is not reentrant. Call
892 * once before processing in case of use in multithreaded programs.
894 * int xmlInitParserCtxt(xmlParserCtxtPtr ctxt)
895 * Initialize a parser context
897 * xmlDocPtr xmlCtxtReadDoc(xmlParserCtxtPtr ctxt, const xmlChar * cur,
898 * const * char URL, const char * encoding, int options);
899 * Parse in-memory NULL-terminated document @cur.
901 * xmlDocPtr xmlParseMemory(const char * buffer, int size)
902 * Parse an XML in-memory block and build a tree.
904 * xmlParserCtxtPtr xmlCreateMemoryParserCtxt(const char * buffer, int
905 * size);
906 * Create a parser context for an XML in-memory document.
908 * xmlParserCtxtPtr xmlCreateDocParserCtxt(const xmlChar * cur)
909 * Creates a parser context for an XML in-memory document.
911 * xmlDocPtr xmlCtxtReadMemory(xmlParserCtxtPtr ctxt,
912 * const char * buffer, int size, const char * URL, const char * encoding,
913 * int options)
914 * Parse an XML in-memory document and build a tree. This reuses the existing
915 * @ctxt parser context.
917 * void xmlCleanupParser(void)
918 * Cleanup function for the XML library. It tries to reclaim all parsing
919 * related glob document related memory. Calling this function should not
920 * prevent reusing the libr finished using the library or XML document built
921 * with it.
923 * void xmlClearParserCtxt(xmlParserCtxtPtr ctxt)
924 * Clear (release owned resources) and reinitialize a parser context.
926 * void xmlCtxtReset(xmlParserCtxtPtr ctxt)
927 * Reset a parser context
929 * void xmlFreeParserCtxt(xmlParserCtxtPtr ctxt)
930 * Free all the memory used by a parser context. However the parsed document
931 * in ctxt->myDoc is not freed.
933 * void xmlFreeDoc(xmlDocPtr cur)
934 * Free up all the structures used by a document, tree included.