libsyncml 0.5.4

- Main Page
- Modules
- Data Structures
- Files

- File List

http_client.c
1 /*
2 * libsyncml - A syncml protocol implementation
3 * Copyright (C) 2005 Armin Bauer <armin.bauer@opensync.org>
4 * Copyright (C) 2007-2009 Michael Bell <michael.bell@opensync.org>
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 *
20 */
21
22 #include <libsyncml/syncml.h>
23 #include <libsyncml/syncml_internals.h>
24 #include <libsyncml/sml_error_internals.h>
25
26 #include <libsyncml/sml_transport_internals.h>
27
28 #include "http_client_internals.h"
29
30 static void smlTransportHttpClientSend(void *userdata, void *link, SmlTransportData *data, SmlError *error);
31 static void smlTransportHttpClientDisconnect(void *data, void *linkdata);
32 static SmlBool smlTransportHttpClientFinalize(void *data, SmlError **error);
33
34 #ifdef HAVE_LIBSOUP22
35 #define soup_message_headers_get soup_message_get_header
36 #define soup_message_headers_append soup_message_add_header
37 #endif
38
39 #ifdef HAVE_LIBSOUP22
40 static void smlTransportHttpClientCallback(SoupMessage *msg, gpointer userdata)
41 {
42 smlTrace(TRACE_ENTRY, "%s(%p, %p)", __func__, msg, userdata);
43 #else
44 static void smlTransportHttpClientCallback(SoupSession *session, SoupMessage *msg, gpointer userdata)
45 {
46 /* session is only traced to avoid warnings from gcc profiling */
47 smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, session, msg, userdata);
48 #endif
49 SmlTransportHttpClientEnv *env = userdata;
50 SmlError *error = NULL;
51
52 /* handle potential session shutdown */
53 if (env->connectDone && msg->status_code == SOUP_STATUS_CANCELLED)
54 {
55 /* FIXME: This is clearly a disconnect.
56 * FIXME: You must check if a disconnect is ongoing.
57 */
58 smlTrace(TRACE_EXIT, "%s - ignoring due to shutdown", __func__);
59 return;
60 }
61
62 /* handle a fresh connection */
63 if (!env->connectDone)
64 {
65 /* This is the first answer or any other event which is
66 * detected after the first message was sent.
67 * We have to check and signal if the connection is ok or not.
68 */
69 if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code))
70 {
71 smlErrorSet(&error,
72 SML_ERROR_GENERIC,
73 "Connection failed (%d) - %s", msg->status_code, msg->reason_phrase);
74 goto error;
75 }
76 else
77 {
78 /* If a transport has no public connect function
79 * then the connect event is sent by the
80 * transport layer itself.
81 */
82 smlTrace(TRACE_INTERNAL, "%s: Connect done", __func__);
83 if (!env->tsp->connected)
84 {
85 /* If a user of the library know that connect
86 * is not available then this is tolerated.
87 */
88 smlTransportReceiveEvent(
89 env->tsp, NULL,
90 SML_TRANSPORT_EVENT_CONNECT_DONE,
91 NULL, NULL);
92 }
93 }
94 env->connectDone = TRUE;
95 }
96 smlTrace(TRACE_INTERNAL, "%s: Result: %d %s",
97 __func__, msg->status_code, msg->reason_phrase);
98
99 /* start http handling with some checks */
100
101 /* check the library status for errors */
102 if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) {
103 smlErrorSet(&error, SML_ERROR_GENERIC, "Call not successfull: %d %s",
104 msg->status_code, msg->reason_phrase);
105 goto error;
106 }
107
108 /* check the header of the received message */
109 const char *header = soup_message_headers_get(msg->response_headers, "Content-Type");
110 smlTrace(TRACE_INTERNAL, "%s: content type ::= %s",
111 __func__, header);
112
113 SmlMimeType mimetype = SML_MIMETYPE_UNKNOWN;
114 if (header && !g_strncasecmp(header, SML_ELEMENT_XML, strlen(SML_ELEMENT_XML)))
115 mimetype = SML_MIMETYPE_XML;
116 else if(header && !g_strncasecmp(header, SML_ELEMENT_WBXML, strlen(SML_ELEMENT_WBXML)))
117 mimetype = SML_MIMETYPE_WBXML;
118 else if(header && !g_strncasecmp(header, SML_ELEMENT_SAN, strlen(SML_ELEMENT_SAN)))
119 mimetype = SML_MIMETYPE_SAN;
120 else if (header) {
121 smlErrorSet(&error, SML_ERROR_GENERIC, "Unknown mimetype");
122 goto error;
123 } else {
124 smlErrorSet(&error, SML_ERROR_GENERIC, "Faulty mimetype");
125 goto error;
126
127 }
128
129 /* prepare the received message */
130 char *data;
131 gsize length;
132
133 #ifdef HAVE_LIBSOUP22
134 length = msg->response.length;
135 #else
136 length = msg->response_body->length;
137 #endif
138 smlTrace(TRACE_INTERNAL, "%s: The message length is %i.", __func__, length);
139
140 /* We need one byte more than the response length
141 * because the data can be a native XML message.
142 * If the data is a native XML message then it is
143 * sometimes directly used as string.
144 *
145 * The string is automatically NULL terminated
146 * because smlTryMalloc0 fills the new memory with NULLs.
147 */
148 data = smlTryMalloc0(length + 1, &error);
149 if (!data)
150 goto error;
151
152 #ifdef HAVE_LIBSOUP22
153 memcpy(data, msg->response.body, length);
154 #else
155 memcpy(data, msg->response_body->data, length);
156 #endif
157
158 SmlTransportData *tspdata = smlTransportDataNew(data, length, mimetype, TRUE, &error);
159 data = NULL;
160 if (!tspdata)
161 goto error_free_data;
162
163 /* signal the received message */
164 smlTransportReceiveEvent(env->tsp, NULL, SML_TRANSPORT_EVENT_DATA, tspdata, NULL);
165
166 /* cleanup */
167 smlTransportDataDeref(tspdata);
168
169 smlTrace(TRACE_EXIT, "%s", __func__);
170 return;
171
172 error_free_data:
173 smlSafeCFree(&data);
174 error:
175 smlTransportReceiveEvent(env->tsp, NULL, SML_TRANSPORT_EVENT_ERROR, NULL, error);
176 smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(&error));
177 smlErrorDeref(&error);
178 }
179
180 /* This is the authentication callback for libsoup.
181 * If a SyncML server uses HTTP then the specification allows
182 * standard HTTP authentication. This callback provides the
183 * http library with the user and passphrase for the SyncML
184 * account. Usually this is not required because the most
185 * SyncML server manage the authentication via SyncHdr.
186 */
187
188 #ifdef HAVE_LIBSOUP22
189 static void
190 _authenticate (SoupSession *session, SoupMessage *msg,
191 const char *auth_type, const char *auth_realm,
192 char **username, char **password, gpointer data)
193 {
194 SmlTransportHttpClientEnv *env = data;
195 smlTrace(TRACE_INTERNAL, "%s: authentication via auth_type %s", __func__, VA_STRING(auth_type));
196 *username = g_strdup(env->username);
197 *password = g_strdup(env->password);
198 }
199 #else
200 static void
201 _authenticate (SoupSession *session, SoupMessage *msg,
202 SoupAuth *auth, gboolean retrying, gpointer data)
203 {
204 smlTrace(TRACE_INTERNAL, "%s(%p, %p, %p, %d, %p)", __func__, session, msg, auth, retrying, data);
205 SmlTransportHttpClientEnv *env = data;
206 smlTrace(TRACE_INTERNAL, "%s: authentication via auth_type %s", __func__, VA_STRING(soup_auth_get_scheme_name(auth)));
207
208 if (!retrying)
209 soup_auth_authenticate(auth, env->username, env->password);
210 }
211 #endif
212
213 static SmlBool smlTransportHttpClientSetConfigOption(
214 SmlTransport *tsp,
215 const char *name,
216 const char *value,
217 SmlError **error)
218 {
219 smlTrace(TRACE_ENTRY, "%s(%p, %s, %s, %p)", __func__, tsp, VA_STRING(name), VA_STRING(value), error);
220 CHECK_ERROR_REF
221 smlAssert(tsp);
222 smlAssert(tsp->transport_data);
223 SmlTransportHttpClientEnv *env = tsp->transport_data;
224
225 if (!strcmp(name, SML_TRANSPORT_CONFIG_URL)) {
226 if (env->url)
227 smlSafeCFree(&(env->url));
228 if (env->uri)
229 soup_uri_free(env->uri);
230 env->url = g_strdup(value);
231 env->uri = soup_uri_new(env->url);
232 if (env->uri == NULL) {
233 smlErrorSet(error, SML_ERROR_INTERNAL_MISCONFIGURATION,
234 "The configured url %s is wrong.", env->url);
235 smlTrace(TRACE_EXIT_ERROR, "%s - %s", __func__, smlErrorPrint(error));
236 return FALSE;
237 }
238 smlTrace(TRACE_INTERNAL, "%s: URL %s detected", __func__, VA_STRING(env->url));
239 } else if (!strcmp(name, SML_TRANSPORT_CONFIG_PROXY)) {
240 env->proxy = g_strdup(value);
241 smlTrace(TRACE_INTERNAL, "%s: PROXY %s detected", __func__, VA_STRING(env->proxy));
242 } else if (!strcmp(name, SML_TRANSPORT_CONFIG_USERNAME)) {
243 env->username = g_strdup(value);
244 smlTrace(TRACE_INTERNAL, "%s: USERNAME %s detected", __func__, VA_STRING(env->username));
245 } else if (!strcmp(name, SML_TRANSPORT_CONFIG_PASSWORD)) {
246 env->password = g_strdup(value);
247 smlTrace(TRACE_INTERNAL, "%s: PASSWORD ******* detected", __func__);
248 } else if (!strcmp(name, SML_TRANSPORT_CONFIG_SSL_CA_FILE)) {
249 env->cafile = g_strdup(value);
250 smlTrace(TRACE_INTERNAL, "%s: SSL_CA_FILE %s detected", __func__, VA_STRING(env->cafile));
251 } else {
252 smlErrorSet(error, SML_ERROR_INTERNAL_MISCONFIGURATION, "Unknown parameter %s found.", name);
253 smlTrace(TRACE_EXIT_ERROR, "%s - %s", __func__, smlErrorPrint(error));
254 return FALSE;
255 }
256
257 smlTrace(TRACE_EXIT, "%s", __func__);
258 return TRUE;
259 }
260
261 /* WARNING: Do not register this function as connect function.
262 * This is only an internal connect function.
263 */
264 static void smlTransportHttpClientConnect(void *data)
265 {
266 smlTrace(TRACE_ENTRY, "%s(%p)", __func__, data);
267 smlAssert(data);
268 SmlTransportHttpClientEnv *env = data;
269 SmlError *error = NULL;
270
271 if (!env->tsp->context)
272 {
273 smlErrorSet(&error, SML_ERROR_INTERNAL_MISCONFIGURATION,
274 "HTTP client runs only in asynchronous mode. So the context must be set.");
275 goto error;
276 }
277
278 env->connectDone = FALSE;
279 env->disconnectDone = FALSE;
280
281 if (env->proxy != NULL && strlen(env->proxy) > 0)
282 {
283 if (env->cafile != NULL && strlen(env->cafile))
284 {
285 #ifdef HAVE_LIBSOUP22_SOLARIS
286 env->session = soup_session_sync_new_with_options(
287 #else
288 env->session = soup_session_async_new_with_options(
289 SOUP_SESSION_ASYNC_CONTEXT, env->tsp->context,
290 #endif
291 SOUP_SESSION_PROXY_URI, env->proxy,
292 SOUP_SESSION_SSL_CA_FILE, env->cafile,
293 NULL);
294 /* FIXME: libsoup does not support CRLs
295 * FIXME: CRL support is required for minimum security.
296 * FIXME: http://bugzilla.gnome.org/show_bug.cgi?id=547881
297 */
298 } else {
299 #ifdef HAVE_LIBSOUP22_SOLARIS
300 env->session = soup_session_sync_new_with_options(
301 #else
302 env->session = soup_session_async_new_with_options(
303 SOUP_SESSION_ASYNC_CONTEXT, env->tsp->context,
304 #endif
305 SOUP_SESSION_PROXY_URI, env->proxy,
306 NULL);
307 }
308 } else {
309 if (env->cafile != NULL && strlen(env->cafile))
310 {
311 #ifdef HAVE_LIBSOUP22_SOLARIS
312 env->session = soup_session_sync_new_with_options(
313 #else
314 env->session = soup_session_async_new_with_options(
315 SOUP_SESSION_ASYNC_CONTEXT, env->tsp->context,
316 #endif
317 SOUP_SESSION_SSL_CA_FILE, env->cafile,
318 NULL);
319 } else {
320 #ifdef HAVE_LIBSOUP22_SOLARIS
321 env->session = soup_session_sync_new();
322 #else
323 env->session = soup_session_async_new_with_options(
324 SOUP_SESSION_ASYNC_CONTEXT, env->tsp->context,
325 NULL);
326 #endif
327 }
328 }
329 if (!env->session) {
330 smlErrorSet(&error, SML_ERROR_GENERIC, "Unable to create new session");
331 goto error;
332 }
333
334 /* add credentials to the environment and
335 * enable authentication callback for http authentication
336 */
337 if (env->username || env->password)
338 {
339 g_signal_connect (env->session, "authenticate", G_CALLBACK (_authenticate), env);
340 } else {
341 env->username = NULL;
342 env->password = NULL;
343 }
344
345 smlTrace(TRACE_EXIT, "%s: %p", __func__, env);
346 return;
347 error:
348 smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(&error));
349 smlTransportReceiveEvent(env->tsp, NULL, SML_TRANSPORT_EVENT_ERROR, NULL, error);
350 }
351
352 static SmlBool smlTransportHttpClientSetResponseURI(
353 SmlTransport *tsp,
354 const char *uri,
355 SmlError **error)
356 {
357 smlTrace(TRACE_ENTRY, "%s(%p, %s, %p)", __func__, tsp, VA_STRING(uri), error);
358 CHECK_ERROR_REF
359 smlAssert(tsp);
360 smlAssert(tsp->transport_data);
361 SmlTransportHttpClientEnv *env = tsp->transport_data;
362 smlAssert(uri);
363
364 if (env->url)
365 smlSafeCFree(&(env->url));
366 if (env->uri)
367 soup_uri_free(env->uri);
368
369 env->url = g_strdup(uri);
370 env->uri = soup_uri_new(env->url);
371 if (env->uri == NULL) {
372 smlErrorSet(error, SML_ERROR_INTERNAL_MISCONFIGURATION,
373 "The specified url \"%s\" is wrong.", env->url);
374 smlTrace(TRACE_EXIT_ERROR, "%s - %s", __func__, smlErrorPrint(error));
375 return FALSE;
376 }
377
378 smlTrace(TRACE_EXIT, "%s", __func__);
379 return TRUE;
380 }
381
382 static SmlBool smlTransportHttpClientCleanupSession(
383 gpointer data,
384 SmlError **error)
385 {
386 smlTrace(TRACE_ENTRY, "%s", __func__);
387 CHECK_ERROR_REF
388 smlAssert(data);
389
390 SoupSession *session = data;
391
392 soup_session_abort(session);
393 g_object_unref(session);
394
395 smlTrace(TRACE_EXIT, "%s", __func__);
396 return TRUE;
397 }
398
399 static SmlBool smlTransportHttpClientFinalize(void *data, SmlError **error)
400 {
401 smlTrace(TRACE_ENTRY, "%s(%p, %p)", __func__, data, error);
402 CHECK_ERROR_REF
403 smlAssert(data);
404 SmlTransportHttpClientEnv *env = data;
405
406 smlAssert(env->tsp);
407
408 if (env->session)
409 {
410 if (!smlThreadCallFunction(env->tsp->thread, smlTransportHttpClientCleanupSession, env->session, error))
411 goto error;
412 env->session = NULL;
413 }
414
415 if (env->uri)
416 soup_uri_free(env->uri);
417
418 if (env->url)
419 smlSafeCFree(&(env->url));
420
421 if (env->proxy)
422 smlSafeCFree(&(env->proxy));
423
424 smlSafeFree((gpointer *)&env);
425
426 smlTrace(TRACE_EXIT, "%s", __func__);
427 return TRUE;
428 error:
429 smlTrace(TRACE_EXIT_ERROR, "%s - %s", __func__, smlErrorPrint(error));
430 return FALSE;
431 }
432
433 static void smlTransportHttpClientSend(void *userdata, void *link_, SmlTransportData *data, SmlError *error)
434 {
435 smlTrace(TRACE_ENTRY, "%s(%p, %p, %p, %p)", __func__, userdata, link_, data, error);
436 smlAssert(error || data);
437 smlAssert(userdata);
438 SmlTransportHttpClientEnv *env = userdata;
439 smlAssert(env);
440 smlAssert(env->uri);
441
442 if (error)
443 goto error;
444
445 if (!env->connectDone)
446 smlTransportHttpClientConnect(userdata);
447
448 SoupMessage *msg = soup_message_new_from_uri(SOUP_METHOD_POST, env->uri);
449 if (!msg) {
450 smlErrorSet(&error, SML_ERROR_GENERIC, "unknown libsoup error during message_new");
451 goto error;
452 }
453
454 const char *content_type;
455 switch (data->type) {
456 case SML_MIMETYPE_XML:
457 content_type = SML_ELEMENT_XML;
458 break;
459 case SML_MIMETYPE_WBXML:
460 content_type = SML_ELEMENT_WBXML;
461 break;
462 case SML_MIMETYPE_SAN:
463 content_type = SML_ELEMENT_SAN;
464 break;
465 default:
466 smlErrorSet(&error, SML_ERROR_GENERIC, "Unknown Mimetype %d", data->type);
467 goto error_free_message;
468 }
469
470 soup_message_headers_append(msg->request_headers, "Accept", content_type);
471 soup_message_set_request (msg, content_type,
472 #ifdef HAVE_LIBSOUP22
473 SOUP_BUFFER_SYSTEM_OWNED,
474 #else
475 SOUP_MEMORY_TAKE,
476 #endif
477 g_memdup(data->data, data->size), data->size);
478
479 #ifdef HAVE_LIBSOUP22
480 smlTrace(TRACE_INTERNAL, "%s: data length: %i", __func__, msg->request.length);
481
482 #else
483 smlTrace(TRACE_INTERNAL, "%s: data length: %i", __func__, msg->request_body->length);
484 #endif
485
486 #ifdef HAVE_LIBSOUP22_SOLARIS
487 soup_session_send_message(env->session, msg);
488 smlTrace(TRACE_INTERNAL, "%s: sent message and starting callback", __func__);
489 smlTransportHttpClientCallback(msg, userdata);
490 g_object_unref(msg); /* synchronous mode !!! */
491 #else
492 soup_session_queue_message(env->session, msg, smlTransportHttpClientCallback, userdata);
493 #endif
494
495 smlTrace(TRACE_EXIT, "%s", __func__);
496 return;
497
498 error_free_message:
499 g_object_unref(msg);
500 error:
501 smlTransportReceiveEvent(env->tsp, NULL, SML_TRANSPORT_EVENT_ERROR, NULL, error);
502 smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(&error));
503 smlErrorDeref(&error);
504 }
505
506 static void smlTransportHttpClientDisconnect(void *data, void *linkdata)
507 {
508 smlTrace(TRACE_ENTRY, "%s(%p, %p)", __func__, data, linkdata);
509 smlAssert(data);
510 SmlTransportHttpClientEnv *env = data;
511 smlAssert(env);
512 smlAssert(env->tsp);
513
514 if (!env->connectDone && !env->session)
515 {
516 /* The client is not connected. */
517 env->disconnectDone = TRUE;
518 smlTransportReceiveEvent(env->tsp, NULL, SML_TRANSPORT_EVENT_DISCONNECT_DONE, NULL, NULL);
519 smlTrace(TRACE_EXIT, "%s - ignoring because never connected", __func__);
520 return;
521 }
522
523 smlAssert(env->session);
524
525 /* The only reason to implement a disconnect function is the fact
526 * that some servers need a TCP/IP disconnect to cleanly finish
527 * a SyncML session.
528 *
529 * Otherwise we are locked into an endless loop of Slow-Syncs.
530 */
531
532 soup_session_abort(env->session);
533 g_object_unref(env->session);
534 env->session = NULL;
535
536 /* Make a new disconnect object */
537 env->disconnectDone = FALSE;
538 smlTransportReceiveEvent(env->tsp, NULL, SML_TRANSPORT_EVENT_DISCONNECT_DONE, NULL, NULL);
539
540 smlTrace(TRACE_EXIT, "%s", __func__);
541 }
542
543 SmlBool smlTransportHttpClientNew(SmlTransport *tsp, SmlError **error)
544 {
545 CHECK_ERROR_REF
546 smlAssert(tsp);
547
548 #ifdef HAVE_LIBSOUP22_SOLARIS
549 smlTrace(TRACE_INTERNAL, "%s: Enabled libsoup 2.2 Solaris workarounds", __func__);
550 #endif
551
552 tsp->functions.set_config_option = smlTransportHttpClientSetConfigOption;
553 tsp->functions.set_response_uri = smlTransportHttpClientSetResponseURI;
554 tsp->functions.disconnect = smlTransportHttpClientDisconnect;
555 tsp->functions.finalize = smlTransportHttpClientFinalize;
556 tsp->functions.send = smlTransportHttpClientSend;
557
558 SmlTransportHttpClientEnv *env = smlTryMalloc0(sizeof(SmlTransportHttpClientEnv), error);
559 if (!env)
560 return FALSE;
561 tsp->transport_data = env;
562 env->tsp = tsp;
563
564 return TRUE;
565 }
566