1 #define _XOPEN_SOURCE 500 /* strdup from string.h */
8 /* Private structure for write_body() call back */
15 /* Close connection to server and destroy CURL handle associated
17 _hidden isds_error
close_connection(struct isds_ctx
*context
) {
18 if (!context
) return IE_INVALID_CONTEXT
;
21 curl_easy_cleanup(context
->curl
);
23 isds_log(ILF_HTTP
, ILL_DEBUG
, _("Connection to server closed"));
26 return IE_CONNECTION_CLOSED
;
31 /* CURL call back function called when chunk of HTTP reponse body is available.
32 * @buffer points to new data
33 * @size * @nmemb is length of the chunk in bytes. Zero means empty body.
34 * @userp is private structure.
35 * Must reuturn the length of the chunk, otherwise CURL will signal
36 * CURL_WRITE_ERROR. */
37 static size_t write_body(void *buffer
, size_t size
, size_t nmemb
, void *userp
) {
38 struct soap_body
*body
= (struct soap_body
*) userp
;
41 /* FIXME: Check for (size * nmemb + body->lengt) !> SIZE_T_MAX.
42 * Precompute the product then. */
44 if (!body
) return 0; /* This should never happen */
45 if (0 == (size
* nmemb
)) return 0; /* Empty body */
47 new_data
= realloc(body
->data
, body
->length
+ size
* nmemb
);
48 if (!new_data
) return 0;
50 memcpy(new_data
+ body
->length
, buffer
, size
* nmemb
);
52 body
->data
= new_data
;
53 body
->length
+= size
* nmemb
;
55 return (size
* nmemb
);
60 * @context holds the base URL,
61 * @url is a (CGI) file of SOAP URL,
62 * @request is body for POST request
63 * @request_length is length of @request in bytes
64 * @reponse is automatically reallocated() buffer to fit HTTP response with
65 * @response_length (does not need to match allocatef memory exactly). You must
66 * free() the @response.
67 * @mime_type is automatically allocated MIME type send by server (*NULL if not
68 * sent). Set NULL if you don't care.
69 * @charset is charset of the body signaled by server. The same constrains
70 * like on @mime_type apply.
71 * @http_code is final HTTP code returned by server. This can be 200, 401, 500
72 * or any other one. Pass NULL if you don't interrest.
73 * In case of error, the response memory, MIME type, charset and lenght will be
74 * deallocated and zerod automatically. Thus be sure they are preallocated or
75 * they points to NULL.
76 * Be ware that successful return value does not mean the HTTP request has
77 * been accepted by the server. You must cosult @http_code. OTOH, failure
78 * return value means the request could not been sent (e.g. SSL error).
79 * Side effect: message buffer */
80 static isds_error
http(struct isds_ctx
*context
, const char *url
,
81 const void *request
, const size_t request_length
,
82 void **response
, size_t *response_length
,
83 char **mime_type
, char **charset
, long *http_code
) {
86 isds_error err
= IE_SUCCESS
;
87 struct soap_body body
;
89 struct curl_slist
*headers
= NULL
;
92 if (!context
) return IE_INVALID_CONTEXT
;
93 if (!url
) return IE_INVAL
;
94 if (request_length
> 0 && !request
) return IE_INVAL
;
95 if (!response
|| !response_length
) return IE_INVAL
;
97 /* Set the body here to allow deallocataion in leave block */
98 body
.data
= *response
;
101 /* Set Request-URI */
102 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_URL
, url
);
104 /* Set TLS options */
105 if (!curl_err
&& context
->tls_verify_server
) {
106 if (!*context
->tls_verify_server
)
107 isds_log(ILF_SEC
, ILL_WARNING
,
108 _("Disabling server identity verification. "
109 "That was your decision.\n"));
110 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_SSL_VERIFYPEER
,
111 (*context
->tls_verify_server
)? 1L : 0L);
113 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_SSL_VERIFYHOST
,
114 (*context
->tls_verify_server
)? 2L : 0L);
117 if (!curl_err
&& context
->tls_ca_file
) {
118 isds_log(ILF_SEC
, ILL_INFO
,
119 _("CA certificates will be searched in `%s' file since now\n"),
120 context
->tls_ca_file
);
121 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_CAINFO
,
122 context
->tls_ca_file
);
124 if (!curl_err
&& context
->tls_ca_dir
) {
125 isds_log(ILF_SEC
, ILL_INFO
,
126 _("CA certificates will be searched in `%s' directory "
127 "since now\n"), context
->tls_ca_file
);
128 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_CAINFO
,
129 context
->tls_ca_file
);
133 /* Set credentials */
134 if (!curl_err
&& context
->username
) {
135 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_USERNAME
,
138 if (!curl_err
&& context
->password
) {
139 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_PASSWORD
,
145 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_NOSIGNAL
, 1);
147 if (!curl_err
&& context
->timeout
) {
148 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_TIMEOUT_MS
,
152 /* Set other CURL features */
154 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_FAILONERROR
, 0);
157 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_FOLLOWLOCATION
, 1);
160 /* TODO: Make the redirect depth configurable */
161 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_MAXREDIRS
, 8);
164 curl_err
= curl_easy_setopt(context
->curl
,
165 CURLOPT_UNRESTRICTED_AUTH
, 1);
168 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_COOKIEFILE
, "");
171 /* Set get-response function */
173 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_WRITEFUNCTION
,
177 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_WRITEDATA
, &body
);
180 /* Set MIME types and headers requires by SOAP 1.1.
181 * SOAP 1.1 requires text/xml, SOAP 1.2 requires application/soap+xml */
183 headers
= curl_slist_append(headers
,
184 "Accept: application/soap+xml,application/xml,text/xml");
189 headers
= curl_slist_append(headers
, "Content-Type: text/xml");
194 headers
= curl_slist_append(headers
, "SOAPAction: ");
199 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_HTTPHEADER
, headers
);
202 /* Set user agent identification */
203 /* TODO: Present library version, curl etc. in User-Agent */
204 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_USERAGENT
, "libisds");
207 /* Set POST request body */
209 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_POST
, 1);
212 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_POSTFIELDS
, request
);
215 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_POSTFIELDSIZE
,
219 /* Check for errors so far */
221 isds_log_message(context
, curl_easy_strerror(curl_err
));
226 isds_log(ILF_HTTP
, ILL_DEBUG
, _("Sending POST request to %s\n"), url
);
227 isds_log(ILF_HTTP
, ILL_DEBUG
,
228 _("POST body length: %zu, content follows:\n"), request_length
);
229 isds_log(ILF_HTTP
, ILL_DEBUG
, "%.*s\n", request_length
, request
);
230 isds_log(ILF_HTTP
, ILL_DEBUG
, _("End of POST body\n"));
231 if ((log_facilities
& ILF_HTTP
) && (log_level
>= ILL_DEBUG
) ) {
232 curl_easy_setopt(context
->curl
, CURLOPT_VERBOSE
, 1);
237 curl_err
= curl_easy_perform(context
->curl
);
239 /* Wipe credentials out of the handler */
240 if (context
->username
) {
241 curl_easy_setopt(context
->curl
, CURLOPT_USERNAME
, NULL
);
243 if (context
->password
) {
244 curl_easy_setopt(context
->curl
, CURLOPT_PASSWORD
, NULL
);
248 curl_err
= curl_easy_getinfo(context
->curl
, CURLINFO_CONTENT_TYPE
,
252 /* TODO: CURL is not internationalized yet. Collect CURL messages for
254 isds_printf_message(context
,
255 _("%s: %s"), url
, _(curl_easy_strerror(curl_err
)));
260 isds_log(ILF_HTTP
, ILL_DEBUG
, _("Final response to %s received\n"), url
);
261 isds_log(ILF_HTTP
, ILL_DEBUG
,
262 _("Response body length: %zu, content follows:\n"),
264 isds_log(ILF_HTTP
, ILL_DEBUG
, "%.*s\n", body
.length
, body
.data
);
265 isds_log(ILF_HTTP
, ILL_DEBUG
, _("End of response body\n"));
268 /* Extract MIME type and charset */
273 sep
= strchr(content_type
, ';');
274 if (sep
) offset
= (size_t) (sep
- content_type
);
275 else offset
= strlen(content_type
);
278 *mime_type
= malloc(offset
+ 1);
283 memcpy(*mime_type
, content_type
, offset
);
284 (*mime_type
)[offset
] = '\0';
291 sep
= strstr(sep
, "charset=");
295 *charset
= strdup(sep
+ 8);
305 /* Get HTTP response code */
307 curl_err
= curl_easy_getinfo(context
->curl
,
308 CURLINFO_RESPONSE_CODE
, http_code
);
316 curl_slist_free_all(headers
);
332 close_connection(context
);
335 *response
= body
.data
;
336 *response_length
= body
.length
;
343 * @context holds the base URL,
344 * @file is a (CGI) file of SOAP URL,
345 * @request is XML node set with SOAP request body.
346 * @file must be NULL, @request should be NULL rather than empty, if they should
347 * not be signaled in the SOAP request.
348 * @reponse is automatically allocated() node set with SOAP response body.
349 * You must xmlFreeNodeList() it. This is literal body, empty (NULL), one node
350 * or more nodes can be returned.
351 * @raw_response is automatically allocated bitstream with response body. Use
352 * NULL if you don't care
353 * @raw_response_length is size of @raw_response in bytes
354 * In case of error the response will be deallocated automatically.
355 * Side effect: message buffer */
356 _hidden isds_error
soap(struct isds_ctx
*context
, const char *file
,
357 const xmlNodePtr request
, xmlNodePtr
*response
,
358 void **raw_response
, size_t *raw_response_length
) {
360 isds_error err
= IE_SUCCESS
;
362 char *mime_type
= NULL
;
364 xmlBufferPtr http_request
= NULL
;
365 xmlSaveCtxtPtr save_ctx
= NULL
;
366 xmlDocPtr request_soap_doc
= NULL
;
367 xmlNodePtr request_soap_envelope
= NULL
, request_soap_body
= NULL
;
368 xmlNsPtr soap_ns
= NULL
;
369 void *http_response
= NULL
;
370 size_t response_length
= 0;
371 xmlDocPtr response_soap_doc
= NULL
;
372 xmlNodePtr response_root
= NULL
;
373 xmlXPathContextPtr xpath_ctx
= NULL
;
374 xmlXPathObjectPtr response_soap_headers
= NULL
, response_soap_body
= NULL
,
375 response_soap_fault
= NULL
;
378 if (!context
) return IE_INVALID_CONTEXT
;
379 if (!response
) return IE_INVAL
;
380 if (!raw_response_length
&& raw_response
) return IE_INVAL
;
382 xmlFreeNodeList(*response
);
384 if (raw_response
) *raw_response
= NULL
;
386 url
= astrcat(context
->url
, file
);
387 if (!url
) return IE_NOMEM
;
389 /* Build SOAP request envelope */
390 request_soap_doc
= xmlNewDoc(BAD_CAST
"1.0");
391 if (!request_soap_doc
) {
392 isds_log_message(context
, _("Could not build SOAP request document"));
396 request_soap_envelope
= xmlNewNode(NULL
, BAD_CAST
"Envelope");
397 if (!request_soap_envelope
) {
398 isds_log_message(context
, _("Could not build SOAP request envelope"));
402 xmlDocSetRootElement(request_soap_doc
, request_soap_envelope
);
403 /* Only this way we get namespace definition as @xmlns:soap,
404 * otherwise we get namespace prefix without definition */
405 soap_ns
= xmlNewNs(request_soap_envelope
, BAD_CAST SOAP_NS
, NULL
);
407 isds_log_message(context
, _("Could not create SOAP name space"));
411 xmlSetNs(request_soap_envelope
, soap_ns
);
412 request_soap_body
= xmlNewChild(request_soap_envelope
, NULL
,
413 BAD_CAST
"Body", NULL
);
414 if (!request_soap_body
) {
415 isds_log_message(context
,
416 _("Could not add Body to SOAP request envelope"));
421 /* Append request XML node set to SOAP body if request is not empty */
422 /* XXX: Copy of request must be used, otherwise xmlFreeDoc(request_soap_doc)
423 * would destroy this outer structure. */
425 xmlNodePtr request_copy
= xmlCopyNodeList(request
);
427 isds_log_message(context
,
428 _("Could not copy request content"));
432 if (!xmlAddChildList(request_soap_body
, request_copy
)) {
433 xmlFreeNodeList(request_copy
);
434 isds_log_message(context
,
435 _("Could not add request content to SOAP "
436 "request envelope"));
443 /* Serialize the SOAP request into HTTP request body */
444 http_request
= xmlBufferCreate();
446 isds_log_message(context
,
447 _("Could not create xmlBuffer for HTTP request body"));
451 /* Last argument 1 means format the XML tree. This is pretty but it breaks
452 * digital signatures probably because ISDS abandoned XMLDSig */
453 save_ctx
= xmlSaveToBuffer(http_request
, "UTF-8", 1);
455 isds_log_message(context
,
456 _("Could not create XML serializer"));
460 /* XXX: According LibXML documentation, this function does not return
461 * meaningfull value yet */
462 xmlSaveDoc(save_ctx
, request_soap_doc
);
463 if (-1 == xmlSaveFlush(save_ctx
)) {
464 isds_log_message(context
,
465 _("Could not serialize SOAP request to HTTP request bddy"));
470 isds_log(ILF_SOAP
, ILL_DEBUG
,
471 _("SOAP request to sent to %s:\n%.*s\nEnd of SOAP request\n"),
472 url
, http_request
->use
, http_request
->content
);
474 err
= http(context
, url
, http_request
->content
, http_request
->use
,
475 &http_response
, &response_length
,
476 &mime_type
, NULL
, &http_code
);
478 /* TODO: HTTP binding for SOAP prescribes non-200 HTTP return codes
479 * to be processes too. */
485 /* Check for HTTP return code */
486 isds_log(ILF_SOAP
, ILL_DEBUG
, _("Server returned %ld HTTP code\n"),
489 /* XXX: We must see which code is used for not permitted ISDS
490 * operation like downloading message without proper user
491 * permissions. In that cat we should keep connection opened. */
493 err
= IE_NOT_LOGGED_IN
;
494 isds_log_message(context
, _("Authentication failed"));
499 isds_log_message(context
,
500 _("Code 404: Document not found on server"));
503 /* 500 should return standard SOAP message */
506 /* Check for Content-Type: text/xml.
507 * Do it after HTTP code check because 401 Unauthorized returns HTML web
508 * page for browsers. */
509 if (mime_type
&& strcmp(mime_type
, "text/xml")
510 && strcmp(mime_type
, "application/soap+xml")
511 && strcmp(mime_type
, "application/xml")) {
512 char *mime_type_locale
= utf82locale(mime_type
);
513 isds_printf_message(context
,
514 _("%s: bad MIME type sent by server: %s"), url
,
516 free(mime_type_locale
);
521 /* TODO: Convert returned body into XML default encoding */
523 /* Parse the HTTP body as XML */
524 response_soap_doc
= xmlParseMemory(http_response
, response_length
);
525 if (!response_soap_doc
) {
530 xpath_ctx
= xmlXPathNewContext(response_soap_doc
);
536 if (register_namespaces(xpath_ctx
, MESSAGE_NS_UNSIGNED
)) {
541 isds_log(ILF_SOAP
, ILL_DEBUG
,
542 _("SOAP response received:\n%.*s\nEnd of SOAP response\n"),
543 response_length
, http_response
);
546 /* Check for SOAP version */
547 response_root
= xmlDocGetRootElement(response_soap_doc
);
548 if (!response_root
) {
549 isds_log_message(context
, "SOAP response has no root element");
553 if (xmlStrcmp(response_root
->name
, BAD_CAST
"Envelope") ||
554 xmlStrcmp(response_root
->ns
->href
, BAD_CAST SOAP_NS
)) {
555 isds_log_message(context
, "SOAP response is not SOAP 1.1 document");
560 /* Check for SOAP Headers */
561 response_soap_headers
= xmlXPathEvalExpression(
562 BAD_CAST
"/soap:Envelope/soap:Header/"
563 "*[@soap:mustUnderstand/text() = true()]", xpath_ctx
);
564 if (!response_soap_headers
) {
568 if (!xmlXPathNodeSetIsEmpty(response_soap_headers
->nodesetval
)) {
569 isds_log_message(context
,
570 _("SOAP response requires unsupported feature"));
571 /* TODO: log the headers
572 * xmlChar *fragment = NULL;
573 * fragment = xmlXPathCastNodeSetToSting(response_soap_headers->nodesetval);*/
579 response_soap_body
= xmlXPathEvalExpression(
580 BAD_CAST
"/soap:Envelope/soap:Body", xpath_ctx
);
581 if (!response_soap_body
) {
585 if (xmlXPathNodeSetIsEmpty(response_soap_body
->nodesetval
)) {
586 isds_log_message(context
,
587 _("SOAP response does not contain SOAP Body element"));
591 if (response_soap_body
->nodesetval
->nodeNr
> 1) {
592 isds_log_message(context
,
593 _("SOAP response has more than one Body element"));
598 /* Check for SOAP Fault */
599 response_soap_fault
= xmlXPathEvalExpression(
600 BAD_CAST
"/soap:Envelope/soap:Body/soap:Fault", xpath_ctx
);
601 if (!response_soap_fault
) {
605 if (!xmlXPathNodeSetIsEmpty(response_soap_fault
->nodesetval
)) {
606 /* TODO: log the fultcode and faultstring */
607 isds_log_message(context
, _("SOAP response signals Fault"));
613 /* Extract XML Tree with ISDS response from SOAP envelope and return it.
614 * XXX: response_soap_body is Body, we need children which may not exist
615 * (i.e. empty Body). */
616 /* TODO: Destroy SOAP response but Body childern. This is more memory
617 * friendly than copying (potentialy) fat body */
618 if (response_soap_body
->nodesetval
->nodeTab
[0]->children
) {
619 *response
= xmlDocCopyNodeList(response_soap_doc
,
620 response_soap_body
->nodesetval
->nodeTab
[0]->children
);
625 } else *response
= NULL
;
627 /* Save raw response */
629 *raw_response
= http_response
;
630 *raw_response_length
= response_length
;
637 xmlFreeNodeList(*response
);
641 xmlXPathFreeObject(response_soap_fault
);
642 xmlXPathFreeObject(response_soap_body
);
643 xmlXPathFreeObject(response_soap_headers
);
644 xmlXPathFreeContext(xpath_ctx
);
645 xmlFreeDoc(response_soap_doc
);
648 xmlSaveClose(save_ctx
);
649 xmlBufferFree(http_request
);
650 xmlFreeDoc(request_soap_doc
); /* recursive, frees request_body, soap_ns*/
659 * void xmlInitParser(void)
660 * Initialization function for the XML parser. This is not reentrant. Call
661 * once before processing in case of use in multithreaded programs.
663 * int xmlInitParserCtxt(xmlParserCtxtPtr ctxt)
664 * Initialize a parser context
666 * xmlDocPtr xmlCtxtReadDoc(xmlParserCtxtPtr ctxt, const xmlChar * cur,
667 * const * char URL, const char * encoding, int options);
668 * Parse in-memory NULL-terminated document @cur.
670 * xmlDocPtr xmlParseMemory(const char * buffer, int size)
671 * Parse an XML in-memory block and build a tree.
673 * xmlParserCtxtPtr xmlCreateMemoryParserCtxt(const char * buffer, int
675 * Create a parser context for an XML in-memory document.
677 * xmlParserCtxtPtr xmlCreateDocParserCtxt(const xmlChar * cur)
678 * Creates a parser context for an XML in-memory document.
680 * xmlDocPtr xmlCtxtReadMemory(xmlParserCtxtPtr ctxt,
681 * const char * buffer, int size, const char * URL, const char * encoding,
683 * Parse an XML in-memory document and build a tree. This reuses the existing
684 * @ctxt parser context.
686 * void xmlCleanupParser(void)
687 * Cleanup function for the XML library. It tries to reclaim all parsing
688 * related glob document related memory. Calling this function should not
689 * prevent reusing the libr finished using the library or XML document built
692 * void xmlClearParserCtxt(xmlParserCtxtPtr ctxt)
693 * Clear (release owned resources) and reinitialize a parser context.
695 * void xmlCtxtReset(xmlParserCtxtPtr ctxt)
696 * Reset a parser context
698 * void xmlFreeParserCtxt(xmlParserCtxtPtr ctxt)
699 * Free all the memory used by a parser context. However the parsed document
700 * in ctxt->myDoc is not freed.
702 * void xmlFreeDoc(xmlDocPtr cur)
703 * Free up all the structures used by a document, tree included.