/*
 * 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 <config.h>
 
#include <glib.h>
#include <libsyncml/sml_support.h>
#include <libsyncml/sml_error_internals.h>
#include <libsyncml/sml_transport_internals.h>
#include "../data_sync_api/sml_data_sync_defines.h"

#include "http_server_internals.h"
#include <string.h>

#ifdef HAVE_LIBSOUP22
static void smlTransportHttpServerCallback(
		SoupServerContext *context,
		SoupMessage *msg,
		gpointer data)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, context, msg, data);
	SmlMimeType mimetype = SML_MIMETYPE_UNKNOWN;
	smlAssert(data);
	SmlTransportHttpServerEnv *env = data;
	SmlLinkHttpServerEnv *linkenv = NULL;
	SmlLink *link_ = NULL;
	GError *error = NULL;
	
	char *path = soup_uri_to_string (soup_message_get_uri(msg), TRUE);
	smlTrace(TRACE_INTERNAL, "%s: %s %s HTTP/1.%d",
		__func__, VA_STRING(msg->method), VA_STRING(path), soup_message_get_http_version(msg));

	if (g_strcasecmp(path, env->url)) {
		/* check that this is a registered session */
		smlSafeCFree(&path);
		path = soup_uri_to_string (soup_message_get_uri(msg), FALSE);
		link_ = g_hash_table_lookup(env->uriToLink, path);
		if (!link_)
		{
			/* The URL was changed and there is no
			 * key/value pair for this URL available.
			 *
			 * SECURITY: This is a potential attack.
			 * FALSE POSITIVE: Session with a timeout.
			 */	
			g_set_error(&error, SML_ERROR, SML_ERROR_INTERNAL_FILE_NOT_FOUND,
				"Not Found (%s).", path);
			soup_message_set_status(msg, SOUP_STATUS_NOT_FOUND);
			smlSafeCFree(&path);
			goto error;
		}
		smlLinkRef(link_);
	}
	smlSafeCFree(&path);

	if (soup_message_get_http_version(msg) != 1) {
		g_set_error(&error, SML_ERROR, SML_ERROR_NOT_IMPLEMENTED, "Wrong http version");
		soup_message_set_status(msg, SOUP_STATUS_NOT_IMPLEMENTED);
		goto error;
	}

	if (soup_method_get_id(msg->method) != SOUP_METHOD_ID_POST) {
		g_set_error(&error, SML_ERROR, SML_ERROR_NOT_IMPLEMENTED, "Wrong method");
		soup_message_set_status(msg, SOUP_STATUS_NOT_IMPLEMENTED);
		goto error;
	}
	
	const char *header = soup_message_get_header(msg->request_headers, "Content-Type");
	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) {
		g_set_error(&error, SML_ERROR, SML_ERROR_GENERIC, "Unknown mimetype");
		soup_message_set_status (msg, SOUP_STATUS_BAD_REQUEST);
		goto error;
	} else {
		g_set_error(&error, SML_ERROR, SML_ERROR_GENERIC, "Faulty mimetype");
		soup_message_set_status (msg, SOUP_STATUS_BAD_REQUEST);
		goto error;
	}

	/* prepare link and linkenv */
	if (!link_)
	{
		/* This is a potentially new session.
		 * It is not possible to check this here.
		 * smlTransportGetResponseURI must be used
		 * to verify this.
		 */
		linkenv = smlTryMalloc0(sizeof(SmlLinkHttpServerEnv), &error);
		if (!linkenv)
			goto error;
		linkenv->env = env;
		link_ = smlLinkNew(env->tsp, linkenv, &error);
		if (!link_)
			goto error;
		linkenv->msg = msg;
		/* the link must be cached for disconnect signaling */
		linkenv->link = link_;
		smlLinkRef(linkenv->link);
		/* We think positive here and signal connect.
	         * the link is now complete, so send the connect event
	         */
		smlTransportReceiveEvent(env->tsp, link_, SML_TRANSPORT_EVENT_CONNECT_DONE, NULL, NULL);

	} else {
		linkenv = link_->link_data;
		if (linkenv->msg)
			smlTrace(TRACE_INTERNAL,
				"%s: WARNING: ResponseURI should not be in use - test mode.",
				__func__);
		linkenv->msg = msg;
	}

	smlTrace(TRACE_INTERNAL, "%s: The message length is %i.", __func__, msg->request.length);

	/* We need one byte more than the request 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.
	 */
	char *body = smlTryMalloc0(msg->request.length + 1, &error);
	if (!body)
		goto error;
	memcpy(body, msg->request.body, msg->request.length);

	SmlTransportData *tspdata = smlTransportDataNew(body, msg->request.length, mimetype, TRUE, &error);
	body = NULL;
	if (!tspdata)
		goto error_unref_msg;
	
	smlTransportReceiveEvent(env->tsp, link_, SML_TRANSPORT_EVENT_DATA, tspdata, NULL);
	
	smlLinkDeref(link_);
	smlTransportDataDeref(tspdata);
	
	soup_message_io_pause(msg);
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return;

error_unref_msg:
	link_->link_data = NULL;
	smlLinkDeref(link_);
	linkenv->msg = NULL;
	smlSafeFree((gpointer *) &linkenv);
error:
	soup_server_message_set_encoding (SOUP_SERVER_MESSAGE (msg), SOUP_TRANSFER_CONTENT_LENGTH);
	soup_message_io_unpause(msg);
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, error->message);
	g_error_free(error);
}

#else /* !HAVE_LIBSOUP22 == libsoup 2.4 */

static void smlTransportHttpServerCallback(
		SoupServer        *server,
		SoupMessage       *msg, 
		const char        *path,
		GHashTable        *query,
		SoupClientContext *client,
		gpointer           data)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %s, %p, %p, %p)", __func__, server, msg, VA_STRING(path), query, client, data);
	SmlMimeType mimetype = SML_MIMETYPE_UNKNOWN;
	smlAssert(data);
	SmlTransportHttpServerEnv *env = data;
	SmlLinkHttpServerEnv *linkenv = NULL;
	SmlLink *link_ = NULL;
	GError *error = NULL;
	
	smlTrace(TRACE_INTERNAL, "%s: %s %s HTTP/1.%d",
		__func__, VA_STRING(msg->method), VA_STRING(path), soup_message_get_http_version(msg));
	
	if (msg->method != SOUP_METHOD_POST) {
		g_set_error(&error, SML_ERROR, SML_ERROR_NOT_IMPLEMENTED, "Wrong method");
		soup_message_set_status(msg, SOUP_STATUS_NOT_IMPLEMENTED);
		goto error;
	}
	
	if (g_strcasecmp(path, env->url)) {
		/* check that this is a registered session */
		char *fullpath = soup_uri_to_string (soup_message_get_uri(msg), FALSE);
		link_ = g_hash_table_lookup(env->uriToLink, fullpath);
		if (!link_)
		{
			/* The URL was changed and there is no
			 * key/value pair for this URL available.
			 *
			 * SECURITY: This is a potential attack.
			 * FALSE POSITIVE: Session with a timeout.
			 */	
			g_set_error(&error, SML_ERROR, SML_ERROR_INTERNAL_FILE_NOT_FOUND,
				"Not Found (%s).", fullpath);
			soup_message_set_status(msg, SOUP_STATUS_NOT_FOUND);
			smlSafeCFree(&fullpath);
			goto error;
		}
		smlLinkRef(link_);
		smlSafeCFree(&fullpath);
	}
	
	const char *header = soup_message_headers_get(msg->request_headers, "Content-Type");
	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) {
		g_set_error(&error, SML_ERROR, SML_ERROR_GENERIC, "Unknown mimetype");
		soup_message_set_status (msg, SOUP_STATUS_BAD_REQUEST);
		goto error;
	} else {
		g_set_error(&error, SML_ERROR, SML_ERROR_GENERIC, "Faulty mimetype");
		soup_message_set_status (msg, SOUP_STATUS_BAD_REQUEST);
		goto error;
	}

	/* prepare link and linkenv */
	if (!link_)
	{
		/* This is a potentially new session.
		 * It is not possible to check this here.
		 * smlTransportGetResponseURI must be used
		 * to verify this.
		 */
		linkenv = smlTryMalloc0(sizeof(SmlLinkHttpServerEnv), &error);
		if (!linkenv)
			goto error;
		linkenv->env = env;
		link_ = smlLinkNew(env->tsp, linkenv, &error);
		if (!link_)
			goto error;
		linkenv->msg = msg;
		/* the link must be cached for disconnect signaling */
		linkenv->link = link_;
		smlLinkRef(linkenv->link);
		/* We think positive here and signal connect.
	         * the link is now complete, so send the connect event
	         */
		smlTransportReceiveEvent(env->tsp, link_, SML_TRANSPORT_EVENT_CONNECT_DONE, NULL, NULL);
	} else {
		linkenv = link_->link_data;
		if (linkenv->msg)
			smlTrace(TRACE_INTERNAL,
				"%s: WARNING: ResponseURI should not be in use - test mode.",
				__func__);
		linkenv->msg = msg;
	}

	if (msg->request_body) {
		smlTrace(TRACE_INTERNAL, "%s: The message length is %i.",
			 __func__, msg->request_body->length);
	} else {
		smlTrace(TRACE_INTERNAL, "%s: The message has no request body.", __func__);
	}

	/* We need one byte more than the request 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.
	 */
	char *body = smlTryMalloc0(msg->request_body->length + 1, &error);
	if (!body)
		goto error;
	memcpy(body, msg->request_body->data, msg->request_body->length);

	SmlTransportData *tspdata = smlTransportDataNew(body, msg->request_body->length, mimetype, TRUE, &error);
	body = NULL;
	if (!tspdata)
		goto error_unref_msg;
	
	smlTransportReceiveEvent(env->tsp, link_, SML_TRANSPORT_EVENT_DATA, tspdata, NULL);
	smlLinkDeref(link_);
	
	/* If we activate this function call then we get segfaults */
	/* smlLinkDeref(link_); */
	smlTransportDataDeref(tspdata);
	
	soup_server_pause_message(server, msg);
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return;

error_unref_msg:
	smlLinkDeref(link_);
error:
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, error->message);
	g_error_free(error);
}

#endif /* HAVE_LIBSOUP22 */

static gboolean
smlTransportHttpServerSetConfigOption (SmlTransport *tsp,
                                       const gchar *name,
                                       const gchar *value,
                                       GError **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);
	SmlTransportHttpServerEnv *env = tsp->transport_data;	

	if (!strcmp(name, SML_TRANSPORT_CONFIG_PORT)) {
		env->port = atoi(value);
		if (!(env->port > 0 && env->port < 65535)) {
			g_set_error(error, SML_ERROR, SML_ERROR_INTERNAL_MISCONFIGURATION, "specified port was wrong");
			smlTrace(TRACE_EXIT_ERROR, "%s - %s", __func__, (*error)->message);
			return FALSE;
		}
		smlTrace(TRACE_INTERNAL, "%s: Port %i detected", __func__, env->port);
	} else if (!strcmp(name, SML_TRANSPORT_CONFIG_URL)) {
		env->url = g_strdup(value);
		smlTrace(TRACE_INTERNAL, "%s: URL %s detected", __func__, VA_STRING(env->url));
	} else if (!strcmp(name, SML_TRANSPORT_CONFIG_SSL_KEY)) {
		env->ssl_key = g_strdup(value);
		smlTrace(TRACE_INTERNAL, "%s: SSL key %s detected", __func__, VA_STRING(env->ssl_key));
	} else if (!strcmp(name, SML_TRANSPORT_CONFIG_SSL_SERVER_CERT)) {
		env->ssl_cert = g_strdup(value);
		smlTrace(TRACE_INTERNAL, "%s: SSL server certificate %s detected", __func__, VA_STRING(env->ssl_cert));
	} else {
		g_set_error(error, SML_ERROR, SML_ERROR_INTERNAL_MISCONFIGURATION, "Unknown parameter %s found.", name);
		smlTrace(TRACE_EXIT_ERROR, "%s - %s", __func__, (*error)->message);
		return FALSE;
	}

	smlTrace(TRACE_EXIT, "%s", __func__);
	return TRUE;
}

static gboolean
smlTransportHttpServerInit (SmlTransport *tsp,
                            GError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p)", __func__, tsp, error);
	CHECK_ERROR_REF
	smlAssert(tsp);
	smlAssert(tsp->context);
	smlAssert(tsp->transport_data);
	SmlTransportHttpServerEnv *env = tsp->transport_data;	

	if (!env->port)
	{
		g_set_error(error, SML_ERROR, SML_ERROR_INTERNAL_MISCONFIGURATION,
			"The HTTP server needs a port where it has to run.");
		goto error;
	}

	if (!env->url)
	{
		/* configure a default location */
		env->url = g_strdup("/");
	}
	
	smlTrace(TRACE_INTERNAL, "%s: config: port %i, url %s", __func__, env->port, VA_STRING(env->url));
	smlTrace(TRACE_INTERNAL, "%s: http server uses context %p.", __func__, tsp->context);

	if(!env->ssl_key || !env->ssl_cert) {
		env->server = soup_server_new(
				SOUP_SERVER_ASYNC_CONTEXT, tsp->context,
				SOUP_SERVER_PORT, env->port,
				NULL);
	} else {
		env->server = soup_server_new(
				SOUP_SERVER_ASYNC_CONTEXT, tsp->context,
				SOUP_SERVER_PORT, env->port,
				SOUP_SERVER_SSL_CERT_FILE, env->ssl_cert,
				SOUP_SERVER_SSL_KEY_FILE, env->ssl_key,
				NULL);
	}
	if (!env->server) {
		g_set_error(error, SML_ERROR, SML_ERROR_INTERNAL_MISCONFIGURATION, "Unable to spawn server");
		goto error;
	}
	
#ifdef HAVE_LIBSOUP22
	soup_server_add_handler(env->server, NULL, NULL, smlTransportHttpServerCallback, NULL, env);
#else
	soup_server_add_handler(env->server, NULL, smlTransportHttpServerCallback, env, NULL);
#endif
	
	soup_server_run_async(env->server);

	smlTrace(TRACE_EXIT, "%s", __func__);
	return TRUE;
error:
	if (env->url)
		smlSafeCFree(&(env->url));
	smlSafeFree((gpointer *)&env);
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, (*error)->message);
	return FALSE;
}

static void smlTransportHttpServerDisconnect(void *data, void *linkdata)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p)", __func__, data, linkdata);
	smlAssert(linkdata);
	SmlTransportHttpServerEnv *env = data;
	SmlLinkHttpServerEnv *linkenv = linkdata;

	/* remove session from sessionToUri */
	smlTrace(TRACE_INTERNAL, "%s: remove link from session/uri cache", __func__);
	if (linkenv->session)
	{
		char *uri = g_hash_table_lookup(linkenv->env->sessionToUri, linkenv->session);
		g_hash_table_remove(linkenv->env->sessionToUri, linkenv->session);
		if (uri)
			smlSafeCFree(&uri);
		/* This is not a bug. We added two references to session
		 * because the session is used in two locations.
		 */
		smlSessionUnref(linkenv->session);
		smlSessionUnref(linkenv->session);
		linkenv->session = NULL;
	}

	/* remove link from uriToLink */
	SmlLink *link_ = NULL;
	smlTrace(TRACE_INTERNAL, "%s: remove link from uri/link cache", __func__);
	if (linkenv->url)
	{
		link_ = g_hash_table_lookup(linkenv->env->uriToLink, linkenv->url);
		g_hash_table_remove(linkenv->env->uriToLink, linkenv->url);
		smlLinkDeref(link_);
		smlSafeCFree(&(linkenv->url));
	}

	/* save link for disconnect event */
	link_ = linkenv->link;

	/* signal disconnect to socket and potential client */
	if (linkenv->msg)
	{
		smlTrace(TRACE_INTERNAL, "%s: close open message/connection", __func__);
		soup_message_set_status (linkenv->msg, SOUP_STATUS_SERVICE_UNAVAILABLE);
#ifdef HAVE_LIBSOUP22
		//soup_server_message_set_encoding (
		//	SOUP_SERVER_MESSAGE (linkenv->msg),
		//	SOUP_TRANSFER_CONTENT_LENGTH);
		soup_message_io_unpause(linkenv->msg);
#else
		soup_server_unpause_message(linkenv->env->server, linkenv->msg);
#endif
		linkenv->msg = NULL;
	}

	/* cleanup */
	smlTrace(TRACE_INTERNAL, "%s: free memory", __func__);
	linkenv->env = NULL;
	link_->link_data = NULL;
	smlSafeFree((gpointer *)&linkenv);

	smlTrace(TRACE_INTERNAL, "%s: signal and unref link", __func__);
	smlTransportReceiveEvent(env->tsp, link_, SML_TRANSPORT_EVENT_DISCONNECT_DONE, NULL, NULL);
	smlLinkDeref(link_);
	smlTrace(TRACE_EXIT, "%s", __func__);
}

static void smlTransportHttpServerFreeResponseURI(gpointer key)
{
	smlTrace(TRACE_ENTRY, "%s(%p)", __func__, key);
	smlSafeCFree((char **)&key);
	smlTrace(TRACE_EXIT, "%s", __func__);
}

static gboolean smlTransportHttpServerFreeUriToLink(
			gpointer key,
			gpointer value,
			gpointer user_data)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, key, value, user_data);
	// SmlTransportHttpServerEnv *env = user_data;
	char *uri = key;
	SmlLink *link_ = value;

	smlLinkDeref(link_);
	smlSafeCFree(&uri);

	smlTrace(TRACE_EXIT, "%s", __func__);
	return TRUE;
}

static gboolean smlTransportHttpServerFreeSessionToUri(
			gpointer key,
			gpointer value,
			gpointer user_data)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, key, value, user_data);
	// SmlTransportHttpServerEnv *env = user_data;
	SmlSession *session = key;
	char *uri = value;

	smlSafeCFree(&uri);
	smlSessionUnref(session);

	smlTrace(TRACE_EXIT, "%s", __func__);
	return TRUE;
}

static gboolean
smlTransportHttpServerCleanupSocket (gpointer data,
                                     GError **error)
{
	smlTrace(TRACE_ENTRY, "%s", __func__);
	CHECK_ERROR_REF
	smlAssert(data);

	SoupServer *server = data;
	
	soup_server_quit(server);
	g_object_unref(server);

	smlTrace(TRACE_EXIT, "%s", __func__);
	return TRUE;
}

static gboolean
smlTransportHttpServerFinalize (void *data,
                                GError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p)", __func__, data, error);
	CHECK_ERROR_REF
	smlAssert(data);
	SmlTransportHttpServerEnv *env = data;
	
	smlAssert(env->tsp);
	
	if (env->server) {
		/* If there was no server init
		 * then there is no server.
		 * (e.g. failure after smlTransportHttpServerNew)
		 */
		if (!smlThreadCallFunction(
		         env->tsp->thread,
		         smlTransportHttpServerCleanupSocket,
		         env->server,
			 error))
			goto error;
		env->server = NULL;
	}

	if (env->url)
		smlSafeCFree(&(env->url));

	guint count = 0;
	guint removed = 0;

	/* cleanup hash table uriToLink */

	count = g_hash_table_size(env->uriToLink);
	removed = g_hash_table_foreach_steal(
			env->uriToLink,
			smlTransportHttpServerFreeUriToLink,
			env);
	if (count != removed)
	{
		g_set_error(error, SML_ERROR, SML_ERROR_GENERIC,
			"The glib function g_hash_table_foreach_steal could " \
			"not remove all key/value pairs from uriToLink (%u of %u).",
			removed, count);
		goto error;
	} else {
		g_hash_table_unref(env->uriToLink);
		env->uriToLink = NULL;
	}

	/* cleanup hash table sessionToUri */

	count = g_hash_table_size(env->sessionToUri);
	removed = g_hash_table_foreach_steal(
			env->sessionToUri,
			smlTransportHttpServerFreeSessionToUri,
			env);
	if (count != removed)
	{
		g_set_error(error, SML_ERROR, SML_ERROR_GENERIC,
			"The glib function g_hash_table_foreach_steal could not " \
			"remove all key/value pairs from sessionToUri. (%u of %u)",
			removed, count);
		goto error;
	} else {
		g_hash_table_unref(env->sessionToUri);
		env->sessionToUri = NULL;
	}

	smlSafeFree((gpointer *)&env);
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return TRUE;
error:
	smlTrace(TRACE_EXIT_ERROR, "%s - %s", __func__, (*error)->message);
	return FALSE;
}

static void
smlTransportHttpServerSend (void *userdata,
                            void *linkdata,
                            SmlTransportData *data,
                            GError *error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p, %p)", __func__, userdata, linkdata, data, error);
	// smlAssert(userdata);
	smlAssert(linkdata);
	smlAssert(error || data);
	// SmlTransportHttpServerEnv *env = userdata;
	SmlLinkHttpServerEnv *linkenv = linkdata;
	smlAssert(linkenv->msg);
	
	SoupMessage *msg = linkenv->msg;
	linkenv->msg = NULL;
	
	if (error)
		goto error_free_message;

	soup_message_set_status (msg, SOUP_STATUS_OK);
#ifdef HAVE_LIBSOUP22
	soup_server_message_set_encoding (SOUP_SERVER_MESSAGE(msg), SOUP_TRANSFER_CONTENT_LENGTH);
#endif

	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:
			g_set_error(&error, SML_ERROR, SML_ERROR_GENERIC, "Unknown Mimetype");
			goto error_free_message;
	}
	
#ifdef HAVE_LIBSOUP22
	// duplicate the data because sometimes data is freed to early
	char *soupcopy = (char *) smlTryMalloc0(data->size, &error);
	if (error)
		goto error_free_message;
	memcpy(soupcopy, data->data, data->size);
	soup_message_set_response (msg, content_type, SOUP_BUFFER_SYSTEM_OWNED,
				   soupcopy, data->size);
	soup_message_io_unpause(msg);
#else
	soup_message_set_response(msg, content_type,
				  SOUP_MEMORY_COPY, data->data, data->size);
	soup_server_unpause_message(linkenv->env->server, msg);
#endif

	smlTrace(TRACE_EXIT, "%s", __func__);
	return;
	
error_free_message:
	if (smlErrorGetClass(&error) <= SML_ERRORCLASS_RETRY)
		soup_message_set_status_full(msg, SOUP_STATUS_BAD_REQUEST, error->message);
	else
		soup_message_set_status_full(msg, SOUP_STATUS_INTERNAL_SERVER_ERROR, error->message);

#ifdef HAVE_LIBSOUP22
	soup_server_message_set_encoding (SOUP_SERVER_MESSAGE (msg), SOUP_TRANSFER_CONTENT_LENGTH);
	soup_message_io_unpause(msg);
#else
	soup_server_unpause_message(linkenv->env->server, msg);
#endif

	g_error_free(error);
	smlTrace(TRACE_EXIT, "%s: Sent Error", __func__);
	return;
}

gchar*
smlTransportHttpServerGetResponseURI (SmlLink *link_,
                                      SmlSession *session,
                                      GError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, link_, session, error);
	/* Do not change the assertion to simple return FALSE stuff
	 * because to many developers incl. me ignore returned FALSE
	 * if it is only configuration.
	 */
	CHECK_ERROR_REF
	smlAssert(link_);
	smlAssert(session);
	smlAssert(link_->link_data);
	SmlLinkHttpServerEnv *linkenv = link_->link_data;
	smlAssert(linkenv->env);
	SmlTransportHttpServerEnv *env = linkenv->env;
	char *responseURI = NULL;
	smlTrace(TRACE_INTERNAL, "%s: params checked", __func__);

	/*           SECURITY NOTE
	 * This is the function were we have to create and validate
	 * the used URLs. If you are not sure about what you are doing
	 * then do not touch the code and contact the developers.
	 */

	/* check the session */

	if (!g_hash_table_lookup(env->sessionToUri, session))
	{
		/* This is a potentially new session.
		 * The URL must be the original URL.
		 */

		smlTrace(TRACE_INTERNAL, "%s: new session detected", __func__);

		char *soupURI = soup_uri_to_string (soup_message_get_uri(linkenv->msg), TRUE);
		if (strcmp(env->url, soupURI))
		{
			/* SECURITY: This can be an attack. */
			g_set_error(error, SML_ERROR, SML_ERROR_RETRY_LATER,
				"A new session with a configured HTTP session was detected.");
			smlSafeCFree(&soupURI);
			goto error;
		}
		smlSafeCFree(&soupURI);

    		/* create new random response ID
		 * 
		 * glib only supports 32 bit random numbers.
		 * Therefore 4 random numbers are created
		 * and a fixed length is used.
		 *
		 * Do not send the response ID as HTTP GET parameter
		 * because this can cause conflicts
		 * because SyncML requires HTTP POST and
		 * libsoup 2.4 filters such parameters
		 * from the path in the callback interface.
		 */
		soupURI = soup_uri_to_string (soup_message_get_uri(linkenv->msg), FALSE),
		responseURI = g_strdup_printf("%s0X%08X%08X%08X%08X",
					soupURI,
					g_random_int (),
					g_random_int (),
					g_random_int (),
					g_random_int ());
		smlSafeCFree(&soupURI);

		/* map URI to link */
		if (g_hash_table_lookup(env->uriToLink, responseURI))
		{
			/* SECURITY: This can be an insecure random number generator. */
			g_set_error(error, SML_ERROR, SML_ERROR_RETRY_LATER,
				"A fresh random session ID is already in use.");
			goto error;
		}
		smlLinkRef(link_);
		g_hash_table_insert(env->uriToLink, g_strdup(responseURI), link_);
		smlTrace(TRACE_INTERNAL, "%s: ResponseURI is %s.", __func__, VA_STRING(responseURI));

		/* map session to URI */
		g_hash_table_insert(env->sessionToUri, session, g_strdup(responseURI));
		smlSessionRef(session);

		/* cache session and URI in linkenv - necessary for cleanup */
		linkenv->session = session;
		smlSessionRef(session);
		linkenv->url = g_strdup(responseURI);
	} else {
		/* The session already exists.
		 * The referenced URL must be checked against the cached URL.
		 * The used URL must be checked against the cached URL too.
		 */

		smlTrace(TRACE_INTERNAL, "%s: cached session detected", __func__);

		char *soupURI = soup_uri_to_string (soup_message_get_uri(linkenv->msg), FALSE);
		const char *sessionURI = g_hash_table_lookup(env->sessionToUri, session);
		if (strcmp(soupURI, sessionURI))
		{
			/* SECURITY: This is a potential attack. */
			g_set_error(error, SML_ERROR, SML_ERROR_RETRY_LATER,
				"Another session (%s) re-used an already existing HTTP session (%s).",
				soupURI, sessionURI);
			smlSafeCFree(&soupURI);
			goto error;
		}

		SmlLink *cachedLink = g_hash_table_lookup(env->uriToLink, soupURI);
		if (!cachedLink)
		{
			/* SECURITY: This is an internal bug. */
			g_set_error(error, SML_ERROR, SML_ERROR_GENERIC,
				"Cannot find link for used URL.");
			smlSafeCFree(&soupURI);
			goto error;
		}
		if (cachedLink != link_)
		{
			/* SECURITY: This is an internal bug. */
			g_set_error(error, SML_ERROR, SML_ERROR_GENERIC,
				"The link objects mismatch.");
			smlSafeCFree(&soupURI);
			goto error;
		}
		if (strcmp(linkenv->url, soupURI))
		{
			/* SECURITY: This can be an attack. */
			g_set_error(error, SML_ERROR, SML_ERROR_GENERIC,
				"The URL of the link object is wrong.");
			smlSafeCFree(&soupURI);
			goto error;
		}

		responseURI = soupURI;
	}
	smlTrace(TRACE_EXIT, "%s - %s", __func__, VA_STRING(responseURI));
	return responseURI;
error:
	smlTrace(TRACE_EXIT_ERROR, "%s - %s", __func__, (*error)->message);
	return NULL;
}

gboolean
smlTransportHttpServerNew (SmlTransport *tsp,
                           GError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p)", __func__, tsp, error);
	CHECK_ERROR_REF
	smlAssert(tsp);

	tsp->functions.set_config_option = smlTransportHttpServerSetConfigOption;
	tsp->functions.initialize = smlTransportHttpServerInit;
	tsp->functions.finalize = smlTransportHttpServerFinalize;
	tsp->functions.send = smlTransportHttpServerSend;
	tsp->functions.get_response_uri = smlTransportHttpServerGetResponseURI;
	tsp->functions.disconnect = smlTransportHttpServerDisconnect;

	SmlTransportHttpServerEnv *env = smlTryMalloc0(sizeof(SmlTransportHttpServerEnv), error);
	if (!env)
		return FALSE;
	tsp->transport_data = env;
	env->tsp = tsp;
	env->sessionToUri = g_hash_table_new(g_direct_hash, g_direct_equal);
	env->uriToLink = g_hash_table_new_full(g_str_hash, g_str_equal, smlTransportHttpServerFreeResponseURI, NULL);

	smlTrace(TRACE_EXIT, "%s", __func__);
	return TRUE;
}

