libsyncml  0.5.4
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 
559  if (!env)
560  return FALSE;
561  tsp->transport_data = env;
562  env->tsp = tsp;
563 
564  return TRUE;
565 }
566 
const char * smlErrorPrint(SmlError **error)
Returns the message of the error.
Definition: sml_error.c:299
SmlBool smlThreadCallFunction(SmlThread *thread, SmlThreadCallFunctionType func, gpointer data, SmlError **error)
Definition: sml_support.c:561
void smlTrace(SmlTraceType type, const char *message,...)
Used for tracing the application.
Definition: sml_support.c:120
void * smlTryMalloc0(long n_bytes, SmlError **error)
Safely mallocs.
Definition: sml_support.c:335
void smlErrorSet(SmlError **error, SmlErrorType type, const char *format,...)
Sets the error.
Definition: sml_error.c:355
Represent an error.