Parse the SOAP response
[libisds.git] / src / soap.c
blobbfb9da055452b157c39f1a3a3f81c4b9b45a6d92
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 document with request.
227 * @request_length is lenght of @request in bytes
228 * @file and @request must be NULL rather than empty strings, if the should
229 * not be signaled in the SOAP request.
230 * @reponse is automatically reallocated() buffer to fit SOAP response with
231 * @response_length (does not need to match allocatef memory exactly). You must
232 * free() the @response. In case of error the response memory and lenght will
233 * be deallocated and zerod automatically.
234 * Side effect: message buffer */
235 _hidden isds_error soap(struct isds_ctx *context, const char *file,
236 const void *request, const size_t request_length,
237 void **response, size_t *response_length) {
239 char *url;
240 isds_error err = IE_SUCCESS;
241 char *mime_type = NULL;
242 xmlDocPtr soap_tree = NULL;
243 xmlXPathContextPtr xpath_ctx = NULL;
244 xmlXPathObjectPtr soap_body = NULL;
247 if (!context) return IE_INVALID_CONTEXT;
248 if (request_length > 0 && !request) return IE_INVAL;
249 if (!response || !response_length) return IE_INVAL;
251 url = astrcat(context->url, file);
252 if (!url) return IE_NOMEM;
254 /* TODO: Wrap the request into SOAP envelope */
256 err = http(context, url, request, request_length,
257 response, response_length,
258 &mime_type, NULL);
260 if (err) {
261 goto leave;
264 /* Check for Content-Type: application/soap+xml */
265 if (mime_type && strcmp(mime_type, "application/soap+xml")
266 && strcmp(mime_type, "application/xml")
267 && strcmp(mime_type, "text/xml")) {
268 isds_log_message(context, url);
269 isds_append_message(context, _(": bad MIME type sent by server: "));
270 isds_append_message(context, mime_type);
271 err = IE_SOAP;
272 goto leave;
275 /* TODO: Convert returned body into XML default encoding */
277 /* TODO: Extract XML Tree with ISDS response from SOAP envelope and return
278 * it*/
279 soap_tree = xmlParseMemory(*response, *response_length);
280 if (!soap_tree) {
281 err = IE_XML;
282 goto leave;
285 xpath_ctx = xmlXPathNewContext(soap_tree);
286 if (!xpath_ctx) {
287 err = IE_ERROR;
288 goto leave;
291 if (register_namespaces(xpath_ctx)) {
292 err = IE_ERROR;
293 goto leave;
296 soap_body = xmlXPathEvalExpression(BAD_CAST "/soap:Envelope/soap:Body/*",
297 xpath_ctx);
302 leave:
303 xmlXPathFreeObject(soap_body);
304 xmlXPathFreeContext(xpath_ctx);
305 xmlFreeDoc(soap_tree);
306 free(url);
308 if (err) {
309 free(*response);
310 *response = NULL;
311 *response_length = 0;
312 free(mime_type);
315 return err;
319 /* LibXML functions:
321 * void xmlInitParser(void)
322 * Initialization function for the XML parser. This is not reentrant. Call
323 * once before processing in case of use in multithreaded programs.
325 * int xmlInitParserCtxt(xmlParserCtxtPtr ctxt)
326 * Initialize a parser context
328 * xmlDocPtr xmlCtxtReadDoc(xmlParserCtxtPtr ctxt, const xmlChar * cur,
329 * const * char URL, const char * encoding, int options);
330 * Parse in-memory NULL-terminated document @cur.
332 * xmlDocPtr xmlParseMemory(const char * buffer, int size)
333 * Parse an XML in-memory block and build a tree.
335 * xmlParserCtxtPtr xmlCreateMemoryParserCtxt(const char * buffer, int
336 * size);
337 * Create a parser context for an XML in-memory document.
339 * xmlParserCtxtPtr xmlCreateDocParserCtxt(const xmlChar * cur)
340 * Creates a parser context for an XML in-memory document.
342 * xmlDocPtr xmlCtxtReadMemory(xmlParserCtxtPtr ctxt,
343 * const char * buffer, int size, const char * URL, const char * encoding,
344 * int options)
345 * Parse an XML in-memory document and build a tree. This reuses the existing
346 * @ctxt parser context.
348 * void xmlCleanupParser(void)
349 * Cleanup function for the XML library. It tries to reclaim all parsing
350 * related glob document related memory. Calling this function should not
351 * prevent reusing the libr finished using the library or XML document built
352 * with it.
354 * void xmlClearParserCtxt(xmlParserCtxtPtr ctxt)
355 * Clear (release owned resources) and reinitialize a parser context.
357 * void xmlCtxtReset(xmlParserCtxtPtr ctxt)
358 * Reset a parser context
360 * void xmlFreeParserCtxt(xmlParserCtxtPtr ctxt)
361 * Free all the memory used by a parser context. However the parsed document
362 * in ctxt->myDoc is not freed.
364 * void xmlFreeDoc(xmlDocPtr cur)
365 * Free up all the structures used by a document, tree included.