http_client.c
/*
* libsyncml - A syncml protocol implementation
* Copyright (C) 2005 Armin Bauer <armin.bauer@opensync.org>
* Copyright (C) 2007-2009 Michael Bell <michael.bell@opensync.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <libsyncml/syncml.h>
#include <libsyncml/syncml_internals.h>
#include <libsyncml/sml_error_internals.h>
#include <libsyncml/sml_transport_internals.h>
#include "http_client_internals.h"
static void smlTransportHttpClientSend(void *userdata, void *link, SmlTransportData *data, SmlError *error);
static void smlTransportHttpClientDisconnect(void *data, void *linkdata);
static SmlBool smlTransportHttpClientFinalize(void *data, SmlError **error);
#ifdef HAVE_LIBSOUP22
#define soup_message_headers_get soup_message_get_header
#define soup_message_headers_append soup_message_add_header
#endif
#ifdef HAVE_LIBSOUP22
static void smlTransportHttpClientCallback(SoupMessage *msg, gpointer userdata)
{
smlTrace(TRACE_ENTRY, "%s(%p, %p)", __func__, msg, userdata);
#else
static void smlTransportHttpClientCallback(SoupSession *session, SoupMessage *msg, gpointer userdata)
{
/* session is only traced to avoid warnings from gcc profiling */
smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, session, msg, userdata);
#endif
SmlTransportHttpClientEnv *env = userdata;
SmlError *error = NULL;
/* handle potential session shutdown */
if (env->connectDone && msg->status_code == SOUP_STATUS_CANCELLED)
{
/* FIXME: This is clearly a disconnect.
* FIXME: You must check if a disconnect is ongoing.
*/
smlTrace(TRACE_EXIT, "%s - ignoring due to shutdown", __func__);
return;
}
/* handle a fresh connection */
if (!env->connectDone)
{
/* This is the first answer or any other event which is
* detected after the first message was sent.
* We have to check and signal if the connection is ok or not.
*/
if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code))
{
smlErrorSet(&error,
SML_ERROR_GENERIC,
"Connection failed (%d) - %s", msg->status_code, msg->reason_phrase);
goto error;
}
else
{
/* If a transport has no public connect function
* then the connect event is sent by the
* transport layer itself.
*/
smlTrace(TRACE_INTERNAL, "%s: Connect done", __func__);
if (!env->tsp->connected)
{
/* If a user of the library know that connect
* is not available then this is tolerated.
*/
smlTransportReceiveEvent(
env->tsp, NULL,
SML_TRANSPORT_EVENT_CONNECT_DONE,
NULL, NULL);
}
}
env->connectDone = TRUE;
}
smlTrace(TRACE_INTERNAL, "%s: Result: %d %s",
__func__, msg->status_code, msg->reason_phrase);
/* start http handling with some checks */
/* check the library status for errors */
if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) {
smlErrorSet(&error, SML_ERROR_GENERIC, "Call not successfull: %d %s",
msg->status_code, msg->reason_phrase);
goto error;
}
/* check the header of the received message */
const char *header = soup_message_headers_get(msg->response_headers, "Content-Type");
smlTrace(TRACE_INTERNAL, "%s: content type ::= %s",
__func__, header);
SmlMimeType mimetype = SML_MIMETYPE_UNKNOWN;
if (header && !g_strncasecmp(header, SML_ELEMENT_XML, strlen(SML_ELEMENT_XML)))
mimetype = SML_MIMETYPE_XML;
else if(header && !g_strncasecmp(header, SML_ELEMENT_WBXML, strlen(SML_ELEMENT_WBXML)))
mimetype = SML_MIMETYPE_WBXML;
else if(header && !g_strncasecmp(header, SML_ELEMENT_SAN, strlen(SML_ELEMENT_SAN)))
mimetype = SML_MIMETYPE_SAN;
else if (header) {
smlErrorSet(&error, SML_ERROR_GENERIC, "Unknown mimetype");
goto error;
} else {
smlErrorSet(&error, SML_ERROR_GENERIC, "Faulty mimetype");
goto error;
}
/* prepare the received message */
char *data;
gsize length;
#ifdef HAVE_LIBSOUP22
length = msg->response.length;
#else
length = msg->response_body->length;
#endif
smlTrace(TRACE_INTERNAL, "%s: The message length is %i.", __func__, length);
/* We need one byte more than the response length
* because the data can be a native XML message.
* If the data is a native XML message then it is
* sometimes directly used as string.
*
* The string is automatically NULL terminated
* because smlTryMalloc0 fills the new memory with NULLs.
*/
data = smlTryMalloc0(length + 1, &error);
if (!data)
goto error;
#ifdef HAVE_LIBSOUP22
memcpy(data, msg->response.body, length);
#else
memcpy(data, msg->response_body->data, length);
#endif
SmlTransportData *tspdata = smlTransportDataNew(data, length, mimetype, TRUE, &error);
data = NULL;
if (!tspdata)
goto error_free_data;
/* signal the received message */
smlTransportReceiveEvent(env->tsp, NULL, SML_TRANSPORT_EVENT_DATA, tspdata, NULL);
/* cleanup */
smlTransportDataDeref(tspdata);
smlTrace(TRACE_EXIT, "%s", __func__);
return;
error_free_data:
smlSafeCFree(&data);
error:
smlTransportReceiveEvent(env->tsp, NULL, SML_TRANSPORT_EVENT_ERROR, NULL, error);
smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(&error));
smlErrorDeref(&error);
}
/* This is the authentication callback for libsoup.
* If a SyncML server uses HTTP then the specification allows
* standard HTTP authentication. This callback provides the
* http library with the user and passphrase for the SyncML
* account. Usually this is not required because the most
* SyncML server manage the authentication via SyncHdr.
*/
#ifdef HAVE_LIBSOUP22
static void
_authenticate (SoupSession *session, SoupMessage *msg,
const char *auth_type, const char *auth_realm,
char **username, char **password, gpointer data)
{
SmlTransportHttpClientEnv *env = data;
smlTrace(TRACE_INTERNAL, "%s: authentication via auth_type %s", __func__, VA_STRING(auth_type));
*username = g_strdup(env->username);
*password = g_strdup(env->password);
}
#else
static void
_authenticate (SoupSession *session, SoupMessage *msg,
SoupAuth *auth, gboolean retrying, gpointer data)
{
smlTrace(TRACE_INTERNAL, "%s(%p, %p, %p, %d, %p)", __func__, session, msg, auth, retrying, data);
SmlTransportHttpClientEnv *env = data;
smlTrace(TRACE_INTERNAL, "%s: authentication via auth_type %s", __func__, VA_STRING(soup_auth_get_scheme_name(auth)));
if (!retrying)
soup_auth_authenticate(auth, env->username, env->password);
}
#endif
static SmlBool smlTransportHttpClientSetConfigOption(
SmlTransport *tsp,
const char *name,
const char *value,
SmlError **error)
{
smlTrace(TRACE_ENTRY, "%s(%p, %s, %s, %p)", __func__, tsp, VA_STRING(name), VA_STRING(value), error);
CHECK_ERROR_REF
smlAssert(tsp);
smlAssert(tsp->transport_data);
SmlTransportHttpClientEnv *env = tsp->transport_data;
if (!strcmp(name, SML_TRANSPORT_CONFIG_URL)) {
if (env->url)
smlSafeCFree(&(env->url));
if (env->uri)
soup_uri_free(env->uri);
env->url = g_strdup(value);
env->uri = soup_uri_new(env->url);
if (env->uri == NULL) {
smlErrorSet(error, SML_ERROR_INTERNAL_MISCONFIGURATION,
"The configured url %s is wrong.", env->url);
smlTrace(TRACE_EXIT_ERROR, "%s - %s", __func__, smlErrorPrint(error));
return FALSE;
}
smlTrace(TRACE_INTERNAL, "%s: URL %s detected", __func__, VA_STRING(env->url));
} else if (!strcmp(name, SML_TRANSPORT_CONFIG_PROXY)) {
env->proxy = g_strdup(value);
smlTrace(TRACE_INTERNAL, "%s: PROXY %s detected", __func__, VA_STRING(env->proxy));
} else if (!strcmp(name, SML_TRANSPORT_CONFIG_USERNAME)) {
env->username = g_strdup(value);
smlTrace(TRACE_INTERNAL, "%s: USERNAME %s detected", __func__, VA_STRING(env->username));
} else if (!strcmp(name, SML_TRANSPORT_CONFIG_PASSWORD)) {
env->password = g_strdup(value);
smlTrace(TRACE_INTERNAL, "%s: PASSWORD ******* detected", __func__);
} else if (!strcmp(name, SML_TRANSPORT_CONFIG_SSL_CA_FILE)) {
env->cafile = g_strdup(value);
smlTrace(TRACE_INTERNAL, "%s: SSL_CA_FILE %s detected", __func__, VA_STRING(env->cafile));
} else {
smlErrorSet(error, SML_ERROR_INTERNAL_MISCONFIGURATION, "Unknown parameter %s found.", name);
smlTrace(TRACE_EXIT_ERROR, "%s - %s", __func__, smlErrorPrint(error));
return FALSE;
}
smlTrace(TRACE_EXIT, "%s", __func__);
return TRUE;
}
/* WARNING: Do not register this function as connect function.
* This is only an internal connect function.
*/
static void smlTransportHttpClientConnect(void *data)
{
smlTrace(TRACE_ENTRY, "%s(%p)", __func__, data);
smlAssert(data);
SmlTransportHttpClientEnv *env = data;
SmlError *error = NULL;
if (!env->tsp->context)
{
smlErrorSet(&error, SML_ERROR_INTERNAL_MISCONFIGURATION,
"HTTP client runs only in asynchronous mode. So the context must be set.");
goto error;
}
env->connectDone = FALSE;
env->disconnectDone = FALSE;
if (env->proxy != NULL && strlen(env->proxy) > 0)
{
if (env->cafile != NULL && strlen(env->cafile))
{
#ifdef HAVE_LIBSOUP22_SOLARIS
env->session = soup_session_sync_new_with_options(
#else
env->session = soup_session_async_new_with_options(
SOUP_SESSION_ASYNC_CONTEXT, env->tsp->context,
#endif
SOUP_SESSION_PROXY_URI, env->proxy,
SOUP_SESSION_SSL_CA_FILE, env->cafile,
NULL);
/* FIXME: libsoup does not support CRLs
* FIXME: CRL support is required for minimum security.
* FIXME: http://bugzilla.gnome.org/show_bug.cgi?id=547881
*/
} else {
#ifdef HAVE_LIBSOUP22_SOLARIS
env->session = soup_session_sync_new_with_options(
#else
env->session = soup_session_async_new_with_options(
SOUP_SESSION_ASYNC_CONTEXT, env->tsp->context,
#endif
SOUP_SESSION_PROXY_URI, env->proxy,
NULL);
}
} else {
if (env->cafile != NULL && strlen(env->cafile))
{
#ifdef HAVE_LIBSOUP22_SOLARIS
env->session = soup_session_sync_new_with_options(
#else
env->session = soup_session_async_new_with_options(
SOUP_SESSION_ASYNC_CONTEXT, env->tsp->context,
#endif
SOUP_SESSION_SSL_CA_FILE, env->cafile,
NULL);
} else {
#ifdef HAVE_LIBSOUP22_SOLARIS
env->session = soup_session_sync_new();
#else
env->session = soup_session_async_new_with_options(
SOUP_SESSION_ASYNC_CONTEXT, env->tsp->context,
NULL);
#endif
}
}
if (!env->session) {
smlErrorSet(&error, SML_ERROR_GENERIC, "Unable to create new session");
goto error;
}
/* add credentials to the environment and
* enable authentication callback for http authentication
*/
if (env->username || env->password)
{
g_signal_connect (env->session, "authenticate", G_CALLBACK (_authenticate), env);
} else {
env->username = NULL;
env->password = NULL;
}
smlTrace(TRACE_EXIT, "%s: %p", __func__, env);
return;
error:
smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(&error));
smlTransportReceiveEvent(env->tsp, NULL, SML_TRANSPORT_EVENT_ERROR, NULL, error);
}
static SmlBool smlTransportHttpClientSetResponseURI(
SmlTransport *tsp,
const char *uri,
SmlError **error)
{
smlTrace(TRACE_ENTRY, "%s(%p, %s, %p)", __func__, tsp, VA_STRING(uri), error);
CHECK_ERROR_REF
smlAssert(tsp);
smlAssert(tsp->transport_data);
SmlTransportHttpClientEnv *env = tsp->transport_data;
smlAssert(uri);
if (env->url)
smlSafeCFree(&(env->url));
if (env->uri)
soup_uri_free(env->uri);
env->url = g_strdup(uri);
env->uri = soup_uri_new(env->url);
if (env->uri == NULL) {
smlErrorSet(error, SML_ERROR_INTERNAL_MISCONFIGURATION,
"The specified url \"%s\" is wrong.", env->url);
smlTrace(TRACE_EXIT_ERROR, "%s - %s", __func__, smlErrorPrint(error));
return FALSE;
}
smlTrace(TRACE_EXIT, "%s", __func__);
return TRUE;
}
static SmlBool smlTransportHttpClientCleanupSession(
gpointer data,
SmlError **error)
{
smlTrace(TRACE_ENTRY, "%s", __func__);
CHECK_ERROR_REF
smlAssert(data);
SoupSession *session = data;
soup_session_abort(session);
g_object_unref(session);
smlTrace(TRACE_EXIT, "%s", __func__);
return TRUE;
}
static SmlBool smlTransportHttpClientFinalize(void *data, SmlError **error)
{
smlTrace(TRACE_ENTRY, "%s(%p, %p)", __func__, data, error);
CHECK_ERROR_REF
smlAssert(data);
SmlTransportHttpClientEnv *env = data;
smlAssert(env->tsp);
if (env->session)
{
if (!smlThreadCallFunction(env->tsp->thread, smlTransportHttpClientCleanupSession, env->session, error))
goto error;
env->session = NULL;
}
if (env->uri)
soup_uri_free(env->uri);
if (env->url)
smlSafeCFree(&(env->url));
if (env->proxy)
smlSafeCFree(&(env->proxy));
smlSafeFree((gpointer *)&env);
smlTrace(TRACE_EXIT, "%s", __func__);
return TRUE;
error:
smlTrace(TRACE_EXIT_ERROR, "%s - %s", __func__, smlErrorPrint(error));
return FALSE;
}
static void smlTransportHttpClientSend(void *userdata, void *link_, SmlTransportData *data, SmlError *error)
{
smlTrace(TRACE_ENTRY, "%s(%p, %p, %p, %p)", __func__, userdata, link_, data, error);
smlAssert(error || data);
smlAssert(userdata);
SmlTransportHttpClientEnv *env = userdata;
smlAssert(env);
smlAssert(env->uri);
if (error)
goto error;
if (!env->connectDone)
smlTransportHttpClientConnect(userdata);
SoupMessage *msg = soup_message_new_from_uri(SOUP_METHOD_POST, env->uri);
if (!msg) {
smlErrorSet(&error, SML_ERROR_GENERIC, "unknown libsoup error during message_new");
goto error;
}
const char *content_type;
switch (data->type) {
case SML_MIMETYPE_XML:
content_type = SML_ELEMENT_XML;
break;
case SML_MIMETYPE_WBXML:
content_type = SML_ELEMENT_WBXML;
break;
case SML_MIMETYPE_SAN:
content_type = SML_ELEMENT_SAN;
break;
default:
smlErrorSet(&error, SML_ERROR_GENERIC, "Unknown Mimetype %d", data->type);
goto error_free_message;
}
soup_message_headers_append(msg->request_headers, "Accept", content_type);
soup_message_set_request (msg, content_type,
#ifdef HAVE_LIBSOUP22
SOUP_BUFFER_SYSTEM_OWNED,
#else
SOUP_MEMORY_TAKE,
#endif
g_memdup(data->data, data->size), data->size);
#ifdef HAVE_LIBSOUP22
smlTrace(TRACE_INTERNAL, "%s: data length: %i", __func__, msg->request.length);
#else
smlTrace(TRACE_INTERNAL, "%s: data length: %i", __func__, msg->request_body->length);
#endif
#ifdef HAVE_LIBSOUP22_SOLARIS
soup_session_send_message(env->session, msg);
smlTrace(TRACE_INTERNAL, "%s: sent message and starting callback", __func__);
smlTransportHttpClientCallback(msg, userdata);
g_object_unref(msg); /* synchronous mode !!! */
#else
soup_session_queue_message(env->session, msg, smlTransportHttpClientCallback, userdata);
#endif
smlTrace(TRACE_EXIT, "%s", __func__);
return;
error_free_message:
g_object_unref(msg);
error:
smlTransportReceiveEvent(env->tsp, NULL, SML_TRANSPORT_EVENT_ERROR, NULL, error);
smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(&error));
smlErrorDeref(&error);
}
static void smlTransportHttpClientDisconnect(void *data, void *linkdata)
{
smlTrace(TRACE_ENTRY, "%s(%p, %p)", __func__, data, linkdata);
smlAssert(data);
SmlTransportHttpClientEnv *env = data;
smlAssert(env);
smlAssert(env->tsp);
if (!env->connectDone && !env->session)
{
/* The client is not connected. */
env->disconnectDone = TRUE;
smlTransportReceiveEvent(env->tsp, NULL, SML_TRANSPORT_EVENT_DISCONNECT_DONE, NULL, NULL);
smlTrace(TRACE_EXIT, "%s - ignoring because never connected", __func__);
return;
}
smlAssert(env->session);
/* The only reason to implement a disconnect function is the fact
* that some servers need a TCP/IP disconnect to cleanly finish
* a SyncML session.
*
* Otherwise we are locked into an endless loop of Slow-Syncs.
*/
soup_session_abort(env->session);
g_object_unref(env->session);
env->session = NULL;
/* Make a new disconnect object */
env->disconnectDone = FALSE;
smlTransportReceiveEvent(env->tsp, NULL, SML_TRANSPORT_EVENT_DISCONNECT_DONE, NULL, NULL);
smlTrace(TRACE_EXIT, "%s", __func__);
}
SmlBool smlTransportHttpClientNew(SmlTransport *tsp, SmlError **error)
{
CHECK_ERROR_REF
smlAssert(tsp);
#ifdef HAVE_LIBSOUP22_SOLARIS
smlTrace(TRACE_INTERNAL, "%s: Enabled libsoup 2.2 Solaris workarounds", __func__);
#endif
tsp->functions.set_config_option = smlTransportHttpClientSetConfigOption;
tsp->functions.set_response_uri = smlTransportHttpClientSetResponseURI;
tsp->functions.disconnect = smlTransportHttpClientDisconnect;
tsp->functions.finalize = smlTransportHttpClientFinalize;
tsp->functions.send = smlTransportHttpClientSend;
SmlTransportHttpClientEnv *env = smlTryMalloc0(sizeof(SmlTransportHttpClientEnv), error);
if (!env)
return FALSE;
tsp->transport_data = env;
env->tsp = tsp;
return TRUE;
}
smlErrorPrint
const char * smlErrorPrint(SmlError **error)
Returns the message of the error.
Definition: sml_error.c:299
smlThreadCallFunction
SmlBool smlThreadCallFunction(SmlThread *thread, SmlThreadCallFunctionType func, gpointer data, SmlError **error)
Definition: sml_support.c:561
SmlTransport
Definition: sml_transport_internals.h:69
SmlTransportData
Definition: sml_transport_internals.h:112
SmlTransportHttpClientEnv
Definition: http_client_internals.h:36
smlTrace
void smlTrace(SmlTraceType type, const char *message,...)
Used for tracing the application.
Definition: sml_support.c:120
smlTryMalloc0
void * smlTryMalloc0(long n_bytes, SmlError **error)
Safely mallocs.
Definition: sml_support.c:335
smlErrorSet
void smlErrorSet(SmlError **error, SmlErrorType type, const char *format,...)
Sets the error.
Definition: sml_error.c:355
SmlError
Represent an error.
Definition: sml_error_internals.h:29